diff --git a/.changeset/quiet-foxes-focus.md b/.changeset/quiet-foxes-focus.md
new file mode 100644
index 0000000000..283e88f525
--- /dev/null
+++ b/.changeset/quiet-foxes-focus.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: don't throw `state_unsafe_mutation` when the focused element is removed while `bind:activeElement` is used
diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/document.js b/packages/svelte/src/internal/client/dom/elements/bindings/document.js
index 420e4359ed..8b238fc3cb 100644
--- a/packages/svelte/src/internal/client/dom/elements/bindings/document.js
+++ b/packages/svelte/src/internal/client/dom/elements/bindings/document.js
@@ -1,4 +1,4 @@
-import { listen } from './shared.js';
+import { listen, without_reactive_context } from './shared.js';
/**
* @param {(activeElement: Element | null) => void} update
@@ -12,6 +12,7 @@ export function bind_active_element(update) {
return;
}
- update(document.activeElement);
+ // Removing the focused element fires `focusout` while an effect is running
+ without_reactive_context(() => update(document.activeElement));
});
}
diff --git a/packages/svelte/tests/runtime-browser/samples/bind-active-element-remove-focused/_config.js b/packages/svelte/tests/runtime-browser/samples/bind-active-element-remove-focused/_config.js
new file mode 100644
index 0000000000..86a0f65551
--- /dev/null
+++ b/packages/svelte/tests/runtime-browser/samples/bind-active-element-remove-focused/_config.js
@@ -0,0 +1,27 @@
+import { test, ok } from '../../assert';
+
+export default test({
+ mode: ['client'],
+ async test({ assert, target }) {
+ /** @type {string[]} */
+ const errors = [];
+ /** @param {ErrorEvent} event */
+ const on_error = (event) => {
+ errors.push(event.message);
+ event.preventDefault();
+ };
+ window.addEventListener('error', on_error);
+
+ const button = target.querySelector('button');
+ ok(button);
+
+ button.focus();
+ button.click();
+ await new Promise((r) => setTimeout(r, 0));
+
+ window.removeEventListener('error', on_error);
+
+ assert.equal(errors.length, 0, errors.join('\n'));
+ assert.equal(target.querySelector('button'), null);
+ }
+});
diff --git a/packages/svelte/tests/runtime-browser/samples/bind-active-element-remove-focused/main.svelte b/packages/svelte/tests/runtime-browser/samples/bind-active-element-remove-focused/main.svelte
new file mode 100644
index 0000000000..93e6e8f308
--- /dev/null
+++ b/packages/svelte/tests/runtime-browser/samples/bind-active-element-remove-focused/main.svelte
@@ -0,0 +1,10 @@
+
+
+
+
+{#if show}
+
+{/if}