Make part of a section interactive
Make part of a section interactive with a client-hydrated island.
Make part of a section interactive.
The delta
Section titled “The delta”Starting from the starter’s hero section, add a "use client" file next to the
four contract files, matching the shape of the starter’s own island,
ExpandableText.tsx: the directive as the file’s first statement, a default-exported
function typed against plain props, and useState for the interactive part.
// sections/hero/ContactReveal.tsx — a new island, same shape as ExpandableText"use client";
import { type ReactNode, useState } from "react";
import type { IslandEditorOptions } from "@homepages/template-kit";
export default function ContactReveal({ hiddenLabel, revealedValue,}: { hiddenLabel: string; revealedValue: string;}): ReactNode { const [revealed, setRevealed] = useState(false); return revealed ? ( <p className="text-base font-medium text-ink">{revealedValue}</p> ) : ( <button type="button" className="font-medium text-accent underline" onClick={() => setRevealed(true)} > {hiddenLabel} </button> );}
// Optional, as on ExpandableText: hydrate live in the editor canvas too.export const editor: IslandEditorOptions = { live: true,};Render it from Renderer.tsx like any other component:
import ContactReveal from "./ContactReveal";// …<ContactReveal hiddenLabel="Show phone number" revealedValue="(555) 010-2020" />If the island renders a schema slot’s own content — the way the starter’s blurb
slot renders through ExpandableText — wrap it in <Slot id="…"> with the matching
schema id, exactly as Renderer.tsx already does:
<Slot id="blurb"> <ExpandableText text={slots.blurb} collapsedChars={140} moreLabel="Read more" /></Slot>A standalone island with no bound slot — like ContactReveal above — needs no
<Slot> wrapper; <Slot> marks an editable slot’s DOM for the editor, not
“this is interactive.”
Props crossing into an island must be JSON-serializable — strings, numbers,
booleans, and plain arrays/objects only, never a function, a Date, or a class
instance; the island owns its own event handlers instead of receiving one. This is
the only mechanism for interactivity in a section: a Renderer stays a pure function
of its props, and any state, effect, or event handler moves into a "use client"
island like the one above.
Commands
Section titled “Commands”None — this is a pure content edit. No CLI command beyond the author loop below.
Verify
Section titled “Verify”Verify with the author loop.
See also
Section titled “See also”- Islands — writing one, its props contract, editor options, and the DOM contract a published page hydrates.
- Server vs client — how a file is scoped server or client, and the three rules an island is exempt from.
serializable-island-props— the rule that catches a non-serializable prop before the render ever throws.