The Composability Ladder
Studio's whole architecture is one idea applied four times: a slot is a placeholder you fill later. The same fill mechanism repeats up the stack, once at the component level (your React code), once inside Sections (Section Slots), once between Sections (Sections compose Sections via Section Slots), and once at the page level (Templates compose Sections).
If you internalize this single rung-on-rung pattern, the rest of Studio's UI stops being a list of features and becomes one ladder you climb deliberately.
The Render Path Is Orthogonal
Before climbing, decide CSR vs SSR once at the app shell. It changes WHERE sdk.fetchCompositionData runs, client (useCompositionData hook) vs server (Server Component / getServerSideProps / loader), and nothing about the composition shape. Every rung below is identical in both modes.
→ CSR vs SSR: choosing a render strategy
The default for App Router (SEO-needing visitor routes) is RSC: fetch in a Server Component, render in a "use client" wrapper. CSR is for SPAs without SEO needs. The canvas route is always client-only.
The Four Rungs
Each rung fills what the rung below it exposed. The same slot mechanism climbs the ladder.
Rung 1: Component with Slot Props
A registered React component declares its prop schema. Slot-typed props are the placeholders the next rung fills:
// Your React code
export function Hero({ headline, children }: HeroProps & StudioAttributes) {
return (
<section className="hero">
<h1>{headline}</h1>
{children} {/* ← the slot. Studio renders whatever the Section drops in. */}
</section>
);
}
// Registration
registerComponent({
type: "doc-hero",
component: Hero,
schema: {
headline: { type: "string", defaultValue: "..." },
children: { type: "slot" }, // ← THIS is the placeholder
},
});A slot prop has no defaultValue; it starts empty until something fills it.
→ Component schema: slot prop → register-component skill
Rung 2: Section Composing Components + Opening Section Slots
A Section drops registered components onto a canvas and binds their props to a linked schema (so the component's data resolves from the CMS). Inside any component's slot prop, the Section author drops a Section Slot, Studio's smart-container that says "leave this placeholder open for the rung above me to fill."
Why a Section Slot inside the component's slot prop, instead of just dropping a CTA component? Because the Section's job is to author the layout shape, not lock in one CTA. The Section Slot defers the actual fill to the Template that uses this Section, same Hero Strip serves a blog post (links to next article) AND a product page (links to checkout), with different CTAs.
→ Sections overview → Section Slots → understand-section-slots: concept skill → build-section skill
Rung 3: Section-in-Slot (Sections Composing Sections)
This is the rung most people miss. A Section's Section Slot doesn't have to be filled with a raw component, it can be filled with another Section.
The fill rule recurses. A child Section can itself expose another Section Slot, and the rung above fills THAT with yet another Section, for as many levels as the design demands.
Two patterns this unlocks:
- Reuse complex compounds. A "Hero Strip with optional CTA + optional ribbon" Section authored once can drop into a "Page Hero" Section's slot, a "Landing Page Hero" Section's slot, and a "Blog Post Hero" Section's slot, each fills the inner CTA slot differently.
- Author-friendly variability. Instead of forking a Section to add one variant, expose a Section Slot. Authors swap the inner Section per template instance without engineering touching anything.
→ understand-section-slots: Section-in-Slot rule → use-section-slot skill
Rung 4: Template Composing Sections
A Template owns the URL pattern + connected entry (or freeform) and composes the top-level Sections. The canvas at this rung is a flat list of Sections, the recursion is hidden in the Sections themselves.
Templates fill Section Slots exposed by their Sections AND override exposed props ("Related Blogs" → "Latest from the blog"), both are different shapes of the same idea: the rung above fills the placeholder the rung below opened.
→ Templates overview → Templates compose Sections + Components → build-connected-template skill → build-freeform-template skill
Where Each Rung Lives in Your Stack
| Rung | Lives in | Edited by |
|---|---|---|
| 1: Component | Your repo (TSX) + registerComponent call | Engineer; PR + deploy |
| 2: Section | Composition entry in Contentstack | Author in Studio canvas; publish |
| 3: Section-in-Slot | Composition entry (parent Section); the slot fill is part of its ui tree | Author in Studio canvas; publish |
| 4: Template | Composition entry; URL + section drops | Author in Studio canvas; publish |
Rung 1 ships through git → CI → deploy. Rungs 2-4 ship through Contentstack publish. The engineering boundary stops at rung 1. Everything above is data the author edits.
When to Introduce a New Rung
A heuristic for staying on the right rung:
- "I want this prop bindable to a different CMS field per template." Don't add a slot; expose the prop. The component stays the same; the binding map changes. → expose-section-props
- "I want this region of the Section to hold different content per template." Add a Section Slot in the Section. → use-section-slot
- "I want this region of the component to hold different content." Declare a slot prop on the component. → Component schema § slot
- "I want a totally different layout per template." New Template, same Sections. → build-connected-template
- "I want a different shape inside this Section, but same outer frame." A new child Section filling the same Section Slot. → rung 3.
Acceptance Test: Do You Have the Mental Model?
You have the model when you can read this sentence and unfold it:
"The Blog Post template drops a Hero Strip Section, which drops a <doc-hero> component, which has a children slot prop filled by a Section Slot, which the template fills with a <doc-button> whose href binds to entry.url, all rendered server-side via sdk.fetchCompositionData in a Server Component and hydrated through a "use client" wrapper."
If that reads as one continuous idea (four rungs of the same fill mechanism + an orthogonal render-path choice), you're done. If it reads as a list of features you'd have to look up, walk back through this page and the linked deep-dives.
Next
- Set up Studio: install the SDKs (rung 0)
- Register your components: rung 1
- Sections: rungs 2 + 3
- Templates: rung 4
- CSR vs SSR: the orthogonal render-path choice