Modern React 35: Styling, design tokens, and variants
This is part 35 of the Modern React development series.
React does not require one styling system. It does require clear component contracts. Design tokens and variants help component styling stay predictable while letting callers choose supported visual states.
Concept
A design token is a named value for a design decision, such as color, spacing, radius, or font size. A variant is a supported component mode, such as primary, secondary, danger, small, or full width.
Terms
- Design token: A named design value used consistently across components.
- Variant: A named visual or behavioral mode supported by a component.
- Class contract: The class names a component applies to connect markup to CSS.
- Inline style: A JavaScript object passed to the
styleprop for dynamic CSS values.
Mental model
Think of tokens as the pantry and variants as menu items. Components should cook from named ingredients and offer supported dishes, not ask every caller to season from scratch.
How it is used
Use tokens for colors, spacing, typography, elevation, borders, and motion. Use variants for buttons, badges, alerts, cards, form fields, and reusable components where callers need controlled styling choices.
How to use it
- Define tokens in CSS variables, a theme object, or the project’s styling system.
- Give components semantic variant props instead of raw style props for common modes.
- Map variants to classes or token values inside the component.
- Use inline styles for truly dynamic values, such as a measured width or user-selected color.
- Keep semantic HTML and accessibility behavior independent of visual variants.
Example: Button variants
import type { ReactNode } from "react";
type ButtonVariant = "primary" | "secondary" | "danger";
type ButtonProps = { variant?: ButtonVariant; children: ReactNode; onClick?: () => void;};
const variantClass: Record<ButtonVariant, string> = { primary: "button button-primary", secondary: "button button-secondary", danger: "button button-danger",};
export function Button({ variant = "primary", children, onClick,}: ButtonProps) { return ( <button className={variantClass[variant]} onClick={onClick}> {children} </button> );}The caller chooses a supported mode. The component owns the class mapping.
Example: CSS tokens
:root { --color-action: #1463ff; --color-danger: #b42318; --space-2: 0.5rem; --radius-2: 0.375rem;}
.button { border-radius: var(--radius-2); padding: var(--space-2);}
.button-primary { background: var(--color-action); color: white;}Tokens keep repeated design values named in one place, which makes component classes easier to maintain.
Details to watch
- Variant limits: A variant prop should describe supported design choices, not expose every CSS property.
- Class composition: Class helpers are useful when multiple boolean and enum variants combine.
- Inline styles: React’s
styleprop uses camelCased CSS property names and JavaScript values. - Design drift: Unbounded styling props can make a design system impossible to keep consistent.
Series navigation
- Previous: Part 34: Code splitting and lazy loading
- Next: Part 36: Accessibility as component API design
- Series index: Modern React development