A new performance-focused way to add interactive client-side components to your web site.
Or, more technically: a framework independent partial hydration islands architecture implementation.
- View the demos
- Check out the initial screencast on Eleventy’s YouTube channel and the follow up screencast with more framework examples.
- Learn more about Islands Architecture
- Easy to add to existing components
- Zero dependencies
- Not tightly coupled to a server framework or site generator tool.
- Small footprint (1.79 kB compressed)
- Server-rendered (SSR) component examples available for SSR-friendly frameworks (Lit, Svelte, Vue, Preact are provided)
- Web Components
- Web Components using Declarative Shadow DOM _(New in v3.0.0)_
- Svelte (and SSR)
- Vue (and petite-vue) (and SSR)
- Preact (and SSR)
- Lit (and SSR)
- Alpine.js
- Embedded in Markdown
defer-hydrationComponent Attribute Support _(New in v3)_ (Read more aboutdefer-hydrationat zachleat.com)- Using import maps to simplify import URLs.
- Renaming
tag name or theon:attribute prefix - _Experimental:_ Image Loading
- Stress test of 10000 islands
- Complex nested
is-landtest
Installation
Available on npm at @11ty/is-land.
npm install @11ty/is-land
<script type="module" src="/is-land.js"></script>
Add is-land.js to your primary bundle.
It can be deferred and/or loaded asynchronously. When using with web components it must be loaded before any other custom elements (via customElements.define) on the page. Choose your style:
Usage
<is-land>This is an island.</is-land>
Add any number of loading conditions to this tag to control how and when the island is initialized. You can mix and match. _All_ conditions be satisfied to initialize.
on:visibleon:load(new in v5)on:idleon:interaction(defaults totouchstart,click)
on:interaction="mouseenter,focusin"
on:media
on:media="(min-width: 64em)"
* Reduced motion:
* When user prefers reduced motion on:media="(prefers-reduced-motion)"
* When user has no preference on motion on:media="(prefers-reduced-motion: no-preference)"
- Save Data (read about Save Data on MDN)
on:save-data
* When Save Data is inactive on:save-data="false"
<is-land on:visible on:idle>
<!-- your HTML here -->
<is-land on:media="(min-width: 64em)">
<!-- Islands can be nested -->
<!-- Islands inherit all of their parents’ loading conditions -->
</is-land>
</is-land>
Programmatic API
Each loading condition is also available as a promise-returning method on Island.
await Island.onidle();
await Island.onvisible(element);
await Island.onload();
await Island.oninteraction(element); // or Island.oninteraction(element, "mouseenter,focusin")
await Island.onmedia("(min-width: 64em)");
await Island.onsavedata(); // or Island.onsavedata("false")
Controlling Fallback Content
Pre-JS
<is-land on:visible on:idle>
<vanilla-web-component>
Put your pre-JS fallback content in your web component.
</vanilla-web-component>
</is-land>
Post-JS with
Place any post-JS content inside of one or more elements anywhere in the . These will be swapped with their template content. You can nest an in there if you want!
<is-land on:visible on:idle>
<template data-island>
<vanilla-web-component>
This component is post-JS.
</vanilla-web-component>
</template>
</is-land>
- Use
data-island="replace"to replace the contents of thewith the template. - Use
data-island="once"to run a template’s contents once per page (keyed from template contents). _(New in v2.0.1)_
Run your own custom JavaScript, load your own CSS
Embed a script inside the template to run custom JS when the island’s loading conditions have been satisfied!
<is-land on:visible>
<template data-island>
<!-- CSS -->
<style>/ My custom CSS /</style>
<link rel="stylesheet" href="my-css-file.css">
<!-- JS -->
<script type="module">console.log("Hydrating!");</script>
<script type="module" src="my-js-file.js"></script>
</template>
</is-land>
Import a module
Use import to load a JavaScript module when the island’s loading conditions have been satisfied. Add import-mount to also call the module’s default export with the element (after fallback content is restored and before ready is set, awaiting any returned promise).
<is-land on:visible import="my-component.js" import-mount></is-land>
// my-component.js
export default async function(island) {
// hydrate island here
}
You can also use the ready attribute for styling, added to the when the island has been hydrated.
<style>
is-land[ready] {
background-color: lightgreen;
}
</style>
View Transitions
Use Island.setSwapBehavior to wrap the template and fallback content swap in a View Transition. Framework init types run after the swap and are not included.
Island.setSwapBehavior((swap, island) => {
if(document.startViewTransition) {
document.startViewTransition(swap);
} else {
swap();
}
});
Your callback must call swap, otherwise the island never hydrates. The island argument is the element being hydrated.
Framework Component Support
type: initialize a framework initialization type, registered by you. Examples included for:alpine,petite-vue,vue,vue-ssr,preact,preact-ssr,svelte, orsvelte-ssr.
Petite Vue
- Examples
- Small library (~9K)
- Rendering modes: Client
- Progressive-enhancement friendly (control fallback content)
<script type="module">
// Define once for any number of Petite Vue islands.
Island.addInitType("petite-vue", async (target) => {
const { createApp } = await import("https://unpkg.com/[email protected]/dist/petite-vue.es.js");
createApp().mount(target);
});
</script>
<is-land on:visible type="petite-vue" v-scope="{ name: 'Vue' }">
Hello from <span v-html="name">HTML</span>
</is-land>
Vue
- Examples
- Larger library (~73 kB)
- Rendering modes: Client (shown), Server, Server + Client (Hydration)
<script type="module">
// Define once for any number of Vue islands.
Island.addInitType("vue", async (target) => {
const { createApp } = await import("https://unpkg.com/[email protected]/dist/vue.esm-browser.js");
createApp({
data: () => (target.dataset), // use <is-land data-> attributes as component data
}).mount(target);
});
</script>
<is-land on:visible type="vue" data-name="Vue">
Hello from <span v-text="name"></span>
</is-land>
Svelte
- Examples (using Import Maps)
- Medium-sized library
- Rendering modes: Client, Server, Server + Client (Hydration)
- Requires a compiler for client mode (uncommon)
<script type="module">
// Define once for any number of Svelte islands.
Island.addInitType("svelte", async (target) => {
// requires an Import map and svelte is lazy loaded when island is ready
const { mount } = await import("svelte");
const component = await import(target.getAttribute("import"));
mount(component.default, {
target: target,
props: {},
});
});
</script>
<!-- This example uses an Eleventy svelte Universal Filter (see SveltePlugin.cjs) -->
{% assign component = "./lib/svelte/my-component-js-only.svelte" | svelte %}
<is-land on:visible type="svelte" import="{{ component.clientJsUrl }}"></is-land>
Show sample Import Map
<!-- importmap from https://generator.jspm.io/ -->
<script type="importmap">
{
"imports": {
"svelte": "https://unpkg.com/[email protected]/src/index-client.js",
"svelte/internal/client": "https://unpkg.com/[email protected]/src/internal/client/index.js",
"svelte/internal/flags/legacy": "https://unpkg.com/[email protected]/src/internal/flags/legacy.js"
},
"scopes": {
"https://unpkg.com/": {
"clsx": "https://unpkg.com/[email protected]/dist/clsx.mjs",
"esm-env": "https://unpkg.com/[email protected]/index.js",
"esm-env/browser": "https://unpkg.com/[email protected]/true.js",
"esm-env/development": "https://unpkg.com/[email protected]/false.js",
"esm-env/node": "https://unpkg.com/[email protected]/false.js"
}
}
}
</script>
Preact
- Examples
- Small library (~9 kB)
- Rendering modes: Client (shown), Server, Server + Client (Hydration)
- No compiler needed when using
htmrather than JSX.
<script type="module">
// Define once for any number of Preact islands.
Island.addInitType("preact", async (target) => {
const component = await import(target.getAttribute("import"));
component.default(target);
});
</script>
<is-land on:visible type="preact" import="preact-component.js"></is-land>
Example component code for
preact-component.js:
import { html, render } from 'https://unpkg.com/htm/preact/index.mjs?module'
function App (props) {
return html<p><strong>Hello ${props.name}!</strong></p>;
}
export default function(el) {
render(html<${App} name="from Preact" />, el);
}
Lit
- Examples (using Import Maps)
- Small library (~10 kB)
- Rendering modes: Client, Server, Server + Client (Hydration)
<is-land on:visible import="lit-component.js">
<lit-component name="Post-JS">Pre-JS Content</lit-web-component>
</is-land>
Show sample Import Map
<!-- importmap from https://generator.jspm.io/ -->
<script type="importmap">
{
"imports": {
"lit": "https://unpkg.com/[email protected]/index.js"
},
"scopes": {
"https://unpkg.com/": {
"@lit/reactive-element": "https://unpkg.com/@lit/[email protected]/reactive-element.js",
"lit-element/lit-element.js": "https://unpkg.com/[email protected]/lit-element.js",
"lit-html": "https://unpkg.com/[email protected]/lit-html.js",
"lit-html/is-server.js": "https://unpkg.com/[email protected]/is-server.js"
}
}
}
</script>
Example component code
lit-component.js:
import {html, css, LitElement} from "lit";
customElements.define('lit-component', class extends LitElement {
static properties = {
name: {type: String},
};
render() {
return html<p>Hello, ${this.name || "Stranger"}!</p>;
}
});
Alpine.js
- Examples
- Smaller library (~20 kB)
- Rendering modes: Client
- Progressive-enhancement friendly (control fallback content)
<script type="module">
// Define once for any number of Alpine islands.
Island.addInitType("alpine", async (target) => {
await import("https://unpkg.com/[email protected]/dist/cdn.min.js");
});
// Workaround for Alpine global mount
Island.addFallback("[x-data]", (node) => {
if(node.hasAttribute("x-ignore")) {
return;
}
node.setAttribute("x-ignore", "");
return () => {
node.removeAttribute("x-ignore");
if(Alpine) {
Alpine.nextTick(() => Alpine.initTree(node));
}
};
});
</script>
<is-land on:visible type="alpine">
<div x-data="{ name: 'Alpine.js' }">
Hello from <span x-text="name">HTML</span>!
</div>
</is-land>
Solid.js
- Examples (using Import Maps)
- Medium library (~40 kB)
- Rendering modes: Client