"use client" island
Components in src/components/ or src/islands/ with a top-level "use client"
directive are bundled for the browser. The component returns an
HTML string; the mount runtime stuffs it into the host and runs
any embedded <script>. This is bext's React-island
analogue — coarser-grained than signals (re-render replaces the
whole inner DOM), but ergonomic for self-contained widgets.
Output
count: 7
src/components/ClientCounter.tsx
"use client";
// Component returns a string. The mount runtime stuffs the HTML
// into the host element AND runs any inline <script> blocks the
// component embeds — that's where interactivity gets wired up.
export default function ClientCounter(props: { initial?: number }): string {
const initial = props.initial ?? 0;
const id = "cc-" + Math.random().toString(36).slice(2, 8);
return `<div id='${id}'>
<p>count: <strong data-c>${initial}</strong></p>
<button data-act='inc'>+1</button>
<button data-act='dec'>−1</button>
</div>
<script>(function(){
var r = document.getElementById('${id}');
var n = ${initial};
var c = r.querySelector('[data-c]');
r.addEventListener('click', function(e) {
var t = e.target.closest('[data-act]');
if (!t) return;
n += t.dataset.act === 'inc' ? 1 : -1;
c.textContent = String(n);
});
})();</script>`;
}page.tsx
import { island } from "@bext-stack/framework/island";
import { Raw } from "@bext-stack/framework/jsx-runtime";
import ClientCounter from "../../../components/ClientCounter";
export default function Page() {
return (
<Raw html={island("ClientCounter", { initial: 7, nonce: 42 }, ClientCounter({ initial: 7, nonce: 42 }))} />
);
}