Building components
The conventions every Sveltier component follows. Your own components can follow them too, and then behave like the built-in ones.
Shared props and defaults
A component with a variant, size, density, colour or rounding takes the shared props with their exact names and values,
resolves them with useDefaults, and passes every resolved
value to sharedClasses.
IDs
Labels, hints and panels are linked by ID. Svelte's $props.id() gives each instance an
ID that is the same on the server and in the browser, so the links survive hydration.
<script lang="ts">
let { id: idProp, label, hint }: { id?: string; label: string; hint?: string } = $props();
// Unique per instance, and the same on the server and in the browser.
const uid = $props.id();
const id = $derived(idProp ?? uid);
</script>
<label for={id}>{label}</label>
<input {id} aria-describedby={hint ? `${id}-hint` : undefined} />
{#if hint}<p id="{id}-hint">{hint}</p>{/if}- The main element takes the
idprop when one is passed. - Parts derive from it:
{id}-label,{id}-hint,{id}-error,{id}-panel. - Reference only parts that render, so screen readers never point at nothing.
- Rendering several Svelte roots on one page? Give each its own
idPrefixinrender()andhydrate().
The root element
Every component renders one root element and passes through the attributes it does not use, a class and a bindable ref. ElementProps types this: the
component's own props, then every attribute of the element it does not redefine, so a shared size or color never clashes with the HTML attribute of the same name.
<script lang="ts" module>
import type { HTMLAttributes } from 'svelte/elements';
import type { ElementProps } from 'sveltier';
// Own props first; every other <span> attribute passes through.
export type DeltaProps = ElementProps<
HTMLSpanElement,
HTMLAttributes<HTMLSpanElement>,
{ value: number }
>;
</script>
<script lang="ts">
import { useLocale } from 'sveltier';
let { value, class: className, ref = $bindable(null), ...rest }: DeltaProps = $props();
const locale = useLocale();
</script>
<!-- The spread comes first, so the attributes the component controls come after and win. -->
<span
bind:this={ref}
{...rest}
class={['delta', value < 0 ? 'delta--down' : 'delta--up', className]}
>
{locale.formatPercent(value, { minimumFractionDigits: 2, signDisplay: 'exceptZero' })}
</span>- Destructure the own props,
classandref; the rest is everything else. - Spread the rest first. Attributes the component controls (its classes, role and ARIA state)
come after the spread, so a stray attribute cannot break them; callbacks such as
onclickpass through. - Merge
classlast in an array: strings, arrays and objects all work, and the user's classes follow the component's. refis$bindable(null)and set withbind:this.