Unique IDs
Svelte's native $props.id() generates an id unique to each component instance, consistent between server render and hydration — use it for label/for and aria links inside islands.
Static component (no hydration)
$props.id() works with zero JavaScript shipped — the id is minted during SSR, no hydration required.
$props.id(): s2
Two hydrated islands, two ids
Each instance gets its own id, so the label/for pairs never collide. Hydration reuses the server-generated value — the id you see was minted during SSR.
$props.id(): s3
$props.id(): s4
Server island
Deferred islands render in a separate request; Mochi namespaces their ids with the island's own id (via render's idPrefix) so they cannot collide with ids already on the page.
Server island that also hydrates
With mochi:defer mochi:hydrate the namespaced id is read back from the SSR markers when the fragment hydrates — click the button and the id stays exactly the same, proving
the value survived from the deferred render into the hydrated client.
Two client-only islands, two ids
Client-only islands are never server-rendered, so each $props.id() is minted in the browser at mount. Svelte draws these from a global counter — unique even across
separate mount() calls — so two independently-mounted islands still get distinct ids (e.g. c1 and c2) without any SSR pass to keep them apart.
<script lang="ts">
import { files } from './files.ts';
import Field from './Field.svelte';
import LabeledField from './LabeledField.svelte';
import ServerStamp from './ServerStamp.svelte';
import ServerHydratedStamp from './ServerHydratedStamp.svelte';
import ClientStamp from './ClientStamp.svelte';
const sources = await loadSources(files);
</script>
<h3>Static component (no hydration)</h3>
<p class="hint">$props.id() works with zero JavaScript shipped — the id is minted during SSR, no hydration required.</p>
<Field />
<h3>Two hydrated islands, two ids</h3>
<p class="hint">Each instance gets its own id, so the label/for pairs never collide. Hydration reuses the server-generated value — the id you see was minted during SSR.</p>
<LabeledField mochi:hydrate />
<LabeledField mochi:hydrate />
<h3>Server island</h3>
<p class="hint">
Deferred islands render in a separate request; Mochi namespaces their ids with the island's own id (via render's idPrefix) so they cannot collide with ids already on the page.
</p>
<ServerStamp mochi:defer />
<h3>Server island that also hydrates</h3>
<p class="hint">
With <code>mochi:defer mochi:hydrate</code> the namespaced id is read back from the SSR markers when the fragment hydrates — click the button and the id stays exactly the same, proving
the value survived from the deferred render into the hydrated client.
</p>
<ServerHydratedStamp mochi:defer mochi:hydrate />
<h3>Two client-only islands, two ids</h3>
<p class="hint">
Client-only islands are never server-rendered, so each <code>$props.id()</code> is minted in the browser at mount. Svelte draws these from a global counter — unique even across
separate
<code>mount()</code> calls — so two independently-mounted islands still get distinct ids (e.g. <code>c1</code> and <code>c2</code>) without any SSR pass to keep them apart.
</p>
<ClientStamp mochi:clientOnly />
<ClientStamp mochi:clientOnly />
Styles
<style>
h3 {
margin: 1.5rem 0 0.25rem;
font-size: 1rem;
font-weight: 600;
}
.hint {
color: var(--text-muted);
font-size: 0.85em;
margin: 0 0 0.5rem;
}
</style>