diff --git a/.changeset/loud-numbers-flow.md b/.changeset/loud-numbers-flow.md
new file mode 100644
index 000000000000..68bd7cfe6f8d
--- /dev/null
+++ b/.changeset/loud-numbers-flow.md
@@ -0,0 +1,5 @@
+---
+"svelte": patch
+---
+
+fix: restore value after attribute removal during hydration
diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js
index 5e39228f2b11..e3816a733fb1 100644
--- a/packages/svelte/src/internal/client/dom/elements/attributes.js
+++ b/packages/svelte/src/internal/client/dom/elements/attributes.js
@@ -16,8 +16,12 @@ import * as w from '../../warnings.js';
*/
export function remove_input_attr_defaults(dom) {
if (hydrating) {
+ // using getAttribute instead of dome.value allow us to have
+ // null instead of "on" if the user didn't set a value
+ const value = dom.getAttribute('value');
set_attribute(dom, 'value', null);
set_attribute(dom, 'checked', null);
+ if (value) dom.value = value;
}
}
diff --git a/packages/svelte/tests/runtime-browser/samples/fine-grained-hydration-clean-attr/_config.js b/packages/svelte/tests/runtime-browser/samples/fine-grained-hydration-clean-attr/_config.js
new file mode 100644
index 000000000000..6602e5983459
--- /dev/null
+++ b/packages/svelte/tests/runtime-browser/samples/fine-grained-hydration-clean-attr/_config.js
@@ -0,0 +1,10 @@
+import { test } from '../../assert';
+
+export default test({
+ html: ``,
+ ssrHtml: ``,
+ test({ window, assert, target, mod }) {
+ const input = target.querySelector('input');
+ assert.equal(input?.checked, true);
+ }
+});
diff --git a/packages/svelte/tests/runtime-browser/samples/fine-grained-hydration-clean-attr/main.svelte b/packages/svelte/tests/runtime-browser/samples/fine-grained-hydration-clean-attr/main.svelte
new file mode 100644
index 000000000000..9dc489489b74
--- /dev/null
+++ b/packages/svelte/tests/runtime-browser/samples/fine-grained-hydration-clean-attr/main.svelte
@@ -0,0 +1,5 @@
+
+
+