Deep Dive: Accessibility by Default

Why Accessibility Belongs in Tokens

Accessibility is often treated as an afterthought—something to audit and fix before launch. But when accessibility decisions are encoded at the token level, they become defaults that every component inherits automatically. Teams don't have to remember to check contrast ratios or respect motion preferences; the system handles it.

This isn't just about compliance. It's about building inclusive experiences from the foundation up. When a designer picks semantic.color.foreground.primary, they're not just getting a color—they're getting a color that's been vetted for contrast against its intended background.

Contrast-Aware Color Tokens

The most common accessibility failure is insufficient color contrast. WCAG 2.1 requires a minimum contrast ratio of 4.5:1 for normal text and 3:1 for large text. Our token system encodes these requirements directly into the semantic layer.

Foreground/Background Pairings

Semantic color tokens are designed as pairs. Each foreground token has a corresponding background it's designed to work with:

Foreground TokenDesigned ForMin Contrast
foreground.primarybackground.primary7:1 (AAA)
foreground.secondarybackground.primary4.5:1 (AA)
foreground.tertiarybackground.primary4.5:1 (AA)
foreground.inversebackground.brand4.5:1 (AA)
status.dangerbackground.primary4.5:1 (AA)

Mode-Aware Contrast

Light and dark modes require different color choices to maintain contrast. The $extensions.design.paths system handles this automatically:

{
  "foreground": {
    "secondary": {
      "$type": "color",
      "$value": "{core.color.palette.neutral.600}",
      "$extensions": {
        "design.paths.light": "{core.color.palette.neutral.600}",
        "design.paths.dark": "{core.color.palette.neutral.300}"
      },
      "$description": "Secondary text - 4.5:1 contrast in both modes"
    }
  }
}

In light mode, neutral.600 (#555555) against white provides 7.5:1 contrast. In dark mode, neutral.300 (#b0b0b0) against near-black provides 8.6:1 contrast. Both exceed the 4.5:1 AA requirement.

Status Colors and Accessibility

Status colors (info, success, warning, danger) must be accessible while remaining visually distinct. This often means using darker shades than designers initially expect:

// BAD: Vibrant but inaccessible
"status": {
  "success": { "$value": "#00ff00" }  // 1.4:1 contrast on white
}

// GOOD: Accessible and still recognizable
"status": {
  "success": { "$value": "{core.color.palette.green.700}" }  // 4.5:1 contrast
}

Motion and Animation Tokens

Some users experience vestibular disorders, motion sickness, or cognitive difficulties with animations. The prefers-reduced-motion media query lets users request minimal motion, and our tokens respect this preference.

Duration Tokens with Reduced Motion

Motion duration tokens include reduced-motion variants that dramatically shorten or eliminate animations:

{
  "motion": {
    "duration": {
      "short": {
        "$type": "duration",
        "$value": "150ms",
        "$extensions": {
          "design.paths.default": "150ms",
          "design.paths.reduced": "0ms"
        },
        "$description": "Quick transitions, instant when reduced motion"
      },
      "medium": {
        "$type": "duration",
        "$value": "250ms",
        "$extensions": {
          "design.paths.default": "250ms",
          "design.paths.reduced": "50ms"
        },
        "$description": "Standard transitions, minimal when reduced motion"
      },
      "long": {
        "$type": "duration",
        "$value": "400ms",
        "$extensions": {
          "design.paths.default": "400ms",
          "design.paths.reduced": "100ms"
        },
        "$description": "Elaborate transitions, shortened when reduced motion"
      }
    }
  }
}

CSS Implementation

The generated CSS respects the user's motion preference:

/* Generated CSS */
:root {
  --motion-duration-short: 150ms;
  --motion-duration-medium: 250ms;
  --motion-duration-long: 400ms;
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --motion-duration-short: 0ms;
    --motion-duration-medium: 50ms;
    --motion-duration-long: 100ms;
  }
}

Easing Tokens

