Skip to main content

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:

html
<div data-island="counter">
  <button>Count: 0</button>
</div>
<script type="module" src="/js/islands/counter.js"></script>

JavaScript:

js
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

js
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:

js
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:

js
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:

js
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:

js
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:

html
<div data-island="header">...</div>
<div data-island="search">...</div>
<div data-island="results">...</div>

To share state between islands, use a shared store:

js
// shared.js
import { store } from 'weblisk';
export const appState = store({ query: '', results: [] });
js
// search.js
import { appState } from './shared.js';
enhance('[data-island="search"]', (el, { $ }) => {
  $('input').addEventListener('input', (e) => {
    appState.query = e.target.value;
  });
});
js
// 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
// 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".