---
name: build-section-pitfalls
---

## When to use

The pitfall table for authoring a Section: what fails silently on the canvas, why, and the fix. Read before reporting a Section done.

Part of the Section-authoring set. Start at [`build-section`](build-section.md).

## Common pitfalls

| Pitfall | Why it bites | Fix |
| --- | --- | --- |
| Trying to link the section to a scalar field (`title`, `description`, `image`) | Studio's picker doesn't offer scalar fields: a section composes UI against a shape, not a value | Pick the parent Group / Global Field / CT / Block. Expose individual props later via Expose Section Prop |
| Linking to a multi-variant schema without dropping a Repeater | Section composes against the whole list as one shape. Renders only the first item or fails to bind | When `isMultiple=y`, drop a Repeater bound to the array path and place components inside it |
| Picking Modular Block instead of a specific Block type | Bindings are typed against the union of block shapes: most fields don't resolve | If the section is for one block type, pick `block` and select that specific block. If the section handles many, use Repeater + Condition Block per block type |
| Skipping Connect A Schema | Section is static-only. Won't auto-bind on templates. Authors wire every value by hand | Pick a structural schema. Only skip when you know the section is purely presentational |
| **Binding a prop to a field that isn't in the Data Picker** (forcing a path, keeping a stale binding to a removed field) | The bind resolves to `undefined` at runtime, so the component falls back to its registration `defaultValue`. The canvas shows plausible-looking placeholder content, so the break is invisible until production renders empty. | Field-existence gate (Step 5d): only bind fields the picker offers. When the field is missing, fix the linked schema or the CT (`migrate-ct-schema`). Never force the bind. |
| **A whole-array/object component prop with no multi-valued field on the CT** (e.g. `Article Grid` bound to `articles` on a CT that has no `articles` reference) | Nothing to bind the collection to, so the component renders its default array forever (`ARTICLE ONE / TWO / THREE`). Looks like it "works" in Design Mode. | Collection-source gate (Step 5e): add the multi-Reference / Modular Block / multiple Group, or pin a query, or decompose to Repeater + atomic child Section (`build-repeating-section`). |
| **Reading Design-Mode placeholder as real bound data** | Design Mode renders `registerComponent` defaults. A broken bind and a working bind look identical there. Ship it and production is empty. | Always verify in **Preview Mode** (Step 5f). If Preview still shows the generic defaults, the bind didn't resolve. Re-check field existence + source. |
| Dropping Studio default components instead of Registered Components | No brand consistency. Section ignores the project's component library | Switch palette category to **Registered Components** before dragging |
| Skipping the Expose Props step on Save | Template authors get a locked section, no value overrides possible | Toggle the props that should be overridable in the Expose Props modal |
| Assuming the Section is broken because the canvas shows defaults, not CMS values | Design Mode renders registration defaults. Preview Mode renders real bindings. See `use-repeater` for the two-mode model. | Toggle Preview Mode in Properties, then Configuration. |
| Binding inside a Repeater without a Condition Block on references / modular blocks | Iteration items are polymorphic. Bindings silently fail | Wrap with Condition Block (single-CT references included) |
| Picking from the Section's linked-schema root for a prop that sits inside a Repeater | Wrong scope: every iteration shows the same value (the parent's field), not the per-item value | Inside a Repeater, the Data Picker switches to the iteration item's fields automatically. Bind from that root |
| Naming the section "Section 1" / "New Section" | Authors cannot tell sections apart in the palette | Use an intent-revealing name like `Hero Strip`, `Card Grid`, `Testimonial Card` |
| Dropping a **Rows / Box wrapper** before the first real component | Leaves a visible "Drop Here" placeholder zone in the canvas (and in every screenshot) that authors must clean up later. Rows is an e2e-test scaffolding habit, NOT a Studio authoring pattern. | Drop the first component directly onto the canvas root slot. Wrap in a container ONLY when you need explicit layout (e.g. an `hstack` to put two cards side-by-side). |
| Adding a **Repeater for a single, non-list use case** | Repeater iterates a multi-valued field. A static page with one Hero + one Product Card needs zero iteration. Adding one introduces a phantom iteration scope and shifts the Data Picker so the parent Section's fields aren't reachable from inside. | Use Repeater ONLY when the linked field is genuinely a list (Modular Block list, multi-Group, multi-Reference, multi-entry pinned query). For a single hero or single card: drop the component directly. |
| Path A when the card visual may be reused elsewhere | Locks the card into one parent. Reuse contexts drift out of sync | Default to Path B (see § Path A vs Path B). |
| Slot inside Repeater without a sized layout container | Child stretches full canvas width | Wrap in grid/flex/`max-width` Box. See `use-section-slot` § Layout container. |
| Leaving empty "Drop Here" zones at the bottom of the canvas | Saved compositions render those zones as visible placeholders in screenshots and to authors browsing the section gallery. | Before Save, switch to Layers and delete any orphan empty Box / Slot rows. Acceptance: the Layers tree contains ONLY the components you intended. No orphan structural wrappers remain. |
| **Typing a Tailwind (or any scanned-utility) class into the Design panel and seeing no effect**: the class is on the element in DevTools with no rule behind it | Tailwind emits CSS only for classes it finds while scanning source files. A composition is JSON in Contentstack, not a file in the repo, so a class that exists only in the Design panel is never generated. Identical symptom to a typo'd class name. | Keep load-bearing utilities in source: put the layout on a registered component, or list the class in Tailwind's `safelist` so it is always emitted. Reach for the Design panel for values the design system already ships. |
| **Assigning a CSS class to a Basic `box` and expecting `display: flex/grid` to apply**: the box renders but the children stack vertically (no flex) or single-column (no grid). | The Basic `Box` component is just `<div {...rest} {...studioAttributes}>`. Studio renders box layout from the node's **`styles.default.responsiveStyles.default`** (set via the Design panel on the node) and surfaces it through the renderer's style pipeline. An external CSS class on the app side has no node-level styles to attach to and only applies whatever rules its stylesheet defines. It does NOT make Studio's renderer emit `display: flex`. | Author Basic-box layout via node `responsiveStyles` in the Design panel: select the Box, open the Design tab, then set `display`, `gap`, `alignItems`, `flexDirection`, etc. The renderer writes these into the DOM. Reach for a CSS class only for tokens/colors/typography already wired through your design system, never for the load-bearing layout shape. (Custom registered components are different: they can apply layout via their own React/CSS.) |
