Skip to content

Regions

Snippets

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

On this page

A snippet is a function. Thus it cannot cross an island boundary directly: the props of an island are serialized, and a function does not serialize. ogygia does this for you: each snippet given to an island crosses as a region. That is the snippet shape of the same primitive behind <Component/> regions. You almost never call a function for this. Children or a {#snippet} passed into an island operate correctly, also when a plain wrapper forwards them.

When to use

You do not select this feature. It is what makes these operate at all: <Tabs>{@render children()}</Tabs>, a forwarded {#snippet header()}, and a wrapper component that passes content into an island. Use region.snippet() only when you make a snippet by hand from raw HTML.

The two shapes of a crossing

A snippet crosses in one of two ways. ogygia selects the way, based on the need to wake:

  • Static: plain content (prose, a code block, markup with no interactivity) renders one time on the server and freezes to HTML. The client adopts that HTML in place. Nothing runs again, and nothing ships. This is the common case for children.
  • Live: a named {#snippet} whose body must wake (it renders an island, or it is interactive) compiles to its own entry. It wakes on the far side. It renders inline on the server and hydrates on the client. Thus it is interactive where it lands, also after a boundary forwards it. Parameters are supported: the arguments of each call ride across.
<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 static content continue to operate. They render as full regions and wake on their own, although the content around them is frozen.

A live snippet is part of the island that renders it:

  • An island inside it renders inline and hydrates with that island, the same as one placed in the island’s own template.
  • A server island (render: 'deferred') inside it stays deferred. Its fallback ships with the page, and its HTML arrives on its schedule. Its code never ships to the browser.
  • It can render the host’s template-root snippets, and read the host’s <script module> imports and values.
  • A lake (wake: 'none') inside it stays frozen. An island inside the lake wakes on its own schedule.
  • It reads the page’s contexts, on the server and in the browser.
  • The island sets data-hydrated and fires ogygia:hydrated after its live snippets have hydrated too.

Forwarding through a wrapper

This matters because of composition. A plain (non-island) component can forward its children into an island. The crossing occurs at the boundary. A wrapper around an island is only Svelte. There is 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, use region.snippet(). It is the sibling of region() for components. It mirrors createRawSnippet from Svelte. But the result is a region snippet: you can render it in the same graph with {@render}, and it can cross a boundary.

Lift an applicable snippet without parameters, 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 make 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 one root element. That is the createRawSnippet rule. captures must be serializable, the same as island props. setup runs on the client after mount. When the snippet crosses a boundary, setup is dropped. A frozen snippet has nothing to start.