diff --git a/.changeset/seven-bees-tell.md b/.changeset/seven-bees-tell.md
new file mode 100644
index 000000000000..753a93add2d1
--- /dev/null
+++ b/.changeset/seven-bees-tell.md
@@ -0,0 +1,5 @@
+---
+"svelte": patch
+---
+
+fix: allow slot attribute inside snippets
diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js
index 8ae8c264bdf4..f4778e432202 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/validation.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js
@@ -256,8 +256,16 @@ function validate_attribute_name(attribute) {
* @param {boolean} is_component
*/
function validate_slot_attribute(context, attribute, is_component = false) {
+ const parent = context.path.at(-2);
let owner = undefined;
+ if (parent?.type === 'SnippetBlock') {
+ if (!is_text_attribute(attribute)) {
+ e.slot_attribute_invalid(attribute);
+ }
+ return;
+ }
+
let i = context.path.length;
while (i--) {
const ancestor = context.path[i];
@@ -283,7 +291,7 @@ function validate_slot_attribute(context, attribute, is_component = false) {
owner.type === 'SvelteComponent' ||
owner.type === 'SvelteSelf'
) {
- if (owner !== context.path.at(-2)) {
+ if (owner !== parent) {
e.slot_attribute_invalid_placement(attribute);
}
diff --git a/packages/svelte/tests/runtime-runes/samples/custom-element-slot-in-snippet/Component.svelte b/packages/svelte/tests/runtime-runes/samples/custom-element-slot-in-snippet/Component.svelte
new file mode 100644
index 000000000000..568baa0be92a
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/custom-element-slot-in-snippet/Component.svelte
@@ -0,0 +1,18 @@
+
+
+
+
+
+ {@render children()}
+
diff --git a/packages/svelte/tests/runtime-runes/samples/custom-element-slot-in-snippet/_config.js b/packages/svelte/tests/runtime-runes/samples/custom-element-slot-in-snippet/_config.js
new file mode 100644
index 000000000000..d40d8f6c7245
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/custom-element-slot-in-snippet/_config.js
@@ -0,0 +1,22 @@
+import { test } from '../../test';
+
+export default test({
+ mode: ['client', 'server'],
+ html: `Default Slotted`,
+ test({ target, assert }) {
+ const shadowRoot = /** @type {ShadowRoot} */ (
+ target.querySelector('my-custom-element')?.shadowRoot
+ );
+ const [defaultSlot, namedSlot] = shadowRoot.querySelectorAll('slot');
+ const assignedDefaultNodes = defaultSlot.assignedNodes();
+ const assignedNamedNodes = namedSlot.assignedNodes();
+
+ assert.equal(assignedDefaultNodes.length, 1);
+ assert.equal(assignedNamedNodes.length, 1);
+ assert.htmlEqual(assignedDefaultNodes[0].textContent || '', `Default`);
+ assert.htmlEqual(
+ /** @type {HTMLElement} */ (assignedNamedNodes[0]).outerHTML,
+ `Slotted`
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/custom-element-slot-in-snippet/main.svelte b/packages/svelte/tests/runtime-runes/samples/custom-element-slot-in-snippet/main.svelte
new file mode 100644
index 000000000000..6902623f7511
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/custom-element-slot-in-snippet/main.svelte
@@ -0,0 +1,10 @@
+
+
+
+ {#snippet children()}
+ Default
+ Slotted
+ {/snippet}
+