Menu
Public repository / fee5b6c2cde89fa6bf8dc51068af7336b56a88b8
AkurAI-Framework / site/frontend/islands.html
Latest change d3227deaa0b875c75ab4eff2f4a9010ce80ce9a1 - feat(frontend): SSR islands hydration runtime (islands.js) + demo + docs by Ólafur Búi Ólafsson
{% include "header" %}
<main class="container">
<header class="page-head">
<span class="eyebrow">Live demo</span>
<h1>Islands & hydration</h1>
<p class="lede">The server renders this whole page. Only the regions marked
<code>data-island</code> become interactive — <code>islands.js</code> hydrates them
in place, reusing the server-rendered nodes, with no virtual DOM and no flicker. Each
island starts at the exact state the server printed. <a href="/docs/islands">Read how it
works →</a></p>
</header>
<!-- Static SSR region: NOT an island. Never touched by JS. -->
<section class="card">
<header class="rt-head">
<h3>Static (server-only)</h3>
<span class="status">no JS</span>
</header>
<p class="muted small">This region has no <code>data-island</code>. It is pure
server-rendered HTML and the hydration runtime leaves it completely alone. View source:
what the server sent is what you see.</p>
</section>
<section class="split rt-grid">
<!-- Counter island: initial state from a <script type="application/json"> blob -->
<article class="card" data-island="counter">
<script type="application/json" data-island-state>{"count": 7, "step": 1}</script>
<header class="rt-head">
<h3>Counter island</h3>
<span class="status ok">data-island="counter"</span>
</header>
<p class="muted small">The server printed <code>count: 7</code> in a JSON blob inside
the island. After hydration the number is live — but it never flashes from 0, because
the signal is seeded from that printed value.</p>
<div class="rt-num" id="counter-value">7</div>
<div class="btn-row">
<button class="btn btn-secondary" data-counter-dec>−1</button>
<button class="btn btn-primary" data-counter-inc>+1</button>
<button class="btn btn-ghost" data-counter-reset>Reset</button>
</div>
</article>
<!-- Toggle island: initial state from a data-state attribute -->
<article class="card" data-island="toggle" data-state='{"open": true}'>
<header class="rt-head">
<h3>Toggle island</h3>
<span class="status ok">data-island="toggle"</span>
</header>
<p class="muted small">This island reads its initial state from a
<code>data-state</code> attribute. The server said <code>open: true</code>, so the
panel is already visible on first paint and stays visible through hydration.</p>
<label class="switch">
<input type="checkbox" class="switch-input" role="switch"
data-toggle-input checked />
<span class="switch-text">Reveal the panel</span>
</label>
<div class="alert alert-info" data-toggle-panel style="margin-top:1rem">
<strong>Visible.</strong> <span>Shown only while the island's signal is true.</span>
</div>
</article>
</section>
<section class="container cta">
<h2>Interactive where it matters. Static everywhere else.</h2>
<div class="btn-row center">
<a class="btn btn-secondary" href="/docs/islands">Islands docs</a>
<a class="btn btn-secondary" href="/reactivity">Full-page reactivity</a>
</div>
</section>
</main>
<!-- Register the islands. Plain native ESM — no build step. -->
<script type="module">
import { registerIsland, signal, effect } from "/islands.js";
// Counter: seed a signal from the server-printed state, wire buttons + text.
registerIsland("counter", (el, initial) => {
const count = signal(initial.count ?? 0);
const step = initial.step ?? 1;
const out = el.querySelector("#counter-value");
effect(() => { out.textContent = String(count()); });
el.querySelector("[data-counter-inc]").addEventListener("click", () => count(count() + step));
el.querySelector("[data-counter-dec]").addEventListener("click", () => count(count() - step));
el.querySelector("[data-counter-reset]").addEventListener("click", () => count(0));
});
// Toggle: boolean signal seeded from data-state, drives panel visibility.
registerIsland("toggle", (el, initial) => {
const open = signal(initial.open ?? false);
const input = el.querySelector("[data-toggle-input]");
const panel = el.querySelector("[data-toggle-panel]");
input.checked = !!open();
effect(() => { panel.style.display = open() ? "" : "none"; });
input.addEventListener("change", () => open(input.checked));
});
</script>
<script type="module" src="/islands.js"></script>
{% include "footer" %}