// preview in
saved for every page

Accessibility

What every Camaleo component guarantees in every skin, how it is tested, and the measured contrast of each skin's colors.

// you'll learn

  • What you get from every component without extra work
  • How the library is tested in all 13 skins, in light and dark, with five hard brand colors
  • The measured contrast of each skin's text and control colors
  • Where an authentic look and accessibility disagree, and which one wins

The target is WCAG 2.2 AA in every skin, in light and dark, with any brand color. A skin can change shape, depth, type and motion; it can't take away a name, a key or a focus ring.

What every component guarantees

How it is tested

The tests live in packages/react/test and run in a real browser (Vitest browser mode with Playwright).

Contrast report

The table below is measured, not typed: apps/docs/scripts/contrast-report.mjs loads tokens.css and each skin's CSS in a browser, resolves the tokens with each of the five matrix brand colors, and keeps the lowest ratio of the five. Numbers are rounded down, so 4.499 shows as 4.49.

What it measures, all against the skin's page color --cml-surface unless noted:

It doesn't measure text on raised surfaces, hover states or glass panels over the backdrop; the matrix covers those on rendered components.

Text on the page color, target 4.5:1
SkinModefgfg-secondaryfg-mutedaccent-textdangersuccesswarning
minimallight19.897.724.836.936.575.016.85
dark19.0613.467.769.947.1411.4112.99
neumorphismlight10.045.155.155.756.255.794.91
dark11.207.676.126.907.278.988.27
retro-pixellight17.098.368.366.636.476.226.10
dark16.1610.3410.349.205.929.5912.71
editoriallight16.318.106.416.216.765.725.66
dark14.8110.207.999.187.579.219.34
neubrutalismlight17.3510.726.586.376.044.616.29
dark16.2011.637.419.286.6710.6612.13
retrolight11.548.745.355.395.284.524.70
dark9.858.196.325.515.446.517.70
glasslight15.789.157.656.136.616.306.05
dark17.4212.858.629.538.5510.9512.46
swisslight21.0015.136.686.936.575.016.85
dark21.0015.318.3210.497.5312.0513.71
progressivelight15.628.465.206.195.875.506.11
dark15.6611.116.808.666.229.9511.32
claylight12.948.806.166.496.156.676.41
dark15.1611.467.118.576.8810.8911.51
materiallight16.148.826.066.596.126.196.12
dark14.4510.868.089.2910.9010.6310.86
cupertinolight18.819.805.366.214.825.376.14
dark21.0013.809.4910.498.6110.3814.87
y2klight14.659.055.955.635.345.795.56
dark16.7111.538.159.336.7010.7212.20
Brand fill (target 4.5:1) and edges (target 3:1)
SkinModeaccent-fgon accentborder-controlfocus-ring
minimallight4.853.246.93
dark7.454.119.94
neumorphismlight4.853.115.75
dark7.453.716.90
retro-pixellight4.8517.096.63
dark7.4516.169.20
editoriallight16.3116.316.21
dark14.8114.819.18
neubrutalismlight5.3517.356.37
dark5.3516.209.28
retrolight9.8010.885.39
dark4.854.215.51
glasslight4.854.206.13
dark7.455.889.53
swisslight4.8521.0021.00
dark7.4521.0021.00
progressivelight4.853.346.19
dark5.354.638.66
claylight4.853.666.49
dark7.454.788.57
materiallight4.854.256.59
dark10.495.839.29
cupertinolight4.853.724.35
dark4.856.445.35
y2klight4.853.585.63
dark5.355.029.33

Every pair meets its target in every skin and mode. Run node scripts/contrast-report.mjs in apps/docs after changing a skin to refresh the numbers.

Authentic or accessible

Some styles fail contrast by nature: neumorphism has no edges, glass puts text on whatever is behind it, a classic desktop OS draws a 1 px dotted focus box. The project decided (decision 9 in the plan) that every skin is accessible by default, and that an authentic variant would be opt-in and labelled "doesn't guarantee AA". No authentic variant ships yet.

SkinThe authentic lookWhat Camaleo does by default
neumorphismControls shaped by light and shade only, no edgesA 1 px border of 3:1 or more on inputs and choices; buttons are named by their text
glassTranslucent panels over any backgroundBackdrop blobs kept light (dark: dark) enough that text holds 4.8:1 at their peak; opaque under reduced transparency
claySoft pastel pieces with no outlineFields and choices keep an edge of 3:1 or more
cupertinoFrosted chrome on menus and popupsDark glass at 94% opacity so text keeps 4.5:1; opaque fallback
y2kGlossy "jelly" buttons with text over the shineText sits only on the flat accent; the gloss stays above and below it
retroDotted focus rectangleKept inside buttons, tabs and toggles (in the label color); fields get a solid 2 px ring outside the bevel
CAREFUL

A token or rule you change can break what the tests guarantee. After any color change, rerun the matrix (pnpm --filter @camaleo/react test) or at least this report.

Before you ship it

  • Icon-only buttons you add have an aria-label
  • Custom colors come from tokens, so they keep their tested pairs
  • Animations you add respect prefers-reduced-motion
  • New popups keep the skin (use PortalScope for your own portals)
  • The contrast report and the matrix still pass after a skin change
Was this page helpful? · Edit this page on GitHub