Creating Custom Svelte Component with Children Props in Typescript

Typing children props in Svelte 5

Let’s say we have a custom Button component. And the API needs to be as simple as:

<button variant="primary">Hello <Icon name="wave" /></button>

Looks like we need two things:

  • A prop for setting the variant
  • Ability to render anything within the Button.

Svelte 4 syntax

We needed an export let for the variant, and a slot for the children. So it was pretty simple:

<script lang="ts">
	export let variant: 'primary' | 'secondary' | 'tertiary';
</script>

<button on:click class="{variantClass(variant)}">
	<slot />
</button>

If you ask me, that looks damn fairly concise but a little magic. The onclick and ` have a huge impact on the component but where are they coming from? It’s not very clear. This has been fixed in Svelte 5.

Svelte 5 syntax

After looking at some examples online, my first implementation was this:

<script lang="ts">
   type Props = { variant: 'primary' | 'secondary' | 'tertiary'; };

  let { variant, children }: Props = $props();
</script>

<button>
   {@render children()}
</button>

Children however are not declared anywhere and I was getting a compilation error.

The $props documentation leaves a lot to be desired for Typescript and how to declare children. Got tired of hunting it down and ended up looking at one of my existing +layout.svelte where children was being used.

By default children props are available when invoking the $props function without specifying any types. When hovering over the children function, you get the following:

I think that Snippet type is what we want! Trying to import it, immediately svelte module came up and it seemed to do the trick. The error was gone. At this point I’m not certain I have the right solution, but after no complaints from the compiler I’m going with it.

import type { Snippet } from "svelte";

<script lang="ts">
  type Props = {
    children: Snippet<[]>;
    variant: 'primary' | 'secondary' | 'tertiary'
  };

  let { children, variant }: Props = $props();
</script>

<button class="{variantClass(variant)}">
   {@render children()}
</button>

The on:click has been deprecated

The other magical svelte 4 syntax was the on:click. By declaring it on the button and not providing any value, it means that the event is being forwarded to the consumer directly. In Svelte 5, we just need to declare it as part of props with a slightly different name onclick and the type is a function.

Here’s the final code:

<script lang="ts">
  import type { Snippet } from "svelte";
  type Props = {
    children: Snippet<\[\]>;
    variant: 'primary' | 'secondary' | 'tertiary';
    onclick: () => void;
  };

  let { children, variant }: Props = $props();
</script>

<button class="{variantClass(variant)}" {onclick}>
   {@render children()}
</button>

Hopefully this helps someone! Thanks for reading.