Skip to content

Restricting children to Specific Components in Svelte with TypeScript #16025

@OTheNonE

Description

@OTheNonE

Describe the problem

This is how you currently render the children within a component:

<!--my-component.svelte-->
<script lang="ts">
    import type { Snippet } from "svelte";

    type Props = {
        children?: Snippet
    }

    let { children }: Props = $props()

</script>

<div>
    My children are:
</div>

{@render children?.()}
<!--+page.svelte-->
<MyComponent>
  <p> Peter </p>
</MyComponent>

Although, using typescript, there is no way to define what the content of children within <MyComponent> should be.

Describe the proposed solution

I would like to define the type of children in the component, so that typescript only allows some specific elements or components to be children:

<!-- This should trigger an error in typescript. -->
<MyComponent>
  <p> Peter </p>
</MyComponent>

<!-- This should also trigger an error in typescript. -->
<MyComponent>
  <NotTheTypedComponentInMyComponent/>
</MyComponent>

<!-- This should be OK. -->
<MyComponent>
  <TheCorrectComponent/>
</MyComponent>

This should be done somehow similar to this:

<!--my-component.svelte-->
<script lang="ts">
    import MyComponent from "$lib/my-component.svelte"

    type Props = {
        children?: MyComponent // This does not work, but something similar to this.
    }

    let { children }: Props = $props()
    ...

Importance

would make my life easier

Activity

7nik

7nik commented on May 29, 2025

@7nik
Contributor

What is the point of limiting children type? Why your component cannot accept <p> Peter </p> or <NotTheTypedComponentInMyComponent/>?

MotionlessTrain

MotionlessTrain commented on May 29, 2025

@MotionlessTrain
Contributor

If it can only accept a couple of components, it would make more sense to give the component as property instead, and have that in the correct place in the template instead. Then it can be typed much more easily

paoloricciuti

paoloricciuti commented on May 29, 2025

@paoloricciuti
Member

What is the point of limiting children type? Why your component cannot accept <p> Peter </p> or <NotTheTypedComponentInMyComponent/>?

Sometimes it could be useful, if you build something like this for example you might want the children to only be TabItems because every other children will not work or even disrupt your component.

Someone might argue that this is not the right way of doing it but i think it could be useful sometimes. The real problem is that snippets don't "return" anything so it could be difficult to type check that.

Regardless this is more an issue for language-tools as svelte can't do much here.

7nik

7nik commented on May 29, 2025

@7nik
Contributor

Can it even be type-checked? Tab expects exactly TabItems in children, but for TS, there is no difference between <TabItem name="tab 1"> and <RandomComponent name="John"> with compatible set of props.

MotionlessTrain

MotionlessTrain commented on May 29, 2025

@MotionlessTrain
Contributor

And what if ’s template actually is nothing else but a bunch of s?

paoloricciuti

paoloricciuti commented on May 29, 2025

@paoloricciuti
Member

Yeah it's definitely not straight forward, probably not even doable.

OTheNonE

OTheNonE commented on May 30, 2025

@OTheNonE
Author

I can let you in on my use-case:

I am creating a small svelte-wrapper for my implementation of OpenLayers in my app. This is a tiny somewhat simplified part of the code:

<!--+page.svelte-->
<script lang="ts">    
    import { Map, OpenStreetMap } from "$lib/ol/components"
</script>

<Map>
    <TileLayer>
        <OpenStreetMap/>
    </TileLayer>
</Map>
<!--map.svelte-->
<script lang="ts">
    import { setMapContext } from "./ctx";
    import type { Snippet } from "svelte";
    import Map from "ol/Map.js"
    import View from "ol/View";

    type Props = {
        children?: Snippet
    }

    const { children }: Props = $props()

    let map_element: HTMLDivElement
    let map = $state<Map>()
    
    setMapContext(() => map)

    const view = new View({ ... })

    $effect(() => {

        map = new Map({
            target: map_element,
            view
        })

        return () => map?.dispose()
    })

</script>


<div class="h-full" bind:this={map_element}>
    {#if map}
        {@render children?.()}
    {/if}
</div>
<!--tile-layer.svelte-->
<script lang="ts">
    import TileLayer from "ol/layer/Tile";
    import { OSM } from "ol/source";
    import { setTileLayerContext, getMapContext } from "./ctx";
    import Layer from "ol/layer/Layer";

    const layer = new TileLayer()
    
    setTileLayerContext(() => layer)

    const map = getMapContext()

    $effect(() => {
        
        map.addLayer(layer)
        
        return () => map.removeLayer(layer)
    })

</script>
<!--openstreetmap.svelte-->
<script lang="ts">
    import TileLayer from "ol/layer/Tile";
    import { OSM } from "ol/source";
    import { getTileLayerContext } from "./ctx";
    
    const source = new OSM()
    
    const layer = getTileLayerContext()

    $effect(() => {
        
        layer.setSource(source)
        
        return () => layer.removeLayer(source)
    })

</script>

In the map.svelte and tile-layer.svelte component, all components are allowed as children, eventhough it does not make sense for some components to be child of map. E.g. the class OSM() must be a child of TileLayer(), and TileLayer() must be child of Map(), through the method of .addLayer() and setSource() (enforced by Typescript). I was hoping that i could enforce the same restrictions within components childrens.

7nik

7nik commented on May 30, 2025

@7nik
Contributor

For now, the best you can do is throwing an error <X> must be inside <Y> when a component fails to get the right context.

added this to the one day milestone on Jun 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @Rich-Harris@dummdidumm@MotionlessTrain@paoloricciuti@7nik

        Issue actions

          Restricting `children` to Specific Components in Svelte with TypeScript · Issue #16025 · sveltejs/svelte