diff --git a/.changeset/thick-swans-type.md b/.changeset/thick-swans-type.md
new file mode 100644
index 000000000000..430cdbdc5b7c
--- /dev/null
+++ b/.changeset/thick-swans-type.md
@@ -0,0 +1,5 @@
+---
+"svelte": patch
+---
+
+feat: add read-only `bind:focused`
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js
index fa711c324f12..e9d3b519c724 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js
@@ -2727,7 +2727,9 @@ export const template_visitors = {
case 'checked':
call_expr = b.call(`$.bind_checked`, state.node, getter, setter);
break;
-
+ case 'focused':
+ call_expr = b.call(`$.bind_focused`, state.node, setter);
+ break;
case 'group': {
/** @type {import('estree').CallExpression[]} */
const indexes = [];
diff --git a/packages/svelte/src/compiler/phases/bindings.js b/packages/svelte/src/compiler/phases/bindings.js
index e3fdd7eb8c07..9a6f1c316b2f 100644
--- a/packages/svelte/src/compiler/phases/bindings.js
+++ b/packages/svelte/src/compiler/phases/bindings.js
@@ -21,6 +21,7 @@ export const binding_properties = {
event: 'durationchange',
omit_in_ssr: true
},
+ focused: {},
paused: {
valid_elements: ['audio', 'video'],
omit_in_ssr: true
diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/universal.js b/packages/svelte/src/internal/client/dom/elements/bindings/universal.js
index 8c40c04da5e3..f42f67d59472 100644
--- a/packages/svelte/src/internal/client/dom/elements/bindings/universal.js
+++ b/packages/svelte/src/internal/client/dom/elements/bindings/universal.js
@@ -1,4 +1,5 @@
import { render_effect } from '../../../reactivity/effects.js';
+import { listen } from './shared.js';
/**
* @param {'innerHTML' | 'textContent' | 'innerText'} property
@@ -67,3 +68,14 @@ export function bind_property(property, event_name, type, element, get_value, up
}
});
}
+
+/**
+ * @param {HTMLElement} element
+ * @param {(value: unknown) => void} update
+ * @returns {void}
+ */
+export function bind_focused(element, update) {
+ listen(element, ['focus', 'blur'], () => {
+ update(element === document.activeElement);
+ });
+}
diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js
index 2362698ab183..6e6488c0019f 100644
--- a/packages/svelte/src/internal/client/index.js
+++ b/packages/svelte/src/internal/client/index.js
@@ -44,7 +44,11 @@ export { bind_prop } from './dom/elements/bindings/props.js';
export { bind_select_value, init_select, select_option } from './dom/elements/bindings/select.js';
export { bind_element_size, bind_resize_observer } from './dom/elements/bindings/size.js';
export { bind_this } from './dom/elements/bindings/this.js';
-export { bind_content_editable, bind_property } from './dom/elements/bindings/universal.js';
+export {
+ bind_content_editable,
+ bind_property,
+ bind_focused
+} from './dom/elements/bindings/universal.js';
export { bind_window_scroll, bind_window_size } from './dom/elements/bindings/window.js';
export {
once,
diff --git a/packages/svelte/tests/runtime-legacy/samples/binding-focused/_config.js b/packages/svelte/tests/runtime-legacy/samples/binding-focused/_config.js
new file mode 100644
index 000000000000..02f2574f340e
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/binding-focused/_config.js
@@ -0,0 +1,19 @@
+import { flushSync } from 'svelte';
+
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, component, target, window }) {
+ const [in1, in2] = target.querySelectorAll('input');
+
+ flushSync(() => in1.focus());
+ assert.equal(window.document.activeElement, in1);
+ assert.equal(component.a, true);
+ assert.equal(component.b, false);
+
+ flushSync(() => in2.focus());
+ assert.equal(window.document.activeElement, in2);
+ assert.equal(component.a, false);
+ assert.equal(component.b, true);
+ }
+});
diff --git a/packages/svelte/tests/runtime-legacy/samples/binding-focused/main.svelte b/packages/svelte/tests/runtime-legacy/samples/binding-focused/main.svelte
new file mode 100644
index 000000000000..7ba892393dc7
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/binding-focused/main.svelte
@@ -0,0 +1,7 @@
+
+
+
+