---
name: canvas-styling-conventions
description:
Style Canvas components with Tailwind CSS 4 theme tokens and approved utility
patterns. Use when (1) Creating a new component, (2) Adding colors or styling
to components, (3) Working with Tailwind theme tokens, (4) Adding or updating
design tokens in global.css, (5) Creating color/style props, (6) Any change
where component props are added/removed/renamed/retyped and can affect
rendered styles. Covers @theme variables, CVA variants, and cn() utility.
---
## Technology stack
| Technology | Purpose |
| ------------------------------------ | ------------------ |
| Tailwind CSS 4.1+ | Styling |
| class-variance-authority (CVA) | Component variants |
| `clsx` + `tailwind-merge` via `cn()` | Class name merging |
Only use these dependencies for styling. Do not add third-party CSS libraries or
create new styling utilities.
## Styling conventions
- Use Tailwind's theme colors (`primary-*`, `gray-*`) defined in `global.css`.
- Avoid hardcoded color values; use theme tokens instead.
- Follow the existing focus, hover, and active state patterns from examples.
## The cn() utility
Use `cn()` to merge Tailwind classes. It combines `clsx` for conditional classes
with `tailwind-merge` to resolve conflicting utilities. Import from either
source:
```jsx
import { cn } from "@/lib/utils";
// or
import { cn } from "drupal-canvas";
```
Example usage:
```jsx
const Button = ({ variant, className, children }) => (
);
```
## Accept className for style customization
Every component should accept a `className` prop to allow style overrides. Pass
it to `cn()` as the last argument so consumer classes take precedence.
```jsx
const Card = ({ colorScheme, className, children }) => (
{children}
);
```
`className` is an implementation/composition prop, not an editor prop. Do not
add `className` to `component.yml`, do not mark it as required, and do not
surface it in Canvas metadata.
## Tailwind 4 theme variables
Canvas projects use Tailwind CSS 4's `@theme` directive to define design tokens
in `global.css`. Variables defined inside `@theme { }` automatically become
available as Tailwind utility classes.
**Always check `global.css` for available design tokens.** The `@theme` block is
the source of truth for colors, fonts, breakpoints, and other design tokens.
### How theme variables map to utility classes
When you define a CSS variable in `@theme`, Tailwind 4 automatically generates
corresponding utility classes based on the variable's namespace prefix:
| CSS Variable in `@theme` | Generated Utility Classes |
| --------------------------- | ---------------------------------------------------------- |
| `--color-primary-600: #xxx` | `bg-primary-600`, `text-primary-600`, `border-primary-600` |
| `--color-gray-100: #xxx` | `bg-gray-100`, `text-gray-100`, `border-gray-100` |
| `--font-sans: ...` | `font-sans` |
| `--breakpoint-md: 48rem` | `md:` responsive prefix |
The pattern is: `--{namespace}-{name}` becomes `{utility}-{name}`.
### Examples
Given this definition in `global.css`:
```css
@theme {
--color-primary-600: #1899cb;
--color-primary-700: #1487b4;
}
```
You can use these colors with any color-accepting utility:
```jsx
// Correct
// Wrong
```
Arbitrary values (e.g., `bg-[#xxx]`) are acceptable for rare, one-off cases
where adding a theme variable would be overkill. However, if a color appears in
multiple places or represents a brand/design system value, add it to `@theme`
instead.
### Semantic aliases
Theme variables can reference other variables to create semantic aliases:
```css
@theme {
--color-primary-700: #1487b4;
--color-primary-dark: var(--color-primary-700);
}
```
Both `bg-primary-700` and `bg-primary-dark` will work. Use semantic aliases when
they better express intent (e.g., `primary-dark` for a darker brand variant).
### Adding or updating theme variables
When a design requires a color, font, or other value not yet defined in the
theme, add it to the `@theme` block in `global.css` rather than hardcoding the
value in a component.
**When to add new theme variables:**
- A design introduces a new brand color or shade
- You need a semantic alias for an existing value (e.g., `--color-accent`)
- The design uses a specific spacing, font, or breakpoint value repeatedly
**When to update existing theme variables:**
- The brand colors change (update the hex values)
- Design tokens need adjustment across the system
**Example - adding a new color:**
```css
@theme {
/* Existing tokens */
--color-primary-600: #1899cb;
/* New token for a success state */
--color-success: #22c55e;
--color-success-dark: #16a34a;
}
```
After adding, you can immediately use `bg-success`, `text-success-dark`, etc.
**Keep the theme organized.** Group related tokens together with comments
explaining their purpose. Follow the existing naming conventions in `global.css`
(e.g., numbered shades like `primary-100` through `primary-900`, semantic names
like `primary-dark`).
## Color props must use variants, not color codes
**Never create props that allow users to pass color codes** (hex values, RGB,
HSL, or any raw color strings). Instead, define a small set of human-readable
variants using CVA that map to the design tokens in `global.css`.
**Always check `global.css` for available design tokens.** The tokens defined
there (such as `primary-*`, `gray-*`, etc.) are the source of truth for color
values.
**Wrong - allowing raw color values:**
```yaml
# Wrong
props:
properties:
backgroundColor:
title: Background Color
type: string
examples:
- "#3b82f6"
```
```jsx
// Wrong
const Card = ({ backgroundColor }) => (
);
```
This approach ensures:
- Consistent colors across the design system
- Users select from curated, meaningful options (not arbitrary values)
- Easy theme updates by modifying `global.css` tokens
- Better accessibility through tested color combinations