`Renderer.tsx` props
The `Renderer.tsx` declaration — props derived from the schema, what an `ImageValue` carries, and the purity rule every renderer is held to.
Never hand-write your prop type. Derive it, and the schema stays the single
source of truth. bindSlots ties that same schema to this render’s values, and each
Slot.* primitive renders one binding and owns its marker — see
Slot primitives for the whole surface.
import { bindSlots, defineSchema, Section, Slot, text } from "@homepages/template-kit";import type { SectionProps } from "@homepages/template-kit";
const schema = defineSchema({ label: "Hero", slots: { headline: text.medium({ label: "Headline" }) },});
type Props = SectionProps<typeof schema>;
export function Renderer({ slots, nav }: Props) { const slot = bindSlots(schema, slots);
// headline is `string` — a text slot's canonical blank is "", never null return ( <Section id={nav.selfAnchor}> <Slot.Text slot={slot.headline} as="h1" /> </Section> );}The schema import here is a value, not import type: a binding carries the slot’s
declared def, which is how Slot.Image and Slot.Video read the slot’s own frame and
playback instead of having them re-passed at the call site.
SectionProps reads the schema literal and resolves each slot to its runtime
value shape: a text becomes string (an unfilled optional arrives as the blank
""), an optional number becomes number | null, an image becomes
ImageValue, a list an array, and so on. A slot you did not
declare is a compile error to read.
An ImageValue splits into fields a renderer reads and fields it only carries.
Hand the whole value to Image,
which reads url, alt, responsive, and mobile off it. Ignore rect, ar, and
crop_key: they are the editor’s re-edit state (so its cropper can reopen at the last
crop) and the provenance of the already-cropped url. Framing is baked into the asset
the platform serves, never applied at render time — an uncropped image is a centered
object-cover over the original and a cropped one is a pre-cropped derivative, so there
is no framing for a renderer to apply. A slot’s crop config steers the editor’s cropper,
not your markup.
The markup a section is required to emit — the section root, the editor’s slot markers — comes from the kit’s primitives; everything else is your own JSX. See Slot primitives.
Renderers must be pure functions of props — no fetches, no globals, no
useEffect for data, no Date.now() / Math.random(). They are rendered to
static markup by the publisher and re-mounted constantly by the editor.
See also
Section titled “See also”- The schema — the declaration these props are inferred from.
- Slot primitives — the surface that renders each binding.
- The marker contract — what the rendered DOM owes the editor, and what it does not.
props-from-schema— the lint rule that refuses a hand-written prop type.