Islands
Islands are the core pattern in Weblisk. HTML renders first as static content — then small JavaScript modules "enhance" specific regions with interactivity. Only the interactive parts ship JavaScript.
Basic Island
HTML:
<div data-island="counter"> <button>Count: 0</button> </div> <script type="module" src="/js/islands/counter.js"></script>
JavaScript:
import { signal, effect, enhance } from 'weblisk'; enhance('[data-island="counter"]', (el, { $ }) => { const [count, setCount] = signal(0); const btn = $('button'); btn.addEventListener('click', () => setCount(count() + 1)); effect(() => { btn.textContent = `Count: ${count()}`; }); });
The enhance() Function
enhance(target, setup)
| Parameter | Type | Description |
|-----------|------|-------------|
| target | string \| Element | CSS selector or DOM element to enhance |
| setup | (el, ctx) => cleanup \| void | Setup function with element and context |
Context Object
The second argument to your setup function provides helpers:
| Property | Description |
|----------|-------------|
| $ | querySelector scoped to the island element |
| $$ | querySelectorAll scoped to the island element |
| effect | Scoped effect() — auto-cleaned when island is destroyed |
Cleanup
Return a function from setup to clean up when the island is removed:
enhance('[data-island="timer"]', (el) => { const id = setInterval(() => { /* ... */ }, 1000); return () => clearInterval(id); // Called on cleanup });
Lazy Islands
For islands below the fold, load them only when they become visible:
import { lazyEnhance } from 'weblisk/core/lazy-island.js'; lazyEnhance('[data-island="chart"]', async () => { const { setupChart } = await import('./chart-setup.js'); return setupChart; });
Lazy Enhance All
Enhance multiple islands matching the same selector:
import { lazyEnhanceAll } from 'weblisk/core/lazy-island.js'; lazyEnhanceAll('.card[data-island]', async (el) => { const type = el.dataset.island; const mod = await import(`./cards/${type}.js`); mod.setup(el); });
Error Boundaries
Wrap islands in error boundaries to gracefully handle failures:
import { boundary } from 'weblisk/core/error.js'; boundary('[data-island="chart"]', setupChart, { fallback: '<p>Chart failed to load. <button data-retry>Retry</button></p>', onError: (err) => console.error('Chart error:', err), retry: true, });
If the setup function throws, the fallback HTML is shown. With retry: true, clicking
[data-retry] re-runs setup.
Multiple Islands on a Page
Each island is independent — they don't share state unless you explicitly connect them:
<div data-island="header">...</div> <div data-island="search">...</div> <div data-island="results">...</div>
To share state between islands, use a shared store:
// shared.js import { store } from 'weblisk'; export const appState = store({ query: '', results: [] });
// search.js import { appState } from './shared.js'; enhance('[data-island="search"]', (el, { $ }) => { $('input').addEventListener('input', (e) => { appState.query = e.target.value; }); });
// results.js import { appState } from './shared.js'; enhance('[data-island="results"]', (el, { effect }) => { effect(() => { el.innerHTML = appState.results.map(r => `<li>${r}</li>`).join(''); }); });
Creating Islands
Create a new file in js/islands/ with an export default function:
// js/islands/my-widget.js export default function myWidget(el) { // el is the element with [data-island] }
Then reference it in your HTML with data-island="/js/islands/my-widget.js?v=0".