Appearance
Vision Filters
Color Vision Deficiency (CVD) simulation. Lets you check whether a theme still reads under the four common cone-deficiency types and full grayscale, before pushing variables to Figma.
Source of truth: src/lib/vision-filters.ts (registry, matrices, SVG defs, JS bake helper).
Modes
| Mode | Population | What's missing | Effect |
|---|---|---|---|
| Regular Vision | ~68% | — | Normal trichromatic vision, all three cone types working |
| Protanopia | ~1.5% | L-cones (red-blind) | Reds appear dark; red, orange, and green become hard to distinguish |
| Deuteranopia | ~1.2% | M-cones (green-blind) | Greens shift to beige; reds shift to dark yellow. Red/green confusion |
| Tritanopia | ~0.03% | S-cones (blue-blind) | Blue/green merge; yellow/pink merge. Very rare |
| Achromatopsia | <0.01% | All cones | Grayscale only — useful to verify contrast holds without colour |
Where to switch modes
The vision-mode picker is a Select labelled "Vision simulation" in the bottom status bar (StatusBar.tsx), not in the preview filter bar. It was moved out of the preview's right-hand column so the filter bar stays focused on what's currently being computed (entities, layers, swatch direction) and the global colour-vision lens reads as a viewing-mode toggle rather than another filter on top of the data. The select item labels carry the population percentage inline (e.g. "Protanopia (~1.5%)"); hovering an option live-previews that mode.
Scope (Settings → Editor Display → Vision simulation)
The active CVD filter applies to one of three scopes (a Preview / Editor / All segmented control). Note the scope segmented control and the mode select both carry the label "Vision simulation" — the scope picker is in Settings, the mode picker is in the status bar:
- Preview (default) — only preview swatches are simulated; the editor stays at true colour so you can edit raw hex values without the filter altering what you see in the picker
- Editor — only the editor (palette + entity rows) is simulated; preview shows true colour
- All — both the editor (active-tab content) and the preview panel are simulated; the status bar, window header, and chat panel stay at true colour
How it works
Two parallel implementations:
Live preview — a hidden SVG
<defs>block (injected viavisionFilterDefs()) registers<filter id="vision-protanopia">…<feColorMatrix>…</filter>for each non-regular mode. The active scope applies the filter as an inlinefilter: url(#vision-deuteranopia)CSS style on the relevant container: the preview canvas forpreview, the editor panel (via avisionFilterprop) foreditor, and both the editor (active-tab content) and the preview panel forall. Browser handles the matrix multiply on the GPU; zero per-frame cost. SVG defs are cached in_visionFilterDefsCacheso repeated mounts don't regenerate the string. Hovering an option in the "Vision simulation" select live-previews that mode through the same inline filter before you commit.Paste-to-canvas baking — Figma's
createNodeFromSvg()ignoresfeColorMatrix, so the preview's SVG export is run throughsimulateSvg(svg, mode)first. That pass walks everyfill="#hex"/stroke="#hex"attribute, applies the same 3×3 matrix in JS (simulateHex), and writes the resulting hex back. The pasted Figma frame matches what the preview showed pixel-for-pixel.
The matrices themselves are the standard Brettel/Vienot/Mollon coefficients — see MATRICES in vision-filters.ts for the exact rows.
Useful checks
- Protanopia + Deuteranopia together — the two red-green cases cover the bulk of CVD users (~2.7%). If a theme's status colours (success / warning / error) survive both, it's safe.
- Achromatopsia — verifies that contrast (not hue) is doing the work. If a button vanishes in grayscale, the theme is leaning on chromatic distinction alone and will fail for low-vision users too.
- Tritanopia — rarely the bottleneck, but worth checking if the brand leans heavily on blue/green pairings.
Related
- Settings → Display → Vision simulation — pick the scope (Preview / Editor / All)
- Preview — the preview panel; vision filter is applied to swatches when scope =
PrevieworAll - Contrast Model — WCAG vs APCA, which is the right yardstick for the residual colour-blind contrast you're testing