Skip to main content

N1-05 · Accessibility by design

Context

Checking contrast manually is tedious and error-prone. You pick a color, open the contrast checker, check the ratio, adjust, check again — for every combination of background and text.

In Aplica DS, that check is not needed for the vast majority of cases. The system calculated the contrast for you. This tutorial explains the one rule you need to follow to make it work.


Concept

The background + txtOn pair

Every color token that represents a surface comes with two companion tokens:

  • background — the surface's background color
  • txtOn — the text color that guarantees adequate contrast on that background
  • border — the border color derived from that surface

The system automatically calculated which text color passes WCAG AA level (minimum contrast ratio of 4.5:1) on that specific background. You do not need to check — the math has already been done.

The rule: Always use the txtOn from the same token as the background.

semantic.color.interface.function.primary.normal.background → button background
semantic.color.interface.function.primary.normal.txtOn → text on that background
↑ same chain

What never to do

Mixing tokens from different families:

❌ background from: interface.function.primary.normal
txtOn from: interface.function.secondary.normal
(contrast is not guaranteed — these two families were not calculated together)

✅ background from: interface.function.primary.normal
txtOn from: interface.function.primary.normal
(same token — contrast guaranteed by the system)

Placing Foundation text on top of a different Semantic background:

❌ background: semantic.color.brand.branding.first.default.background (brand color)
text: foundation.txt.body (calculated for neutral backgrounds)
(nobody guaranteed that body text has contrast on that brand background)

✅ background: semantic.color.brand.branding.first.default.background
text: semantic.color.brand.branding.first.default.txtOn
(system guaranteed contrast for this specific pair)

What about plain text tokens — semantic.color.text.*?

Plain text tokens (text.title, text.body, text.muted, text.label) were calculated for use on the system's neutral backgrounds — ambient.contrast.base and ambient.contrast.deep. They are safe on the interface's default canvas.

If you place text.body on top of a brand color background, contrast is not guaranteed. Use the txtOn for that brand background instead.


Guided example

Success alert — accessible assembly

A banner that reads "Settings saved successfully."

ElementCorrect tokenWhy
Banner backgroundfeedback.success.default.normal.backgroundSubtle success background
Main textfeedback.success.default.normal.txtOnContrast guaranteed on that background
Check iconfeedback.success.secondary.normal.backgroundSaturated green, visible
Left borderfeedback.success.secondary.normal.borderVisual border emphasis

Common mistakes that look fine but do not guarantee accessibility:

❌ WrongWhy it's not safe
Background success.default + text text.bodytext.body was calculated for neutral backgrounds, not green
Background success.default + text success.secondary.txtOnDifferent tokens — contrast not calculated together
Icon in hex #22C55EFixed value that won't change in dark mode

Disabled button — two correct patterns

When a control is disabled, you have two options:

Option A — Disabled token (preferred for individual buttons and controls):

background: interface.function.disabled.normal.background
txtOn: interface.function.disabled.normal.txtOn

Contrast between this background and this text is guaranteed — the user can read the disabled button label.

Option B — Opacity (for entire disabled sections): Reducing the opacity of a whole region via semantic.opacity.raw.translucid (50%). Works for large blocks, but should not be used on small text — readability may be compromised depending on the background.


Try it yourself

Given the component below, identify which color combinations are accessible and which are not:

Component: Product card with a promotion badge

ElementColor usedAccessible?
Card backgroundbrand.ambient.contrast.base.positive.background
Product titletext.title
Descriptiontext.body
"PROMO" badge — backgroundproduct.promo.default.default.background
"PROMO" badge — texttext.body
Highlighted pricebrand.branding.first.default.background (brand color used as colored text)

Expected result:

ElementAccessible?Fix
Background + titleYestext.title was calculated for neutral backgrounds
Background + descriptionYestext.body was calculated for neutral backgrounds
Badge background + text.bodyNoUse product.promo.default.default.txtOn
Price with brand color as textDependsUse semantic.color.text.promo (calculated for text use)

Checkpoint

By the end of this tutorial you should know:

  • What the background + txtOn pair is and why it exists
  • The rule: always use txtOn from the same token as the background
  • Why text.body is not safe on all backgrounds
  • When to use the disabled token vs opacity
  • How to identify color combinations that break the contrast contract

Next step

N1-06 · Dark mode without effort

You use the correct tokens, contrast is guaranteed. Now: what happens when the user activates dark mode? (Spoiler: nothing you need to do.)


References