Easing curves also adapt for reduced motion. Aggressive easings become linear to reduce perceived motion:

{
  "easing": {
    "bounce": {
      "$type": "cubicBezier",
      "$value": [0.68, -0.55, 0.265, 1.55],
      "$extensions": {
        "design.paths.default": [0.68, -0.55, 0.265, 1.55],
        "design.paths.reduced": [0, 0, 1, 1]
      },
      "$description": "Bouncy easing, linear when reduced motion"
    }
  }
}

Minimum Target Sizes

WCAG 2.2 requires interactive targets to be at least 24x24 CSS pixels, with a recommendation of 44x44 for touch interfaces. Our spacing and dimension tokens encode these minimums.

Touch Target Tokens

{
  "interaction": {
    "target": {
      "minimum": {
        "$type": "dimension",
        "$value": "24px",
        "$description": "WCAG 2.2 minimum target size"
      },
      "comfortable": {
        "$type": "dimension",
        "$value": "44px",
        "$description": "Recommended touch target size"
      },
      "large": {
        "$type": "dimension",
        "$value": "48px",
        "$description": "Large touch target for primary actions"
      }
    }
  }
}

Component Application

Components use these tokens to ensure interactive elements meet minimum size requirements:

// Button component tokens
{
  "button": {
    "minHeight": {
      "$type": "dimension",
      "$value": "{semantic.interaction.target.comfortable}",
      "$description": "Ensures buttons meet touch target requirements"
    },
    "paddingY": {
      "$type": "dimension",
      "$value": "{core.spacing.size.03}",
      "$description": "Vertical padding contributing to target size"
    }
  }
}

Focus Indicators

Keyboard users rely on visible focus indicators to navigate interfaces. Our tokens define consistent, accessible focus styles that work across all components.

Focus Ring Tokens

{
  "focus": {
    "ring": {
      "width": {
        "$type": "dimension",
        "$value": "2px",
        "$description": "Focus ring thickness - visible at all sizes"
      },
      "offset": {
        "$type": "dimension",
        "$value": "2px",
        "$description": "Gap between element and focus ring"
      },
      "color": {
        "$type": "color",
        "$value": "{core.color.palette.blue.500}",
        "$extensions": {
          "design.paths.light": "{core.color.palette.blue.500}",
          "design.paths.dark": "{core.color.palette.blue.400}"
        },
        "$description": "Focus ring color - high contrast in both modes"
      }
    },
    "outline": {
      "style": {
        "$type": "string",
        "$value": "solid",
        "$description": "Focus outline style"
      }
    }
  }
}

CSS Focus Styles

The generated CSS creates consistent focus indicators across all interactive elements:

/* Generated focus styles */
:focus-visible {
  outline: var(--focus-ring-width) var(--focus-outline-style) var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* High contrast mode enhancement */
@media (forced-colors: active) {
  :focus-visible {
    outline: 3px solid CanvasText;
  }
}

Typography and Readability

Readable text is accessible text. Our typography tokens encode line-height, letter-spacing, and size relationships that ensure comfortable reading.

Line Height for Readability

WCAG recommends line-height of at least 1.5 for body text. Our tokens enforce this:

{
  "typography": {
    "lineHeight": {
      "tight": {
        "$type": "number",
        "$value": 1.25,
        "$description": "For headings only - not body text"
      },
      "normal": {
        "$type": "number",
        "$value": 1.5,
        "$description": "WCAG-compliant body text line height"
      },
      "relaxed": {
        "$type": "number",
        "$value": 1.75,
        "$description": "Enhanced readability for long-form content"
      }
    }
  }
}

Minimum Font Sizes

While users can zoom, we set sensible minimums to ensure text is readable at default zoom:

{
  "typography": {
    "size": {
      "minimum": {
        "$type": "dimension",
        "$value": "12px",
        "$description": "Absolute minimum - use sparingly (captions, labels)"
      },
      "body": {
        "$type": "dimension",
        "$value": "16px",
        "$description": "Default body text - comfortable reading size"
      },
      "large": {
        "$type": "dimension",
        "$value": "18px",
        "$description": "Large body text for enhanced readability"
      }
    }
  }
}

Pitfalls to Avoid

1. Relying on Color Alone

Color should never be the only indicator of state or meaning. Always pair color with text, icons, or patterns.

// BAD: Color-only error indication
.input--error {
  border-color: var(--status-danger);
}

// GOOD: Color + icon + text
.input--error {
  border-color: var(--status-danger);
}
.input__error-icon {
  /* Visible error icon */
}
.input__error-message {
  color: var(--status-danger);
  /* "This field is required" */
}

2. Disabling Focus Indicators

Never remove focus indicators without providing an alternative. It's tempting to hide the “ugly” focus ring, but this makes the interface unusable for keyboard users.

/* BAD: Removing focus indicators */
:focus {
  outline: none;
}

/* GOOD: Custom focus indicator */
:focus {
  outline: none;
}
:focus-visible {
  box-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring-color);
}

