require-island-editor
"Every island must declare whether it is live in the editor canvas."
Venue: eslint — Every island must declare whether it is live in the editor canvas.
A file is an island when its directive prologue says "use client" and it has a
default export (see server vs client). Every island must carry:
export const editor = { live: true };live must be written as true or false literally. A computed value reports.
live is also the whole declaration. Three fields that an older, wider editor carried
are gone, and each one reports:
| Field | What replaced it |
|---|---|
shieldMode |
Nothing to declare — the canvas derives select-first shields from your island’s own markup. |
liveSurface |
The same derivation; the invented surface attributes it needed are gone with it. |
props |
Nothing — an island renders the same props in the canvas as on a published page. To react to what the author selected, read selection from useEditor(). |
They report rather than being ignored: nothing reads them, but a declaration still reads to a human as a working one, so an island would keep a shield surface or an editor-only prop merge in its source with no way to learn it stopped happening.
Two files are exempt, and neither is a loophole:
- A module with no
"use client"— it is server-rendered code, not an island. - A
"use client"module with no default export — the platform’s codemod only turns a default-exported client module into an island, so this file never becomes one.
Reason
Section titled “Reason”live controls one thing: whether the editor canvas hydrates your island, or leaves it
as the static HTML the server rendered. Both answers are legitimate. A gallery lightbox
wants live: true so an author can see it behave; a scroll-triggered animation wants
live: false so it does not fire while someone is editing copy.
It answers for a top-level island. One island rendered by another hydrates inside its
parent’s React tree, so the nested module’s own live is never read — an island that can
end up nested gates editor-hostile behavior itself rather than through the flag (see
Islands).
The rule exists because the answer used to be expressed by saying nothing. An island
with no editor export simply never hydrated in the canvas — which meant “I have not
thought about this” and “I want static HTML here” produced identical code. They are not
the same statement, and the difference is invisible in review.
live is read once, when your module loads. That is why it must be a literal: a
value computed at runtime cannot change the answer, so a non-literal is never a working
declaration — only a misleading one.
Declare it. If you are unsure, live: false is the safe answer: it is exactly what your
island does today, now written down.
Before
Section titled “Before”"use client";
import { useState } from "react";
export default function Lightbox({ headline }: { headline: string }) { const [open, setOpen] = useState(false); return ( <div> <button onClick={() => setOpen(true)}>Open gallery</button> {open && <p>{headline}</p>} </div> );}"use client";
import { useState } from "react";
// Authors open the lightbox in the canvas to check it, so hydrate it there.export const editor = { live: true };
export default function Lightbox({ headline }: { headline: string }) { const [open, setOpen] = useState(false); return ( <div> <button onClick={() => setOpen(true)}>Open gallery</button> {open && <p>{headline}</p>} </div> );}Type it if you want the editor to check the shape:
import type { IslandEditor } from "@homepages/template-kit";
export const editor = { live: true } satisfies IslandEditor;See also
Section titled “See also”- Islands — the other half of the contract: the
useEditor()hook, and the two patterns a live island uses it for. require-editor-reason— the escape hatch a live island uses instead of a widereditordeclaration, and what it must say.- Server vs client — the directive, the prologue, and what an island may do.