Exporting a color palette to code
A palette that lives only in a design file will drift from the code. The cure is to treat colors as named values that both sides read. This guide shows three common targets: CSS custom properties, SwiftUI colors, and W3C design tokens, each with a dark-mode variant.
The samples use one small palette: an ink color, a paper color, and a brand purple, with a lighter purple for dark mode.
Checked against the W3C’s WCAG 2.2 Understanding documents on November 12, 2026.
CSS custom properties with a dark variant
Declare each color once on :root, then override the same names inside a prefers-color-scheme: dark media query. Components use the names and never need to know which mode is active.
:root {
--ink: #1B1B1F;
--paper: #FFFFFF;
--brand: #6D00FF;
}
@media (prefers-color-scheme: dark) {
:root {
--ink: #F2F2F5;
--paper: #121214;
--brand: #A678FF;
}
}
body {
color: var(--ink);
background: var(--paper);
}
a {
color: var(--brand);
}Contrast holds in both modes: ink on paper is 17.17:1 in light mode and 16.75:1 in dark, and the brand color on paper is 6.79:1 in light mode and 6.02:1 in dark, as a script confirmed.
SwiftUI colors that adapt
In SwiftUI, a Color extension gives every color a name, and a small initializer chooses between light and dark values by appearance. This sample type-checks as written.
import SwiftUI
extension Color {
static let brand = Color(
light: Color(red: 0.427, green: 0.000, blue: 1.000),
dark: Color(red: 0.651, green: 0.471, blue: 1.000)
)
static let paper = Color(
light: Color(red: 1.000, green: 1.000, blue: 1.000),
dark: Color(red: 0.071, green: 0.071, blue: 0.078)
)
init(light: Color, dark: Color) {
#if canImport(UIKit)
self.init(uiColor: UIColor { traits in
traits.userInterfaceStyle == .dark ? UIColor(dark) : UIColor(light)
})
#elseif canImport(AppKit)
self.init(nsColor: NSColor(name: nil) { appearance in
appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua ? NSColor(dark) : NSColor(light)
})
#else
self = light
#endif
}
}Each channel is the 8-bit value divided by 255, so #6D00FF becomes 109/255, 0, and 255/255, or 0.427, 0, and 1. Use the colors as Color.brand and Color.paper. If you manage colors in an asset catalog instead, Opalite’s Xcode asset catalog export produces the color sets.
Design tokens
Design tokens are a platform-neutral file that tools turn into CSS, Swift, Android resources, and more. The W3C Design Tokens Community Group format writes each color as a token with a type and a value:
{
"brand": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.427, 0, 1],
"hex": "#6d00ff"
}
}
}Dark-mode values need somewhere to live. Opalite writes them under $extensions.mode.dark, the field the format reserves for tool-specific data, so check that your token pipeline reads it.
How Opalite exports these
Opalite writes the same formats from a palette. Pair a second palette as the dark variant, and colors pair by position, so keep both palettes in the same order. Then choose a format:
- CSS code: custom properties on :root, with a prefers-color-scheme: dark block when a dark palette is paired. Each color is written as rgb(), plus -hex and -oklch companion properties, and a -p3 property for colors outside sRGB.
- SwiftUI code: a Color extension, with adaptive light and dark colors and a helper initializer when a dark palette is paired.
- Design tokens: Tailwind CSS, a W3C tokens.json, an Xcode asset catalog, Android resources, Flutter (Dart), Sass variables, plain JSON, and Figma variables. A paired dark palette becomes the dark value in each format. For Android it is added as _dark entries to copy into values-night.
- Other hand-offs: Adobe Swatch Exchange (ASE) for Adobe apps, GIMP palettes, and Procreate swatches that import directly into Procreate on iPad.
Exporting as an Opalite file or an image is free. The code, token, ASE, GIMP, and Procreate exports are part of Onyx, the one-time paid upgrade. Palettes sync across your Apple devices through your private iCloud.
Habits that keep code and design in step
- Name colors by role, such as brand and paper, not by appearance, so a dark-mode change does not make a name wrong.
- Keep one source palette and export from it. Do not edit hex values in the code by hand.
- Run a contrast check on the light and dark palettes before exporting.
- Commit the exported file so changes show up in review.
Quick answers
- How do I add a dark mode to CSS custom properties?
- Declare the colors on :root, then redeclare the same names inside @media (prefers-color-scheme: dark) { :root { ... } }. Components keep using var(--name).
- Does the design token format support dark mode?
- Opalite stores the dark value under $extensions.mode.dark, a field the format reserves for tool-specific data. Check that your token pipeline reads it.
- Which Opalite exports are free?
- Saving as an Opalite file or an image is free. The code, design token, Adobe ASE, GIMP, and Procreate exports need Onyx.
Want these checks built into your palette?
Opalite checks contrast, simulates color blindness, and exports palettes to code on iPhone, iPad, Mac, and more. Free to start.
Opalite is an independent app from Molargik Software. WCAG is published by the W3C, which is not affiliated with and does not endorse Opalite.