Skip to content

Documentation

Use Astro's Client Directives without a Framework
Terminal window
...

Bring Astro’s client directives to vanilla JavaScript! 🎉

Script Islands fakes a framework renderer so you can use client:load, client:visible, client:idle and friends with plain JavaScript. Each <ScriptIsland> extracts its <script> into a separate file that only loads when triggered.

Why?

  • Use Astro’s powerful client directives without any framework overhead
  • Defer scripts until they’re actually needed
  • Reduce initial page load size and improve performance

Install Script Islands in your Astro project:

Terminal window
npm install astro-script-islands

Add the integration to your astro.config.* file:

import { defineConfig } from 'astro/config';
import ScriptIslands from 'astro-script-islands/integration';
export default defineConfig({
integrations: [
ScriptIslands()
],
});
  1. Import the ScriptIsland component from astro-script-islands/component
  2. Wrap your JavaScript code inside <ScriptIsland>
  3. Use any of Astro’s client:* directives to control when the script loads
ClientIdle.astro
---
import ScriptIsland from "astro-script-islands/component"
import { Badge } from "@astrojs/starlight/components"
---
<p id="client-idle">
<span>Script loaded:</span>
<Badge id="idle-false" text="false" variant="danger" />
<Badge id="idle-true" text="true" variant="success" class="hidden" />
</p>
<ScriptIsland client:idle>
<script>
document.querySelector("#client-idle #idle-false").classList.add("hidden")
document.querySelector("#client-idle #idle-true").classList.remove("hidden")
document.querySelector("#client-idle span.success").textContent =
new Date().toLocaleTimeString()
// demo:start
window.dispatchEvent(new CustomEvent("island-hydrated"))
// demo:end
</script>
</ScriptIsland>
Preview

Script loaded: false

The script inside <ScriptIsland> is extracted into a separate file and only loaded when the client:idle condition is met (after the page finishes its initial load and the browser is idle).

You can slot HTML elements or Astro components inside <ScriptIsland>. This is useful when using client:visible, which needs a visible element to observe.

ClientVisible.astro
---
import ScriptIsland from "astro-script-islands/component"
---
<ScriptIsland client:visible>
<p>You finally waited for <strong id="countup">0</strong> seconds.</p>
<script>
import { CountUp } from "countup.js"
let demo = new CountUp("countup", 3000)
demo.start()
// demo:start
window.dispatchEvent(new CustomEvent("island-hydrated"))
// demo:end
</script>
</ScriptIsland>
Preview

You finally waited for 0 seconds.

Script Islands work seamlessly with Astro’s custom client directives. This example demonstrates a custom client:tracking directive that activates tracking code across multiple components when a global event is dispatched.

ClientTracking/Hover.astro
---
import ScriptIsland from "astro-script-islands/component"
---
<button id="hover-button" disabled>Hover me</button>
<ScriptIsland client:tracking>
<script>
import { track } from "./track.ts"
const hoverButton = document.getElementById("hover-button")
hoverButton.disabled = false
hoverButton?.addEventListener("mouseover", () => {
track()
})
// demo:start
window.dispatchEvent(new CustomEvent("island-hydrated"))
// demo:end
</script>
</ScriptIsland>
ClientTracking/Click.astro
---
import ScriptIsland from "astro-script-islands/component"
---
<button id="click-button" disabled>Click me</button>
<ScriptIsland client:tracking>
<script>
import { track } from "./track.ts"
const clickButton = document.getElementById("click-button")
clickButton.disabled = false
clickButton?.addEventListener("click", () => {
track()
})
// demo:start
window.dispatchEvent(new CustomEvent("island-hydrated"))
// demo:end
</script>
</ScriptIsland>
Preview
Tracking Events: Disabled