Skip to content
HomePagesHomePages template kit
Menu

A collection slot — a repeating, per-item list

Add a repeating list whose items the editor selects and edits one at a time.

Add a repeating list — a unit table, agent cards, a feature list, a gallery — whose items the editor selects and edits one at a time.

Starting from the starter’s hero section, add an object_list slot: a row of nested item slots that repeats. Bind the whole array to a list fact with direct("units") (a unit table) — the array is baked from the property, so it needs no fill-spec.ts decision, exactly like a fact-bound scalar (bind-property-fact.md). Each item field binds to a per-row fact with its own direct(...).

// schema.ts — a repeating list of units, after the `blurb` slot
units: {
type: "object_list",
source: direct("units"),
editable_by_user: true,
required: true,
source_title_field: "unit_label",
display_name: "Units",
item: {
unit_label: { type: "text", size: "short", editable_by_user: true, display_name: "Unit", source: direct("unit_label") },
beds: { type: "number", editable_by_user: true, display_name: "Beds", source: direct("beds") },
baths: { type: "number", editable_by_user: true, display_name: "Baths", source: direct("baths") },
sqft: { type: "number", editable_by_user: true, display_name: "Sqft", source: direct("sqft") },
price: { type: "text", size: "short", format: "currency", editable_by_user: true, display_name: "Price", source: direct("price") },
},
},
// Renderer.tsx — <Slot> marks the whole array, <SlotItem> marks each row
import { Image, Section, Slot, SlotItem } from "@homepages/template-kit";
// SectionProps types an object_list as Record<string, unknown>[], so narrow
// each row to your item shape at the map site.
type UnitRow = { unit_label: string; beds: number; baths: number; sqft: number; price: string };
<Slot id="units" as="ul" className="mt-4 flex w-full flex-col divide-y">
{(slots.units as UnitRow[]).map((unit, i) => (
<SlotItem key={unit.unit_label} index={i} as="li" className="flex justify-between py-2">
<span>{unit.unit_label}</span>
<span>{unit.beds} bd · {unit.baths} ba · {unit.sqft} sqft · {unit.price}</span>
</SlotItem>
))}
</Slot>
// fixtures.ts — supply the array from a scenario's facts
import { scenarios } from "@homepages/template-kit/fixtures";
// ...in base.slots:
units: scenarios.luxuryMultiUnit.facts.units,

Two contracts to know before you widen this:

  • direct() binds only a registry collectionunits or contacts (the List sources). For any other repeating data, drop the source and give the slot a produced_by decision instead (see the fill-spec decision types).
  • An item needs more than one field, or a non-scalar one — a lone scalar field is rejected; use a list slot for a flat list of scalars.

object_list is one of five collection types. A gallery of framed photos is an image_collection, several hosted videos are a video_collection, a flat scalar list is a list, neighborhood points are a poi_list — same <SlotItem> render pattern, different value shape. See the slot types for which to pick.

None — this is a pure content edit. No CLI command beyond the author loop below.

Verify with the author loop.

  • Contract primitives — the SlotItem / SlotGroup contract: both markers a collection needs, and why index must be the row’s array position.
  • The closed vocabulary — every collection slot type (object_list / image_collection / video_collection / poi_list / list) and the direct() List sources.
  • The schema system — the produced_by decision types (structured-list, list-extract, image-collection, video-collection) for a collection AI fills instead of binding to a fact.
  • Bind a slot to a property fact — the direct() pattern this extends from a scalar to a whole array.