canvas-component-composability
Prefer small, focused components over monolithic ones with many props. When a component starts accumulating many unrelated props, decompose it into smaller, composable pieces.
For repeatable card/list/grid UI, default to two Canvas components: a parent
layout component with a slot for the repeated children, and a child component
for one item. Do not flatten repeated items into numbered prop groups such as
car1Name, car2Name, feature1Title, or card3Image.
Ownership
This skill owns the reusable modeling rules for:
- props vs slots
- variants vs granular props
- repeatable-content patterns
- granularity checks (split, merge, slot extraction)
For the canonical reuse-first policy, follow
canvas-component-definition. For
exact component.yml grammar and slot schema, follow
canvas-component-metadata.
Reuse with existing components
When the task names existing components or clearly implies composition, start
with the reuse check in
canvas-component-definition
before creating a new component. This skill focuses on how components compose
once that boundary is clear.
Reference map
Load references only as needed:
- Repeatable lists/grids and array-to-slot conversion:
references/repeatable-content.md - Slot interactivity in empty flex/grid containers:
references/repeatable-content.md("Slot container minimum size" section)
Props vs slots
Slots are the primary mechanism for composability. Instead of passing complex
data through props, use slots to let parent components accept child components.
This matches how Canvas users build pages by placing components inside other
components. This is the design-time rubric to use before writing
component.yml.
When to use slots vs props
| Use slots for | Use props for |
|---|---|
| Variable number of child components | Single, required values (text, URL) |
| Content that users should compose | Configuration options (size, color) |
| Complex nested structures | Simple data (strings, booleans) |
| Content that varies between instances | Content consistent across instances |
Treat a single image as one prop, not as a slot and not as multiple URL/alt
props. If a component needs one image, use a semantic image prop such as image
or backgroundImage with the Canvas image schema ref.
For repeatable cards/items, the parent usually owns layout props such as heading, intro text, alignment, or column count, while each child owns item content props such as title, image, price, label, CTA, or metadata.
Repeatable rich items should use the parent-slot-plus-child-component pattern,
not array-of-object props. See references/repeatable-content.md.
Apply the rubric to blocks this way: presets and simple copy stay in props; grids of cards, tab panels, footer columns, or swappable regions belong in slots or nested child components with their own slots. Do not encode large composed regions as string props or JSON blobs.
Variants vs granular props
Use one primary modeling style per component. Sometimes a hybrid is valid: one
variant plus one or two orthogonal toggles.
Variants are the default:
- A primary
variantenum selects named presets whose values imply a bundle of styles and structure rules. - Use a primary
variantenum when the design has named presets or when many arbitrary combinations would be invalid. - Keep
variantas the prop ID for the main preset field instead of synonyms likeappearanceorlayoutVariant. - Add orthogonal enums such as
sizeordensityonly when those axes are independently meaningful. - Variants usually produce fewer invalid combinations, simpler Canvas forms, and easier alignment with design-system or Figma variants.
- Map enum values to CVA or equivalent implementation branches and theme tokens.
Granular props are appropriate when:
- the user explicitly wants separate controls per axis
- the axes are independently valid in many combinations
- product rules or analytics require individual toggles
Hybrid modeling is acceptable, but avoid a large pile of independent knobs when the design really describes a small set of named presets.
Choosing
| Situation | Favor |
|---|---|
| Mutually exclusive layouts (A or B, not mix-and-match) | Variants |
| Design uses named presets or Figma variants | Variants |
| Many combinations would be invalid or untested | Variants |
| Product needs arbitrary mixing of orthogonal toggles | Granular (or hybrid) |
| User asks for separate controls per axis | Granular |
Document the choice in the decomposition handoff so implementation and
component.yml stay aligned.
Prop order in handoffs and metadata
Match the decomposition handoff and component.yml order so Canvas editors see
a sensible form flow.
- If there is one primary preset enum, name it
variantand list it first. - Then list content props (copy, links, media).
- Then list remaining configuration props (secondary toggles, style helpers).
When there is no primary variant, list content props first and configuration
after.
If there is no single variant but the model is otherwise preset-oriented,
still put the dominant preset enum first when it exists.
Canvas-specific constraints
- No array-of-object props for repeatable rich items. Use a parent slot plus child component instead.
- Props are editor-facing. Do not expose implementation-only values.
- Required props should not rely on silent JSX defaults. Metadata and the editor should supply those values explicitly.
- Keep prop IDs camelCase-aligned with their labels and finalize exact shape in
canvas-component-metadata.
When a prop pretends to be a slot
- JSON-in-a-string or serialized blocks in a prop
- arrays of objects for child UI
Replace these with slot-plus-child-component composition.
When a slot pretends to be a prop
- A slot that always holds one component type with fixed props
In those cases, consider whether the parent should own that markup or use a narrow prop instead.
Declare slots in component.yml
Declare slots in component.yml and render them as named props in JSX.
For exact slot schema and constraints (map vs [], slot keys, children
handling), follow canvas-component-metadata as the source of truth.
Granularity checks
Use these heuristics when auditing whether a component boundary is too coarse or too fine. Use them after an initial tree and prop/slot sketch exist. The goal is that editors can reason about each component and instances can reuse without copy-paste props.
Signs a component should be decomposed
Consider breaking up a component when it has:
- Many granular toggles that really describe a small set of named presets instead of independent choices. Prefer variants unless the user wants granular control.
- One
machineNameowning unrelated concerns - More than 6-8 props that serve distinct purposes
- Props for elements that make sense as standalone components (breadcrumbs, titles, metadata, navigation)
- A growing prop list where each prop maps to a visually separate block
- Built-in layout assumptions that limit where the component can be used
- Multiple distinct visual sections that could be reused independently
- Repeated prop groups that differ only by an index or prefix/suffix
(
item1Title,item2Title,item3Title) - Layout plus multiple independent content columns that could change order
- A parent that mixes layout shell with multiple independent content blocks
showX/showYchains that encode which children exist instead of letting authors place children directly
Common decomposition patterns
Page-level elements should be separate components
Elements that appear on many pages but are not always needed together should be separate components.
Extract repeated patterns into small components
When the same combination of elements is repeated, extract it:
- Date + category/tag ->
article-meta - Cover image + download button ->
resource-cover - Label + value pairs ->
metadata-item - Icon + text link ->
icon-link - Grid/list wrapper + repeated card ->
card-grid+card - Featured cars wrapper + repeated car card ->
featured-cars-grid+car-card
Use layout components instead of built-in layouts
Do not bake two-column or grid layouts into content components. Use layout components and compose content into them.
When to extract a child component
Extract a child component, or add a slot, when any of these are true:
- The block appears twice or more with the same role
- The block has a clear name and could appear elsewhere on the site
- Authors might reorder, omit, or swap that block independently
- The block mixes interaction with static siblings in a way that muddies the parent story
Typical extractions: Meta, Actions, Media, Nav, and item components for
lists or grids.
Canvas editing lens
- If authors must drop arbitrary blocks into an area, use a slot rather than a prop that encodes component type lists.
- If copy is fixed marketing text with no composition need, string props are appropriate.
- If the only composition is one optional rich block that never varies in type, consider keeping it internal or using a single slot rather than many micro-slots.
When not to decompose
Keep components together when:
- They always appear together in every design variant and never make sense separately
- They share significant internal state that would be awkward to lift up
- The visual design tightly couples them (for example, overlapping elements, shared backgrounds, masking)
- Decomposition would create components with only 1-2 props that are not useful elsewhere
Fine signals (merge or convert)
- Components that only wrap a single heading or static paragraph with no second instance planned
- Slots that always receive one specific child type and never vary. Merge into the parent or replace with props if the structure is fixed.
- Wrapper components whose sole job is CSS on one element. Prefer parent layout or utility classes unless reuse clearly demands that wrapper.
Anti-patterns
- Slot per
div: every flex child becomes a slot without giving authors real control. Group by meaningful composition boundaries. - Prop drilling toggles: many booleans for independent blocks that should be separate children or slots
- God layout: one component hard-codes the whole page grid. Prefer layout components and nested regions instead of one monolithic page shell.
Rework loop
- Mark each failing node as coarse, fine, or an authoring mismatch.
- For coarse nodes, split responsibility, introduce slots for variable regions, or extract repeated items.
- For fine nodes, merge nodes, collapse slots, or move fixed structure inside the parent implementation.
- Re-sketch props and slots only for the nodes whose boundary changed.
- Re-audit until pass or document exceptions, including why the usual rule does not apply.
Vocabulary (optional)
Terms like atom, molecule, and organism can describe scale, not a mandate. Prefer reuse, editor control, and Canvas constraints over strict atomic-design tiers.
More from drupal-canvas/skills
canvas-component-definition
Start here for any React component task to enforce the canonical Canvas
90canvas-component-metadata
Define valid component.yml metadata for Canvas components, including props,
88canvas-component-utils
Use utility components to render formatted text and media correctly. Use when
75canvas-data-fetching
Fetch and render Drupal content in Canvas components with JSON:API and SWR
75canvas-styling-conventions
Style Canvas components with Tailwind CSS 4 theme tokens and approved utility
72canvas-component-push
Push validated Canvas component changes to Drupal Canvas and recover from
47