Islands: Triggers

Hydration Triggers

Control exactly when your islands become interactive.

Available Triggers

Luna provides four hydration triggers:

TriggerWhenUse Case
loadImmediately on page loadCritical UI, above-fold content
idleWhen browser is idleNon-critical features
visibleWhen element enters viewportBelow-fold content
mediaWhen media query matchesResponsive features

Load Trigger

Hydrate immediately when the page loads:

<div luna:id="search" luna:url="/static/search.js" luna:client-trigger="load">
  <!-- Server-rendered content -->
</div>

Use for:

  • Header search boxes

  • Navigation menus

  • Critical interactive elements

  • Above-the-fold content

Idle Trigger

Hydrate when the browser is idle (using requestIdleCallback):

<div luna:id="analytics" luna:url="/static/analytics.js" luna:client-trigger="idle">
  <!-- Server-rendered content -->
</div>

Use for:

  • Analytics tracking

  • Non-essential widgets

  • Background features

  • Low-priority interactions

Visible Trigger

Hydrate when the element scrolls into view (using IntersectionObserver):

<div luna:id="comments" luna:url="/static/comments.js" luna:client-trigger="visible">
  <!-- Server-rendered content -->
</div>

Use for:

  • Comments sections

  • Image galleries

  • Infinite scroll

  • Footer widgets

  • Any below-fold content

Media Trigger

Hydrate when a media query matches:

<div luna:id="sidebar" luna:url="/static/sidebar.js" luna:client-trigger="media:(min-width: 768px)">
  <!-- Server-rendered content -->
</div>

Use for:

  • Desktop-only features

  • Mobile-specific components

  • Responsive interactions

  • Orientation-dependent UI

Media Query Examples

<!-- Desktop only (768px+) -->
<div luna:client-trigger="media:(min-width: 768px)">...</div>

<!-- Mobile only (under 768px) -->
<div luna:client-trigger="media:(max-width: 767px)">...</div>

<!-- Dark mode preference -->
<div luna:client-trigger="media:(prefers-color-scheme: dark)">...</div>

<!-- Reduced motion preference -->
<div luna:client-trigger="media:(prefers-reduced-motion: no-preference)">...</div>

<!-- Landscape orientation -->
<div luna:client-trigger="media:(orientation: landscape)">...</div>

Choosing the Right Trigger

Decision Flow

Is it above the fold?
โ”œโ”€โ”€ Yes โ†’ Is it critical for initial interaction?
โ”‚         โ”œโ”€โ”€ Yes โ†’ load
โ”‚         โ””โ”€โ”€ No โ†’ idle
โ””โ”€โ”€ No โ†’ Will users always scroll to it?
          โ”œโ”€โ”€ Yes โ†’ visible
          โ””โ”€โ”€ No โ†’ Is it device-specific?
                    โ”œโ”€โ”€ Yes โ†’ media
                    โ””โ”€โ”€ No โ†’ visible or idle

Performance Impact

TriggerInitial LoadLCP ImpactTTI Impact
loadHeavyNoneDelayed
idleLightNoneMinimal
visibleNoneNoneNone
mediaConditionalNoneMinimal

Combining Strategies

A typical page might use all triggers:

<div>
  <!-- Immediate - critical for UX -->
  <div luna:id="search" luna:client-trigger="load">...</div>

  <!-- Idle - nice to have but not urgent -->
  <div luna:id="theme-toggle" luna:client-trigger="idle">...</div>

  <!-- Static article content - no JS -->
  <article>...</article>

  <!-- Visible - only load when user scrolls down -->
  <div luna:id="comments" luna:client-trigger="visible">...</div>

  <!-- Media - only on desktop -->
  <div luna:id="sidebar" luna:client-trigger="media:(min-width: 1024px)">...</div>
</div>

For server-side rendering with MoonBit, see the MoonBit Tutorial.

Manual Trigger (None)

For programmatic control, use none:

<div luna:id="modal" luna:url="/static/modal.js" luna:client-trigger="none">
  <!-- Server-rendered modal content -->
</div>

Then trigger from JavaScript:

// Hydrate manually when needed
window.__LUNA_HYDRATE__?.("modal");

Use for:

  • Modals opened by user action

  • Lazy-loaded features

  • On-demand functionality

Monitoring Hydration

Track hydration timing:

// In your island component
hydrate("myComponent", (props) => {
  console.log("Hydrated at:", performance.now());
  return <MyComponent {...props} />;
});

Try It

Assign triggers to these components:

  1. Site-wide search box

  2. Cookie consent banner

  3. Image lightbox

  4. Live chat widget

  5. Mobile hamburger menu

Suggested Answers
  1. Search box โ†’ load (critical, above fold)

  2. Cookie consent โ†’ idle (not critical, can wait)

  3. Image lightbox โ†’ visible or none (only when viewing images)

  4. Live chat โ†’ idle (background feature)

  5. Mobile menu โ†’ media:(max-width: 767px) (mobile only)

Next

Learn about Server-to-Client State โ†’