template-kit/sidebar-order
The template-kit/sidebar-order authoring rule — what it enforces, why, and how to fix it.
The editor sidebar’s cards are ordered by the schema. The page is ordered by your JSX. They must agree — and when they don’t, the schema is what moves.
The editor renders one sidebar card per group (and one per ungrouped slot), in the order the schema declares them. This check server-renders the section’s baseline fixture, reads the first-appearance order of the slot markers in the resulting HTML, and asserts that it matches the schema’s declaration order.
Three carve-outs, each of which looks like a bug if you don’t know about it:
- A slot rendered more than once counts at its first marker. An address shown in a header and again in a footer is ordered by the header.
- Order within a group is not checked, and neither is contiguity. A group’s members may appear in any order on the page, and other slots may sit between them. Responsive layouts legitimately do both, and the sidebar collapses the group into one card anyway.
- A slot declared but never rendered is excluded. It has no position on the page, so it cannot contradict one.
Reason
Section titled “Reason”Two files describe the same section in two different orders, and only one of them is visible to the person editing. The failure mode is drift: you reorder the markup — move the eyebrow above the headline, promote the price — the schema keeps its original order, and the sidebar quietly stops matching the page.
Nothing breaks. The section renders, the slots fill, and every card still works. The user just finds that the second card edits the first thing they see, and stops trusting the panel. This check makes that drift impossible to ship rather than merely unlikely.
Reorder the schema, not the page. The page’s visual order is the truth — it is what the
user is looking at — so bring schema.ts to it: reorder the slots keys, and, if you have
groups, the meta.groups entries, so declaration order matches the page’s top-to-bottom
order.
Before
Section titled “Before”The schema declares headline first, the page renders eyebrow first:
// sections/hero/schema.ts — template-kit/sidebar-orderimport type { SectionProps, SectionSchemaShape } from "@homepages/template-kit";
export const schema = { meta: { displayName: "Hero" }, slots: { headline: { type: "text", size: "medium", required: true, editable_by_user: true }, eyebrow: { type: "text", size: "short", required: false, editable_by_user: true }, price: { type: "text", size: "short", required: false, editable_by_user: true }, },} as const satisfies SectionSchemaShape;
export type Props = SectionProps<typeof schema>;// sections/hero/Renderer.tsx — the page, top to bottom: eyebrow, headline, priceimport { Section, Slot } from "@homepages/template-kit";
import type { Props } from "./schema.js";
export function Renderer({ slots }: Props) { return ( <Section> <Slot id="eyebrow" textLeaf><p>{slots.eyebrow}</p></Slot> <Slot id="headline" as="h1" textLeaf><span>{slots.headline}</span></Slot> <Slot id="price" textLeaf><p>{slots.price}</p></Slot> </Section> );}The Renderer is untouched. The schema moves:
import type { SectionProps, SectionSchemaShape } from "@homepages/template-kit";
export const schema = { meta: { displayName: "Hero" }, slots: { eyebrow: { type: "text", size: "short", required: false, editable_by_user: true }, headline: { type: "text", size: "medium", required: true, editable_by_user: true }, price: { type: "text", size: "short", required: false, editable_by_user: true }, },} as const satisfies SectionSchemaShape;
export type Props = SectionProps<typeof schema>;See also
Section titled “See also”- The schema system —
meta.groups, whose entry order is the other half of the sidebar’s card order. missing-slot-marker— the markers this check reads to establish the page’s order.