Code Component Format
When to Use
Use Code Components when you need browser-rendered React/Preact with interactive state, dynamic behavior, or Tailwind CSS styling without a separate Drupal theme build. Use SDC components when you need server-side Drupal field integration.
Decision
| Situation | Choose | Why |
|---|---|---|
| Interactive React state / hooks | Code Component | Full React/Preact state management |
| Tailwind-styled, no Drupal preprocess | Code Component | Tailwind 4 globally available; no build config needed |
| Server-side rendering + Drupal fields | SDC Component | Works with Drupal's render + caching system |
| Static text/image layout | SDC Component | Less overhead than React |
Pattern
File structure (local development with CLI):
my-components/
components/
hero-banner/
component.yml ← metadata, props, slots schema
index.jsx ← React/Preact component (default export required)
index.css ← optional: component styles (Tailwind utility classes)
component.yml for Code Components:
# component.yml (Code Component)
name: Hero Banner
description: 'Full-width hero section with headline, body, and CTA button.'
group: Marketing
status: stable
props:
type: object
properties:
headline:
type: string
title: Headline
body:
type: string
title: Body
ctaLabel:
type: string
title: 'CTA Button Label'
ctaUrl:
type: string
title: 'CTA URL'
slots:
badge:
title: Badge
Note: Code Component props use camelCase names (e.g., ctaLabel), unlike SDC props which use snake_case.
index.jsx — The React Component:
// index.jsx — MUST use default export, not named export
import { useState } from 'preact/hooks';
export default function HeroBanner({ headline, body, ctaLabel, ctaUrl, badge }) {
return (
<section className="relative min-h-64 bg-gradient-to-r from-blue-600 to-blue-800">
{badge && (
<div className="absolute top-4 left-4">{badge}</div>
)}
<div className="container mx-auto px-4 py-16 text-white">
{headline && <h1 className="text-4xl font-bold mb-4">{headline}</h1>}
{body && <p className="text-xl mb-8">{body}</p>}
{ctaLabel && ctaUrl && (
<a href={ctaUrl} className="btn bg-white text-blue-600 px-6 py-3 rounded-lg">
{ctaLabel}
</a>
)}
</div>
</section>
);
}
Critical rules:
1. Default export only — named exports are not allowed and will cause errors
2. Preact under the hood — Canvas uses Preact with the React compatibility layer; react, react-dom, react-dom/client are all mapped to preact/compat
3. Tailwind CSS 4 is available globally — Tailwind utility classes work without any build configuration
4. Props arrive as component parameters — same names as defined in component.yml
5. Slots arrive as React children — a slot named badge in YAML becomes a badge prop containing renderable content
Allowed package imports:
| Import | Notes |
|---|---|
preact |
Preact core — use instead of React |
preact/hooks |
React-equivalent hooks (useState, useEffect, etc.) |
preact/compat |
React compatibility layer (mapped from react) |
react |
Mapped to preact/compat |
react-dom |
Mapped to preact/compat |
drupal-canvas |
Canvas utilities and base components |
Third-party npm packages are NOT importable by default. Canvas issue #3500761 tracks expanding this.
Common Mistakes
- Wrong: Named exports (
export function MyComponent) → Right: Canvas requiresexport default - Wrong: Importing arbitrary npm packages (
import _ from 'lodash') → Right: Only allowed packages work; others silently fail or error - Wrong: Server-side expectations — Code Components render only in the browser → Right: No PHP/Drupal preprocess available
- Wrong: Forgetting that slots are renderable content, not strings → Right: Render
{badge}directly, not{badge.toString()}
See Also
- Canvas CLI for the local development workflow
- Canvas NPM Tools for full npm tooling context
- Canvas Code Component docs: https://project.pages.drupalcode.org/canvas/code-components/
- Packages: https://project.pages.drupalcode.org/canvas/code-components/packages/
- canvas-starter (Balint Kleri's preconfigured dev environment): https://github.com/balintbrews/canvas-starter