no-raw-slot-values
"Read slot values through bindSlots/bindItems, never off the raw `slots` prop."
Venue: eslint — Read slot values through bindSlots/bindItems, never off the raw
slotsprop.
A section reads slot values only through a binding. The rule reports, in any .ts or
.tsx file:
- a member read of the
slotsprop —slots.units,slots["headline"]; - the same read spelled as destructuring —
const { headline } = slots; - the undestructured form —
props.slots.<name>.
Reading a slot definition off the schema (schema.slots.<name>) is not a value read
and passes. So do nav, variant and every other section prop — this rule
is about slot values only.
Reason
Section titled “Reason”bindSlots and bindItems are the value boundary. What the schema promises, the
binding delivers: every value is normalized to its kind’s canonical shape (a legacy raw
number stored in a text slot arrives as a string; an unrecognizable value degrades to
that kind’s blank), and each binding carries a derived present you can branch on.
The raw slots prop is that same data before any of it happens. A value read from it is
whatever the content document holds — so the section has to defend itself, and that
defense spreads. One section grew a hand-written string coercion it called at ten
separate sites, because a single unguarded read anywhere in the file was enough to print
[object Object] on a published page. Binding once at the top of the render is what
makes those guards unnecessary, and this rule is what keeps them from coming back.
Bind once, at the top of the render, and read through the binding everywhere after.
Before
Section titled “Before”import { bindSlots, Section, Slot } from "@homepages/template-kit";
import { schema } from "./section.js";import type { Props } from "./section.js";
export function Renderer({ slots }: Props) { const slot = bindSlots(schema, slots); // template-kit/no-raw-slot-values: skips the binding layer const units = slots.units; return ( <Section> <Slot.Text slot={slot.intro} as="p" /> <p>{units.length} homes</p> </Section> );}import { bindItems, bindSlots, defineSchema, list, number, Section, Slot, SlotGroup, text } from "@homepages/template-kit";import type { SectionProps } from "@homepages/template-kit";
const schema = defineSchema({ label: "Floorplans", slots: { intro: text.long({ label: "Intro" }), units: list.of( { label: text.short({ label: "Label" }), beds: number({ label: "Beds" }) }, { label: "Units" }, ), },});
type Props = SectionProps<typeof schema>;
export function Renderer({ slots }: Props) { const slot = bindSlots(schema, slots); const units = bindItems(slot.units); return ( <Section> <Slot.Text slot={slot.intro} as="p" /> <Slot.List slot={slot.units} as="ul"> {units.map((unit) => ( <SlotGroup slot={unit} as="li" key={unit.index}> <Slot.Text slot={unit.fields.label} as="span" /> </SlotGroup> ))} </Slot.List> </Section> );}See also
Section titled “See also”- Values and emptiness — what the binding
guarantees, what
presentderives, and who owns formatting and the empty state. - Slot primitives — the primitive for every slot kind, and what each one does with a blank value.
no-slot-marker-literal— the identity half of the same boundary: markers come from the primitives, values come from the bindings.