sveltesvelte-5

How to use svelte directives on components using svelte runes?


I have this svelte component that is currently using the use: directive from svelte runes:

<script>
    import {route} from "@mateothegreat/svelte5-router";

    let {children, className, ...rest} = $props();
</script>

<a
        use:route
        class={`btn ${className}`}
        {...rest}
>
    {@render children()}
</a>

When trying to pass it as a prop I get a render error: "Svelte: This type of directive is not valid on components". Here is how I'm trying to use the directive as a prop:

<ButtonLink
       onclick={() => $currentRoute = window.location.pathname}
       href={href}
       class:active={isActive}
    >

The class:active={isActive} is throwing the error. Is there a way to pass the use of directives to a component in svelte runes? If so how and what am I doing wrong?


Solution

  • use: cannot be used on components, but that is not the problem here since a is an element. class: also cannot be used on components, so you have to work around that.

    Since Svelte 5.16 you can pass classes as objects/arrays which uses clsx internally. So here you could pass an object using the flag:

    <script>
        import Component from './Component.svelte';
        let active = $state(false);
    </script>
    
    <label>
        <input bind:checked={active} type="checkbox"/>
        Active
    </label> <br>
    
    <Component class={{ 'red': active }} />
    
    <!-- Component.svelte -->
    <script>
        // Rename of `class` attribute on destructuring is required
        // since `class` is a keyword.
        // With just `className`, the attribute would also have
        // to be called just that: <Component className="...">
        const { class: className, ...rest } = $props();
    </script>
    
    <!-- className and fixed classes also need to be passed as
         object here, so values are resolved correctly. -->
    <span class={['big', className]}>Hello</span>
    ...
    

    Playground

    If you are not using this mechanism, you will have to manually turn the flag into a class string, e.g. in your example

    <ButtonLink class="{isActive ? 'active' : ''}">...
    

    Regarding the class attribute on components: You can also get it from rest props/all the props as long as the spread happens before setting class manually, that way it will be overwritten with the correct combination of local classes and passed in ones. E.g.

    <script>
        let { text, ...rest } = $props();
    </script>
    
    <span {...rest} class={['big', rest.class]}>
      {text}
    </span>