mcpField
C
canvas-styling-conventions

canvas-styling-conventions

github.com/drupal-canvasDesign & UI3216 installsUpdated 5w ago
Repository

About canvas-styling-conventions

A styling-conventions skill for developers working on Drupal Canvas projects with React and Tailwind CSS 4. It distinguishes Canvas Headless guidance from React-specific rules and covers theme tokens, class merging, and variants.

Key features

  1. 01Identifies Canvas Headless and React projects
  2. 02Uses Tailwind theme tokens
  3. 03Merges classes with cn()
  4. 04Defines color variants with CVA
  5. 05Avoids raw color values in component props

Use cases

  1. 01Style Canvas components consistently
  2. 02Move repeated colors into theme tokens
  3. 03Define controlled component color schemes

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/drupal-canvas/skills --skill canvas-styling-conventions

After installing, type/canvas-styling-conventionsin Claude Code to confirm it loaded.

Details

From the project README

Project type

Before applying this skill, check package.json for a dependency named @drupal-canvas/headless or starting with @drupal-canvas/headless-. If one is present, this is a Canvas Headless codebase — read canvas-headless first; it overrides the React-specific guidance below. The Tailwind 4 theme-token guidance in this skill applies to headless projects unchanged; the cn() and className sections are React-specific. If no such dependency is present, this is a Canvas-rendered React codebase: components are React (index.jsx/.tsx) and everything in this skill applies as written. These are the only two project types.

Technology stack

TechnologyPurpose
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.

In a Canvas Headless codebase, the drupal-canvas package (and its cn()) is not supported. CVA still works in any framework when the project installs it; for class merging, use the framework's own class-composition idiom — see canvas-headless.

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:

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

This rule is React-specific; in Vue and Astro components, rely on the framework's own class pass-through (attribute fallthrough in Vue, a class prop in Astro) when consumers need style overrides.

Every React 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 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 @themeGenerated Utility Classes
--color-primary-600: #xxxbg-primary-600, text-primary-600, border-primary-600
--color-gray-100: #xxxbg-gray-100, text-gray-100, border-gray-100
--font-sans: ...font-sans
--breakpoint-md: 48remmd: 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 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:

# 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 global.css tokens
  • Better accessibility through tested color combinations

Comments

Markdown supported