Add interactivity without surrendering the page.
Ask an agent to “make this module interactive” and the easy answer is to turn the whole thing into client-side JavaScript. CMS-React gives you a sharper tool: islands — the page renders server-side first, and only the marked subtree hydrates. This prompt makes your agent propose the smallest island — boundary, props, hydration timing — before it writes any code.
Copy it, run it on a real module
Take one module that needs a single interactive element — a toggle, tabs, a live filter. The prompt opens by surveying how your theme handles interactivity today, asks what you're adding, and won't write code until you've approved the boundary.
I want to add one interactive element to a HubSpot CMS-React module without
turning the module into client-side JavaScript. On this platform, modules
render on the server and ship no JS by default; only a component imported
with the ?island suffix and rendered through the Island component hydrates on
the client and may use hooks, state, or event handlers. Keeping the island
small is a blast-radius decision, not just a performance one — that's the
constraint you build inside.
PHASE 0 — Orient, then ask. Read the theme and tell me which modules exist,
which already use islands, and how interactivity is currently handled. Then
ask me:
- Which module am I changing, and what single piece needs to be
interactive (a counter, tabs, a filter, a form with live validation)?
- What should it feel like — instant on load, or fine to hydrate lazily?
- Is this module used on one page or many? (If I don't know, list what
you'd check.)
Then STOP and wait for my answers.
PHASE 1 — Propose the smallest island. Before writing any code, show me:
- THE BOUNDARY — exactly which subtree becomes the island, and what stays
a server-rendered component. If your boundary wraps more than the
interactive piece, shrink it and explain why the smaller cut works.
- THE PROPS — the exact serializable props the island needs. No functions
across the boundary — and only the values required, never a whole data
blob: island props are serialized into the page HTML and visible in page
source.
- THE HYDRATION TIMING — whether this island should hydrate on load, when
the browser is idle, or only when scrolled into view, and why.
- WHAT DOESN'T CROSS — anything (handlers, secrets, big objects) that must
stay on the server side of the seam.
Wait for my go-ahead on the proposal.
PHASE 2 — Build it. Write the island component and the module changes,
including the import with its ?island suffix and the ts-ignore line above it
that the untyped island import needs. Keep everything outside the island
exactly as it was — same server rendering, same fields, same markup.
PHASE 3 — Prove the containment. Show me how to verify that the page still
renders server-side first, that only the island's JavaScript loads, and that
nothing outside the boundary changed. If any check can only be done in the
account or a live preview, say so explicitly — list it as mine to run, and
tell me exactly what to look for.
Start with Phase 0 now.Small islands are a blast-radius decision
The boundary comes first
The agent must name the island's exact subtree — and defend why nothing smaller works — before writing a line. That single constraint kills the default failure: wrapping the whole module in client-side JavaScript to make one button clickable.
The seam stays clean
Island props must be serializable — no functions — and they're visible in the page source, so the prompt forces a data-only diet across the boundary. The seam the platform enforces becomes a seam the agent designs deliberately.
Containment gets proved
The build ends with verification, honestly split: what the agent can show from the code, and what only you can check in a live preview — that the page still renders server-first and only the island hydrates.
The proposal you approve before code exists
The shape of a Phase 1 proposal — the boundary drawn and defended, the props on a data-only diet, the hydration timing chosen for the page, and the verification split between what the agent proves and what you check live.
For "make the pricing table's billing toggle interactive," Phase 1 comes back:
THE BOUNDARY
island: <BillingToggle /> — the switch + the two price labels
server: everything else — table, tiers, CTAs, footnotes
(rejected: wrapping the whole PricingTable — 40 lines interactive,
400 shipped to the client for no reason)
THE PROPS — serializable only, visible in page source
monthlyPrice: 24 annualPrice: 240 currency: "USD"
(NOT the whole pricing object — the island needs three values)
HYDRATION "visible" — below the fold on both pages that mount this module
DOESN'T CROSS the analytics handler (stays server-config), tier metadata
VERIFY agent: island compiles, module markup unchanged outside the seam
you: view page source — prices render server-side; toggle JS
loads only when scrolled into viewHow islands actually work in CMS-React
The prompt's constraints aren't style preferences — each one traces to how the platform renders. The specifics, sourced:
The page is fully rendered server-side first (HTML, no interactivity); the island's JS is then loaded and hydrated on the client. Only islands can use hooks, state, or event handlers — a plain server component that calls useState will error.
To make one: import Island from @hubspot/cms-components, import your component with a ?island suffix on the path, and render <Island module={Component} … />. Props must be serializable — no functions — and they're serialized into the page HTML (visible in page source), so pass only what the island needs. hydrateOn tunes when it hydrates: "load" (default), "idle", or "visible". The ?island import is untyped, so it needs a ts-ignore line above it.
Reflects HubSpot as of June 2026 · verify against the linked docs
One island is a build. The seams are a practice.
Islands are one seam in a platform full of them — code vs. live content, one page vs. every page, local vs. the account. The HubSpot CMS module teaches the full set, so you can direct an agent through a real theme without clobbering the work of everyone who edits it live.
Prefer it as a reusable tool? This prompt is the public twin of the kit's /build skill. The full kit ships with the course.