A complete worked example

From one brand color to light and dark website themes

Keep your original brand color. Give the interface permission to adapt. Those are separate decisions—and your exported tokens should keep them separate.

Reviewed 7 October 2026 · 4 min read

Start with an exact reference

Our starting color is #6750A4, used here as an illustrative purple rather than a claim about any particular company’s identity. Store that exact value as --color-brand. Switching a preview from light to dark should not repeatedly convert and overwrite it.

A brand swatch is a reference. An interface role is a color doing a job against a specific backdrop. The role may need to differ from the brand reference when the intended combination does not reach the contrast target.

Build two systems, not one inversion

Here are actual values generated by the studio from this starting color. The light and dark themes each retain their own roles; changing the preview does not recalculate them.

Default complementary direction · normal text target 4.5:1
RoleLight themeDark theme
background#FAFAFE#121114
surface#EFEDF6#222127
text#1D1B26#EBE9F8
muted#5B5965#B1AFBD
primary#6750A4#745DB2
primaryHover#573F91#7B65BB
onPrimary#FFFFFF#FFFFFF
link#6750A4#907BD3
border#7B7888#817E8E
focus#6750A4#745DB2

Notice that button fill and text link are separate roles. A button label is tested against its fill, while a link is tested against page and surface. They do not have to use identical hex values.

Check the components people will actually use

Calculated from the exact sRGB hex values above
Theme / pairRatioRequiredResult
light · Body on surface14.6439:14.5:1Pass
light · Link on surface5.5562:14.5:1Pass
light · Button label6.4412:14.5:1Pass
light · Hover label8.3164:14.5:1Pass
light · Input border on surface3.7087:13:1Pass
light · Focus ring on surface5.5562:13:1Pass
dark · Body on surface13.3425:14.5:1Pass
dark · Link on surface4.5326:14.5:1Pass
dark · Button label5.3148:14.5:1Pass
dark · Hover label4.7498:14.5:1Pass
dark · Input border on surface4.0372:13:1Pass
dark · Focus ring on surface3.0051:13:1Pass

The complete studio report checks 17 intended pairs in each theme. Normal-text checks use 4.5:1 by default, or 7:1 when you select the enhanced target. Meaningful boundaries and the offset focus ring use 3:1 against their adjacent page/surface. A passing report covers these modeled pairs, not every requirement of an accessible website.

Lock a deliberate choice before exploring

Lock the button fill if it must stay exact while you explore a new accent and background tint. “Build a palette” preserves locked roles and recomputes the other choices. A locked value that fails is reported as a failure; a lock is not an accessibility exemption.

Before changing direction, use “Save comparison.” Try another relationship or edit a role, then compare the two versions on the same website, dashboard or editorial preview. Restoring the saved version and undoing it are both explicit actions.

Review the tradeoff rather than accepting a magic fix

Change the text to match the page and inspect the failure. “Review contrast repairs” proposes a different foreground that works on both page and surface, while holding those backdrops. It lists the exact before/after values. Apply the changes, then use Undo to return to the failing example if you want to compare it.

Repair explores a defined set of OKLCH lightness candidates, maps them to sRGB, and evaluates the actual rounded hex values. When locks or conflicting surfaces prevent a solution, the report stays explicit about the remaining failures. Read the repair method and its limits.

Export the decision, not just the swatches

The CSS export includes an exact brand reference, independent light/dark role tokens, explicit data-theme overrides and a system-preference fallback. The Tailwind v4 export maps the same roles with @theme inline. Project JSON and share links restore both themes and locks. A separate contrast report records the checked foregrounds, backgrounds, requirements and results.

<html data-theme="dark">
/* Use the exported tokens in your own components. */
body { background: var(--color-background); color: var(--color-text); }
a { color: var(--color-link); text-decoration: underline; }
button { background: var(--color-primary); color: var(--color-on-primary); }
button:hover { background: var(--color-primary-hover); }
button:focus-visible { outline: 3px solid var(--color-focus); outline-offset: 4px; }

Audit the implemented component again if it adds opacity, overlays, images, a different adjacent background or additional states. Theme tokens make decisions reusable; they do not replace testing the real interface.

Sources: W3C text contrast, W3C non-text contrast, Oklab’s author, and Tailwind theme variables.

Try this exact starting point

Build both themes from #6750A4, inspect the pairs, and keep the original brand intact.

Open the worked example ↗

Our examples use original palette values and the WCAG 2.2 calculation. Full methodology and primary sources.

← All field notes