template-kit/slot-group-marker
The template-kit/slot-group-marker authoring rule — what it enforces, why, and how to fix it.
A group declared in
meta.groupswith two or more members must be wrapped in<SlotGroup id="…">in the JSX. Grouping is authored in two places, and they must agree.
For every entry in schema.ts’s meta.groups whose flattened member list has ≥ 2
members, the section’s JSX must contain <SlotGroup id="<that group's id>"> wrapping a
container that is a shared ancestor of every member’s marker.
“Flattened” is literal: members: ["agent_name", ["agent_email", "agent_phone"]] is three
members, not two — a [a, b] tuple is a 2-up row, not a single member.
Membership is read from the typed meta.groups block. The check never guesses a group by
scanning your markup for things that look related.
Carve-outs:
- A 1-member group needs no wrapper. One slot, one card — the group adds no grouping the marker isn’t already doing.
- A single slot rendered more than once is not a group. An address shown in both a
desktop and a mobile layout is one slot in two places; it needs no
meta.groupsentry and no<SlotGroup>.
Reason
Section titled “Reason”meta.groups makes the editor collapse several slots into one accordion card in the
sidebar. The <SlotGroup> wrapper is the other half of that promise: the canvas outlines
and selects the group as one unit, because its hover/selection chrome targets the
data-slot-group container.
Declare the group and omit the wrapper and the two halves disagree. The sidebar shows one card, and the canvas — with no group container to find — falls back to outlining the members one at a time. The user sees a single “Agent” card that highlights three separate boxes on the page.
SlotGroup must be an ancestor of every member’s data-slot-id element, because the
editor resolves the group with closest("[data-slot-group]") from the clicked slot. A
marker that sits beside the members rather than around them is never found.
Wrap the members’ shared container in <SlotGroup id="…">, using the same id as the
meta.groups entry. If the slots do not actually edit as a unit, the other honest fix is
to delete the group from meta.groups — then each slot is its own card and no wrapper is
wanted.
Before
Section titled “Before”import type { SectionProps, SectionSchemaShape } from "@homepages/template-kit";
export const schema = { meta: { displayName: "Contact", groups: [ { id: "agent", label: "Agent", members: ["agent_name", ["agent_email", "agent_phone"]] }, ], }, slots: { agent_name: { type: "text", size: "short", required: true, editable_by_user: true }, agent_email: { type: "text", size: "short", required: false, editable_by_user: true }, agent_phone: { type: "text", size: "short", required: false, editable_by_user: true }, },} as const satisfies SectionSchemaShape;
export type Props = SectionProps<typeof schema>;// sections/contact/Renderer.tsx — template-kit/slot-group-marker: agentimport { Section, Slot } from "@homepages/template-kit";
import type { Props } from "./schema.js";
export function Renderer({ slots }: Props) { return ( <Section> <div className="flex flex-col gap-2"> <Slot id="agent_name" textLeaf><h3>{slots.agent_name}</h3></Slot> <Slot id="agent_email" textLeaf><span>{slots.agent_email}</span></Slot> <Slot id="agent_phone" textLeaf><span>{slots.agent_phone}</span></Slot> </div> </Section> );}The container was already there — it just needed to be the group:
import { Section, Slot, SlotGroup } from "@homepages/template-kit";
import type { Props } from "./schema.js";
export function Renderer({ slots }: Props) { return ( <Section> <SlotGroup id="agent" className="flex flex-col gap-2"> <Slot id="agent_name" textLeaf><h3>{slots.agent_name}</h3></Slot> <Slot id="agent_email" textLeaf><span>{slots.agent_email}</span></Slot> <Slot id="agent_phone" textLeaf><span>{slots.agent_phone}</span></Slot> </SlotGroup> </Section> );}See also
Section titled “See also”- Contract primitives:
SlotGroup— the ancestor requirement and what the marker does. - The schema system — grouping is
declared in
meta.groups; there is no per-slotgroupfield. missing-slot-marker— the per-slot marker each member still needs.