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:
href
does not exist on Props type.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.
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}