Skip to content

Regions

Snippets

Snippets cross an island boundary as regions. Static content freezes to server HTML; a snippet that needs to wake hydrates on the far side. region.snippet() is the manual form.

On this page

A snippet is a function, so it cannot cross an island boundary directly: the props an island ships are serialized, and a function does not serialize. Ogygia handles this for you: any snippet handed to an island crosses as a region, the snippet shape of the same primitive behind <Component/> regions. You almost never call anything; passing children or a {#snippet} into an island just works, including when a plain wrapper forwards them along.

When to use

You do not reach for this on purpose. It is what makes <Tabs>{@render children()}</Tabs>, a forwarded {#snippet header()}, or a wrapper component that passes content into an island work at all. Reach for region.snippet() only when you are building a snippet by hand from raw HTML.

The two shapes of a crossing

A snippet crosses in one of two ways, chosen for you by whether it needs to wake:

  • Static: plain content (prose, a code block, markup with no interactivity of its own) renders once on the server and freezes to HTML. The client adopts that HTML in place; nothing re-runs, nothing ships. This is the common case for children.
  • Live: a named {#snippet} whose body must wake (it renders an island, or is interactive) compiles to its own entry and wakes on the far side. It renders inline on the server and hydrates on the client, so it is interactive wherever it lands, even after being forwarded through a boundary. Parameters are supported: the arguments you call the snippet with ride across to each render.
<script>
  import Tabs from './Tabs.svelte' with { wake: 'load' };
</script>

<Tabs>
  <p>Frozen prose — crosses static, ships no JS.</p>

  {#snippet toolbar()}
    <LiveButton />   <!-- crosses live: interactive inside the island -->
  {/snippet}
</Tabs>

Nested islands inside crossed content keep working: they render as full regions and wake on their own, even though the content around them is frozen.

Forwarding through a wrapper

The reason this matters is composition. A plain (non-island) component can forward its children straight into an island, and the crossing still happens at the boundary. Wrapping an island is just Svelte, no special API:

<!-- a plain wrapper -->
<script>
  import Inner from './Inner.svelte' with { wake: 'load' };
  let { children } = $props();
</script>

<Inner>{@render children()}</Inner>

Building one by hand: region.snippet()

When you need a snippet made from raw HTML (the sibling of region() for components), use region.snippet(). It mirrors Svelte’s createRawSnippet, but the result is a region snippet: renderable in the same graph with {@render}, and able to cross a boundary.

Lift an existing parameterless snippet to freeze it as static content:

{#snippet badge()}<span class="badge">stable</span>{/snippet}

<script>
  import { region } from 'ogygia';
  const frozen = region.snippet(badge);
</script>

Or build one from raw HTML with the same { render, setup?, captures? } contract as createRawSnippet:

import { region } from 'ogygia';

const stamp = region.snippet({
  render: (label) => `<span class="stamp">${label}</span>`,
  setup: (el) => {
    // client-only wiring; dropped when the snippet is reduced across a wire
  },
  captures: ['shipped']   // serializable inputs, same law as island props
});

render must return a single root element (the createRawSnippet rule); captures are serializable, just like island props; setup runs on the client after mount and is dropped when the snippet crosses a boundary (a frozen snippet has nothing to boot).