3. Ignoring Reduced Motion

Animations that ignore prefers-reduced-motion can cause physical discomfort for some users. Always provide reduced-motion alternatives.

/* BAD: Animation ignores preference */
.modal {
  animation: slideIn 300ms ease-out;
}

/* GOOD: Respects reduced motion */
.modal {
  animation: slideIn var(--motion-duration-medium) var(--motion-easing-standard);
}

@media (prefers-reduced-motion: reduce) {
  .modal {
    animation: fadeIn var(--motion-duration-short) linear;
  }
}

4. Assuming Contrast is Enough

Meeting contrast ratios is necessary but not sufficient. Text must also be appropriately sized, spaced, and styled for readability.

/* BAD: Meets contrast but hard to read */
.caption {
  color: var(--foreground-secondary); /* 4.5:1 contrast */
  font-size: 10px;
  line-height: 1.1;
  letter-spacing: -0.5px;
}

/* GOOD: Accessible and readable */
.caption {
  color: var(--foreground-secondary);
  font-size: var(--typography-size-minimum); /* 12px */
  line-height: var(--typography-lineHeight-normal); /* 1.5 */
  letter-spacing: var(--typography-letterSpacing-normal);
}

Testing Accessibility Tokens

Accessibility decisions encoded in tokens should be validated automatically:

Contrast Validation

// Token validation script
function validateContrast(foreground, background, minRatio = 4.5) {
  const ratio = calculateContrastRatio(foreground, background);
  if (ratio < minRatio) {
    throw new Error(
      `Contrast ratio ${ratio.toFixed(2)}:1 is below minimum ${minRatio}:1`
    );
  }
}

// Run during build
validateContrast(
  tokens.semantic.color.foreground.primary,
  tokens.semantic.color.background.primary,
  7 // AAA requirement
);

Target Size Validation

// Ensure interactive elements meet minimum size
function validateTargetSize(token, minSize = 24) {
  const value = parseFloat(token.$value);
  if (value < minSize) {
    throw new Error(
      `Target size ${value}px is below WCAG minimum ${minSize}px`
    );
  }
}

Summary

  • Contrast-aware colors — Foreground/background pairings meet WCAG AA (4.5:1) or AAA (7:1) requirements
  • Motion tokens — Duration and easing adapt for prefers-reduced-motion
  • Target sizes — Interactive elements meet 24x24px minimum, 44x44px recommended
  • Focus indicators — Consistent, visible focus rings across all components
  • Typography — Line-height, sizing, and spacing for comfortable reading
  • Never rely on color alone — Always pair with text, icons, or patterns

By encoding accessibility decisions at the token level, we shift from “audit and fix” to “accessible by default.” Teams can focus on building features, confident that the foundation supports all users.

Next Steps

Accessibility tokens work alongside the broader token architecture. Review the core vs semantic model to understand how these decisions are structured, and explore multi-brand theming to see how accessibility is maintained across different visual identities.

← Back to Design Tokens