git clone https://github.com/NeverSight/learn-skills.dev
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/acquia/nebula/canvas-styling-conventions" ~/.claude/skills/neversight-learn-skills-dev-canvas-styling-conventions && rm -rf "$T"
data/skills-md/acquia/nebula/canvas-styling-conventions/SKILL.mdTechnology stack
| Technology | Purpose |
|---|---|
| Tailwind CSS 4.1+ | Styling |
| class-variance-authority (CVA) | Component variants |
+ via | 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-*
) defined ingray-*
.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:
import { cn } from "@/lib/utils"; // or import { cn } from "drupal-canvas";
Example usage:
const Button = ({ variant, className, children }) => ( <button className={cn( "rounded px-4 py-2", variant === "primary" && "bg-primary-600 text-white", variant === "secondary" && "bg-gray-200 text-gray-800", className, )} > {children} </button> );
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.
const Card = ({ colorScheme, className, children }) => ( <div className={cn(cardVariants({ colorScheme }), className)}>{children}</div> );
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
for available design tokens. The global.css
@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 | Generated Utility Classes |
|---|---|
| , , |
| , , |
| |
| responsive prefix |
The pattern is:
--{namespace}-{name} becomes {utility}-{name}.
Examples
Given this definition in
global.css:
@theme { --color-primary-600: #1899cb; --color-primary-700: #1487b4; }
You can use these colors with any color-accepting utility:
// Correct <button className="bg-primary-600 hover:bg-primary-700 text-white"> Click me </button> // Wrong <button className="bg-[#1899cb] text-white hover:bg-[#1487b4]">Click me</button>
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:
@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:
@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
for available design tokens. The tokens defined
there (such as global.css
primary-*, gray-*, etc.) are the source of truth for color
values.
Wrong - allowing raw color values:
# Wrong props: properties: backgroundColor: title: Background Color type: string examples: - "#3b82f6"
// Wrong const Card = ({ backgroundColor }) => ( <div style={{ backgroundColor }}>{/* ... */}</div> );
Correct - using CVA variants with design tokens:
# Correct props: properties: colorScheme: title: Color Scheme type: string enum: - default - primary - muted - dark meta:enum: default: Default (White) primary: Primary (Blue) muted: Muted (Light Gray) dark: Dark examples: - default
// Correct import { cva } from "class-variance-authority"; const cardVariants = cva("rounded-lg p-6", { variants: { colorScheme: { default: "bg-white text-black", primary: "bg-primary-600 text-white", muted: "bg-gray-100 text-gray-700", dark: "bg-gray-900 text-white", }, }, defaultVariants: { colorScheme: "default", }, }); const Card = ({ colorScheme, children }) => ( <div className={cardVariants({ colorScheme })}>{children}</div> );
This approach ensures:
- Consistent colors across the design system
- Users select from curated, meaningful options (not arbitrary values)
- Easy theme updates by modifying
tokensglobal.css - Better accessibility through tested color combinations