props-from-schema
"A Renderer's Props type must be derived from schema.ts, not hand-written."
Venue: eslint — A Renderer’s Props type must be derived from schema.ts, not hand-written.
The rule runs only on Renderer.tsx. It reports an exported type alias or interface
whose name ends in Props — Props, HeroProps, GalleryProps.
Two consequences of that being the whole test:
- It does not read the right-hand side. Even
export type Props = SectionProps<typeof schema>;reports if you write it in the Renderer. The derivation belongs beside the schema declaration; the Renderer imports it. - A non-exported local type is invisible to the rule. An internal
type Row = { id: string };used to type a helper’s parameter is your business, and is not reported.
import type { Props } from "./section.js"; is the shape the rule is steering you to, and
it reports nothing.
Reason
Section titled “Reason”The schema is the section’s single source of truth, and the prop type is inferred from
it — SectionProps<typeof schema> reads the schema literal and resolves each slot to
its runtime value shape: a text becomes string (blank is ""), an optional
number becomes number | null, an image becomes ImageValue, and so on.
A hand-written Props shadows that inference, and then drifts silently. Add a slot
to the schema and forget to render it: with the derived type, reading slots.subhead in
the Renderer is a compile error until the slot exists, and the platform’s tooling and
your renderer agree by construction. With a hand-written Props, the Renderer keeps
compiling happily against the old shape — while the editor, the fill pipeline, and the
schema all now believe in a slot your markup has never heard of. Nothing fails until a
user is looking at a page with a missing block in it.
It also forces the editor to reason about a second definition of the same thing.
Declare the derived type once beside the schema, in section.ts — and import it in
the Renderer:
export type Props = SectionProps<typeof schema>;import type { Props } from "./section.js";Before
Section titled “Before”import { Section } from "@homepages/template-kit";
export type Props = { slots: { headline: string };};
export function Renderer({ slots }: Props) { return ( <Section> <h1 data-slot-id="headline" data-slot-text-leaf="">{slots.headline}</h1> </Section> );}import { defineSchema, type SectionProps, text } from "@homepages/template-kit";
export const schema = defineSchema({ label: "Hero", slots: { headline: text.medium({ label: "Headline" }), },});
export type Props = SectionProps<typeof schema>;return ( <Section> <Slot.Text slot={slot.headline} as="h1" /> </Section>);The builders preserve the literal types SectionProps needs: without them the types
are widened away and SectionProps can no longer tell one slot kind from another.
See also
Section titled “See also”Renderer.tsx—SectionPropsinference off the schema literal, and why the prop type is never hand-written.