Search code examples
javascripttypescriptsveltesvelte-5

TS error when building Svelte 5 component that renders either anchor or button element based on presence of href prop


I'm trying to create a Svelte 5 component that renders either a button or anchor element based on the presence of a href prop, however I'm running into a TS error.

Button.svelte

<script lang="ts">
  import type { HTMLButtonAttributes, HTMLAnchorAttributes } from 'svelte/elements';

  type Props = HTMLAnchorAttributes | HTMLButtonAttributes;
  
  const {
    href,
    children,
    ...restProps
  }: Props = $props();
</script>

{#if href}
  <a {href} {...restProps}>
    {@render children()}
  </a>
{:else}
  <button {...restProps}>
    {@render children()}
  </button>
{/if}

The above produces two errors:

  1. When destructuring props: href does not exist on Props type.
  2. When spreading restProps: Argument of type ... is not assignable to parameter of type 'HTMLProps<"a", HTMLAtributes>'

I thought checking for the existence of href would act as a type guard allowing me to spread the correct props on the correct element.

Any insights appreciated. Thanks in advance.


Solution

  • By splitting the properties via the destructuring, the relationship between href and the other props is lost. Checking href then has no effect on anything other than href itself.

    If you don't split the properties and check via in, the anchor case works, but the button will have an issue because both anchors and buttons have a type attribute with different possible values and lack of a href attribute does logically not imply that the user only specified button attributes because href is not required to exist in an anchor.

    This could be enforced with a more strict Props type, though:

    <script lang="ts">
        import type { HTMLButtonAttributes, HTMLAnchorAttributes } from 'svelte/elements';
    
        type Props =
            ({ href: string } & HTMLAnchorAttributes) |
            HTMLButtonAttributes;
    
        const { children, ...props }: Props = $props();
    </script>
    
    {#if 'href' in props}
        <a {...props}>
            {@render children?.()}
        </a>
    {:else}
        <button {...props}>
            {@render children?.()}
        </button>
    {/if}