This guide walks you through building a WCAG 2.2-compliant accessible color system in Figma from scratch using Figma Variables, semantic tokens, and contrast-checking tools. Expect to spend roughly two to three hours completing the full setup, after which your design system will automatically surface accessibility violations before a single line of code is written.
What You'll Build
- A structured primitive color palette using Figma Variables with mode support (light and dark)
- A semantic token layer that maps primitives to purpose-driven roles (text, background, border, interactive)
- A contrast-checking workflow using the Able plugin and the WCAG 2.2 AA and AAA thresholds
- A reusable color swatch documentation frame that any developer or designer can reference at handoff
- A dark mode variant that inherits all semantic tokens without duplicating design work
Prerequisites
- Figma Professional or Organisation plan (required for Variables with multiple modes, as of 2026)
- The Able plugin installed from the Figma Community (free)
- The Tokens Studio for Figma plugin installed (free tier is sufficient)
- Basic familiarity with Figma components and styles
- A brand palette — even a rough one with two or three hex values is enough to start
Step 1: Establish Your Primitive Palette
Primitive colors are raw values with no semantic meaning. They form the foundation everything else references. Think of them as your paint supply, not your instructions for using the paint.
How many primitives do you actually need?
A practical primitive palette contains five to seven hue families, each with ten tonal steps (50 through 950). That gives you 50 to 70 named values total. For most SMBs — whether you're building a SaaS product in Toronto or a retail app in Sydney — this range covers every surface, state, and data-visualisation need without becoming unmanageable.
Open Figma and create a new page called 🎨 Color Foundation. Use the Tokens Studio plugin to define your first hue family. Here is an example JSON structure you can paste directly into Tokens Studio:
{
"color": {
"brand": {
"50": { "value": "#F0F4FF", "type": "color" },
"100": { "value": "#D9E4FF", "type": "color" },
"200": { "value": "#B3C9FF", "type": "color" },
"300": { "value": "#80A3FF", "type": "color" },
"400": { "value": "#4D7EFF", "type": "color" },
"500": { "value": "#1A59FF", "type": "color" },
"600": { "value": "#1447CC", "type": "color" },
"700": { "value": "#0F3599", "type": "color" },
"800": { "value": "#0A2466", "type": "color" },
"900": { "value": "#051233", "type": "color" },
"950": { "value": "#02091A", "type": "color" }
}
}
}
Repeat this structure for neutral, success, warning, error, and any secondary brand hues. Keep primitives purely numeric — no names like "sky" or "coral" at this stage.
Common pitfall: Many designers skip the 950 step and regret it when building dark mode surfaces that need near-black backgrounds with enough separation to distinguish elevation layers.
Step 2: Create Semantic Tokens in Figma Variables
Semantic tokens give your primitives meaning. Instead of applying brand.500 directly to a button, you apply interactive.primary.default — which references brand.500 underneath. This separation is what makes dark mode and theming possible without touching every component.
How do you structure semantic token names?
Use a three-part naming convention: {role}/{element}/{state}. Examples:
text/primary/defaulttext/primary/disabledsurface/elevated/defaultinteractive/primary/hoverborder/subtle/default
In Figma, open the Variables panel (Local Variables from the right panel). Create a new collection called Semantic / Light. Add a mode called Dark. Now create your first variable:
- Click the + icon and name it
text/primary/default - Set the type to Color
- In Light mode, reference
neutral.900from your primitives - In Dark mode, reference
neutral.50
This single variable now drives correct text colour across both modes with zero duplication. Repeat for every semantic role. A complete system typically contains 40 to 60 semantic variables — manageable in a focused afternoon session.
Pro tip: Group your variables using the slash-naming convention Figma recognises. text/primary/default will automatically nest under text → primary in the Variables panel, keeping things scannable as the system grows.
Step 3: Run WCAG 2.2 Contrast Checks with Able
WCAG 2.2 (published October 2023) remains the active standard as of mid-2026. The AA threshold for normal text is a contrast ratio of 4.5:1. For large text (18pt regular or 14pt bold) it drops to 3:1. AAA targets 7:1 for normal text. Australian and Canadian accessibility regulations both reference WCAG 2.2 AA as the minimum compliance bar.
What is the fastest way to check every semantic pair?
Build a dedicated contrast matrix frame. Place a text layer using text/primary/default on a rectangle using surface/default. Select both layers and open the Able plugin. Able will display the contrast ratio instantly and flag any failures with a red indicator.
Work through every foreground–background combination that will appear in your product:
- Body text on default surface (must pass AA: 4.5:1)
- Placeholder text on input background (common failure — designers often use neutral.400 on white)
- Button label on interactive primary (must pass AA against the button fill)
- Disabled text on any surface (WCAG 2.2 exempts disabled elements, but document your decision)
- Link text inline with body copy (must be distinguishable from surrounding text beyond colour alone)
Common pitfall: Checking contrast in light mode only. Switch Figma to dark mode variable values and re-run every check. Dark mode often introduces new failures, particularly with mid-range semantic surfaces like cards and modals.
Step 4: Build the Documentation Frame
A color system is only useful if the team can reference it confidently. Build a single documentation frame that any developer can read at handoff — this becomes your single source of truth.
What should the documentation frame include?
Structure the frame into three sections:
- Primitive swatches: One row per hue family, showing all tonal steps with hex value, variable name, and relative luminance percentage labelled beneath each swatch.
- Semantic token table: Four columns — token name, light mode value (resolved hex), dark mode value (resolved hex), and WCAG contrast ratio against its default paired surface.
- Usage examples: Annotated component snippets showing correct and incorrect application, matching the do/don't format developers are familiar with from Material Design and Apple HIG documentation.
Use Figma's Inspect panel to verify resolved values display correctly to developers who don't have edit access. If you're working with a development team — or handing off to engineers via a tool like Zeroheight or Supernova — export the documentation frame as a PDF alongside your Tokens Studio JSON export for a complete package.
At Lenka Studio, we include this exact documentation structure in every design system project we deliver. It reduces developer questions about colour usage by roughly 60% compared to systems that only ship a style guide PDF without resolved token values.
Step 5: Add Dark Mode Without Duplicating Work
Because every component in your Figma file already uses semantic variables rather than primitives, enabling dark mode takes minutes rather than days.
- Select the top-level frame of any screen in your file
- In the right panel under Variables, switch the applied mode from
LighttoDark - Every semantic token resolves to its dark mode primitive automatically
If any component breaks visually, it means that component was using a hard-coded hex value rather than a semantic token. Open the Tokens Studio plugin, use the Token Resolver to find unlinked values, and replace them. Expect to catch five to fifteen hard-coded values in a typical existing file — far fewer if you built the system from scratch using this workflow.
Pro tip: After switching to dark mode, re-run the Able contrast checks across your semantic pairs. Aim to pass AA on all interactive and text tokens in both modes before calling the system complete.
Step 6: Export Tokens for Engineering Handoff
The final step connects your design work directly to the codebase. Tokens Studio can export your semantic token JSON in formats compatible with Style Dictionary, which transforms tokens into CSS custom properties, JavaScript/TypeScript constants, or Swift/Kotlin values for native apps.
# Install Style Dictionary globally (requires Node 20+)
npm install -g style-dictionary
# Run transformation from your project root
style-dictionary build --config sd.config.json
A minimal sd.config.json targeting CSS custom properties looks like this:
{
"source": ["tokens/**/*.json"],
"platforms": {
"css": {
"transformGroup": "css",
"prefix": "ls",
"buildPath": "dist/css/",
"files": [
{
"destination": "tokens.css",
"format": "css/variables\




