--- name: canvas-component-composability description: Design Canvas-ready React components with slots and decomposition-first patterns. Use when (1) Designing a component's prop/slot structure, (2) A component is growing too large, (3) Deciding between props vs slots, (4) Refactoring monolithic components. Ensures Canvas compatibility. --- **Prefer small, focused components over monolithic ones with many props.** When a component starts accumulating many unrelated props, it's often a sign that it should be decomposed into smaller, composable pieces. ## Signs a component should be decomposed Consider breaking up a component when it has: - **More than 6-8 props** that serve distinct purposes - **Props for elements that make sense as standalone components** (breadcrumbs, titles, metadata, navigation) - **Built-in layout assumptions** that limit where the component can be used - **Multiple distinct visual sections** that could be reused independently ## Use slots for flexible composition **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. ### Prefer slots over complex props When a component needs to render variable content, use a slot instead of props with complex structures: ```jsx // Wrong const ResourceDetail = ({ metadata: [ { label: "Type", value: "Report" }, { label: "Author", value: "UNICEF" }, ], }) => (
{metadata.map((item) => ( ))}
); // Correct const ResourceMetadata = ({ items }) => (
{items}
); // Usage: pass MetadataItem components through the slot } />; ``` ### Slots enable Canvas compatibility In Drupal Canvas, users compose pages by dragging components into slots. When you design components with slots: - Users can add, remove, or reorder child components freely - Each child component's props can be edited independently - The parent component doesn't need to know about child component types ### 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 | ### Declare slots in component.yml Every slot must be declared in the component's `component.yml`: ```yaml slots: content: title: Content sidebar: title: Sidebar ``` In the JSX, slots are received as props and rendered directly: ```jsx const TwoColumnLayout = ({ content, sidebar }) => (
{content}
); ``` ## Common decomposition patterns ### Page-level elements should be separate components Elements that appear on many pages but aren't always needed together should be separate components: ```jsx // Wrong const ResourceDetail = ({ breadcrumbItems, title, date, taxonomyTag, coverImage, downloadButtonUrl, metadata, description, }) => (
{/* ... */}
); // Correct const ResourceDetailPage = () => (
} />
} />
} />
); ``` ### Extract repeated patterns into small components When you see the same combination of elements repeated, extract them: | Pattern | Extract to component | | ----------------------------- | -------------------- | | Date + category/tag | `article-meta` | | Cover image + download button | `resource-cover` | | Label + value pairs | `metadata-item` | | Icon + text link | `icon-link` | ### Use layout components instead of built-in layouts Don't build two-column or grid layouts into content components. Use layout components like `grid-container` and compose content into them: ```jsx // Wrong const ResourceDetail = ({ leftContent, rightContent }) => (
{leftContent}
{rightContent}
); // Correct
} />; ``` ## When NOT to decompose Keep components together when: - **They always appear together** and never make sense separately - **They share significant internal state** that would be awkward to lift up - **The visual design tightly couples them** (e.g., overlapping elements, shared backgrounds) - **Decomposition would create components with only 1-2 props** that aren't useful elsewhere