Skip to content

Color & Contrast Guidance

Airframe ships with a default color palette that meets WCAG 2.2 AA contrast requirements. When overriding tokens, you must ensure contrast ratios are maintained.

  • Normal text (smaller than 18pt/24px): 4.5:1 minimum
  • Large text (18pt/24px or larger, or 14pt/19px bold): 3:1 minimum
  • UI components (icons, borders): 3:1 minimum
  • Focus indicators: Must have 3:1 contrast with adjacent colors

Airframe uses semantic color tokens:

/* Semantic tokens (contrast-safe by default) */
--af-color-text          /* Meets 4.5:1 on --af-color-background */
--af-color-text-muted     /* Meets 3:1 on --af-color-background */
--af-color-background    /* Base background */
--af-color-surface-primary       /* Primary surface background */
--af-color-border        /* Border color */
--af-color-primary        /* Primary action color */
--af-color-on-primary    /* Text on primary (meets contrast) */

✅ Safe: Override with Contrast-Checked Colors

Section titled “✅ Safe: Override with Contrast-Checked Colors”
:root {
  /* ✅ Good - verify contrast before using */
  --af-base-primary: #018183; /* Checked: 4.5:1 on white */
  --af-color-primary: var(--af-base-primary);
}
:root {
  /* ❌ Bad - may not meet contrast */
  --af-color-text: #CCCCCC; /* Too light on white */
}
  1. Visit our Accessibility Contrast Checker
  2. Enter your foreground and background colors
  3. Verify ratio meets requirements:
    • Normal text: 4.5:1 or higher
    • Large text: 3:1 or higher

Chrome DevTools:

  1. Inspect element
  2. Click color swatch in Styles panel
  3. Contrast ratio shown automatically

Firefox DevTools:

  1. Inspect element
  2. Click color in Computed panel
  3. Contrast ratio shown in color picker

Hover states must not reduce contrast below requirements:

/* ✅ Good - hover maintains or improves contrast */
.af-btn:hover {
  background: color-mix(in srgb, var(--af-color-primary), black 10%);
}

/_ ❌ Bad - hover reduces contrast _/
.af-btn:hover {
  background: color-mix(in srgb, var(--af-color-primary), white 30%);
  /_ May reduce contrast below 4.5:1 _/
}

Never rely on color alone to convey information:

<!-- ❌ Bad - error only shown by color -->
<input style="border-color: red;">
<!-- ✅ Good - error shown multiple ways -->
<input
aria-invalid="true"
aria-describedby="error"
class="af-input af-is-error"
>
<div id="error">
  Error message
</div>

Required indicators:

<!-- ❌ Bad - required only shown by red asterisk -->
<label for="name">
  Name
  <span style="color: red;">
    *
  </span>
</label>
<input id="name" class="af-input" type="text" required>
<!-- ✅ Good - required shown with text -->
<label for="name">
  Name
  <span class="af-text-muted">
    (required)
  </span>
</label>
<input id="name" class="af-input" type="text" required>

Focus rings must have sufficient contrast:

/* ✅ Good - focus ring has 3:1 contrast */
:focus-visible {
  outline: 2px solid var(--af-color-primary);
  outline-offset: 2px;
}

/_ ❌ Bad - focus ring too subtle _/
:focus-visible {
  outline: 1px solid rgba(0, 0, 0, 0.2);
  /_ May not meet 3:1 contrast _/
}

Airframe provides focus ring tokens that meet contrast:

--af-focus-ring-width: 2px;
--af-focus-ring-color: /* Meets 3:1 contrast */
--af-focus-ring-offset: 2px;

When implementing dark mode, verify contrast for both themes:

/* Light theme */
[data-theme="light"] {
  --af-color-text: #1f2937;
  --af-color-background: #ffffff;
  /* 4.5:1 contrast ✅ */
}

/_ Dark theme _/
[data-theme="dark"],
.dark {
  --af-color-text: #f9fafb;
  --af-color-background: #111827;
  /_ 4.5:1 contrast ✅ _/
}

Use semantic tokens instead of raw colors:

/* ✅ Good - uses semantic token */
.error-message {
  color: var(--af-color-danger);
}

/_ ❌ Bad - uses raw color _/
.error-message {
  color: #dc2626;
  /_ What if theme changes? _/
}
  • axe DevTools - Checks contrast automatically
  • Lighthouse - Includes contrast audit
  • WAVE - Web accessibility evaluation tool
  1. Use contrast checker - Verify all text meets requirements
  2. Test hover states - Ensure contrast doesn’t drop
  3. Test focus indicators - Verify 3:1 contrast
  4. Test in dark mode - If you support it
  5. Test with color blindness simulators - Ensure information isn’t color-only
/* ❌ Bad - doesn't meet 4.5:1 */
--af-color-text: #CCCCCC;
/* ❌ Bad - hover makes text harder to read */
.button:hover {
  color: color-mix(in srgb, currentColor, white 40%);
}
<!-- ❌ Bad - error only by color -->
<input style="border-color: red;">
/* ❌ Bad - focus ring too subtle */
:focus-visible {
  outline: 1px solid rgba(0, 0, 0, 0.1);
}
  1. Always check contrast before overriding tokens
  2. Use semantic tokens instead of raw colors
  3. Test hover states to ensure contrast doesn’t drop
  4. Never rely on color alone to convey information
  5. Verify focus indicators have sufficient contrast
  6. Test in both light and dark modes if supported
  7. Use automated tools to catch contrast issues early