Appearance
Colors
Define colors in any format - they'll be automatically converted to OKLCH. You can compose colors from the palette into gradients and themes.
typescript
export default defineConfig({
colors: {
palette: {
value: {
simple: {
value: {
white: "oklch(100% 0 0)",
black: "#000",
green: { rgb: [0, 255, 0] },
blue: { hsl: [240, 100, 50] },
violet: { oklch: "oklch(0.7 0.2 270)" },
red: { hex: "#FF0000" },
},
},
another: {
value: {
yellow: { hex: "#FFFF00" },
cyan: { hex: "#00FFFF" },
},
settings: {
selector: ":root.Another",
},
},
},
},
gradients: {
value: {
"white-green": {
value: {
primary: {
value: "linear-gradient(to right, var(--c1), var(--c2))",
variables: {
"c1": "palette.simple.white",
"c2": "palette.simple.green",
},
},
},
},
},
},
theme: {
light: {
value: {
background: {
value: {
primary: "var(--1)",
secondary: "var(--2)",
},
variables: {
1: "palette.simple.white",
2: "gradients.white-green.primary", //Reference the color name directly.
},
settings: {
variantNameOnly: true,
},
},
},
},
dark: {
value: {
background: {
value: {
primary: "var(--1)",
secondary: "var(--2)",
},
variables: {
1: "palette.another.yellow",
2: "palette.another.cyan",
},
settings: {
variantNameOnly: true,
},
},
},
settings: {
atRule: "@media (prefers-color-scheme: dark)",
},
},
pink: {
value: {
background: {
value: {
primary: "var(--1)",
secondary: "var(--2)",
},
variables: {
1: "palette.simple.red",
2: "palette.simple.violet",
},
settings: {
variantNameOnly: true,
},
},
},
settings: {
selector: ".ThemePink",
},
},
},
},
});This will generate the following CSS :
css
/*____ CSSForge ____*/
:root {
/*____ Colors ____*/
/* Palette */
/* simple */
--palette-simple-white: oklch(100% 0 none);
--palette-simple-black: oklch(0% 0 none);
--palette-simple-green: oklch(86.644% 0.29483 142.49535);
--palette-simple-blue: oklch(45.201% 0.31321 264.05202);
--palette-simple-violet: oklch(70% 0.2 270);
--palette-simple-red: oklch(62.796% 0.25768 29.23388);
/* Gradients */
/* white-green */
--gradients-white-green-primary: linear-gradient(to right, var(--palette-simple-white), var(--palette-simple-green));
/* Themes */
/* Theme: light */
/* background */
--primary: var(--palette-simple-white);
--secondary: var(--gradients-white-green-primary);
/* Theme: dark */
@media (prefers-color-scheme: dark) {
/* background */
--primary: var(--palette-another-yellow);
--secondary: var(--palette-another-cyan);
}
}
/* another */
:root.Another {
--palette-another-yellow: oklch(96.798% 0.21101 109.76924);
--palette-another-cyan: oklch(90.54% 0.15455 194.76896);
}
/* Theme: pink */
.ThemePink {
/* background */
--primary: var(--palette-simple-red);
--secondary: var(--palette-simple-violet);
}The another palette is emitted under :root.Another, so the element carrying the theme class has to be the root element:
html
<html class="Another">Custom property references are substituted when the alias is computed, before inheritance. --primary: var(--palette-another-yellow) is computed on :root, so --palette-another-yellow has to be defined on :root as well. Scoping the palette to :root.Another keeps both declarations on the same element; a theme class on a descendant leaves --primary invalid at computed-value time, and every var(--primary, fallback) reference uses its fallback.
Color formats for browsers without oklch
Palette colors are generated in OKLCH, which a browser without oklch() support cannot render. Set formats to generate sRGB values alongside it, and fallback to declare one of them for those browsers:
typescript
export default defineConfig({
colors: {
palette: {
value: {
coral: { 100: { hex: "#FF7F50" } },
coralDark: {
value: { 100: { hex: "#FF6347" } },
settings: { atRule: "@media (prefers-color-scheme: dark)" },
},
},
settings: {
color: {
formats: {
hex: { string: true, digits: true, number: true },
rgb: { string: true, array: true },
},
fallback: "hex",
},
},
},
},
});This will generate the following CSS :
css
/*____ CSSForge ____*/
:root {
/*____ Colors ____*/
/* Palette */
/* coral */
--palette-coral-100: oklch(73.511% 0.16799 40.24666);
/* coralDark */
@media (prefers-color-scheme: dark) {
--palette-coralDark-100: oklch(69.622% 0.19552 32.32143);
}
}
@supports not (color: oklch(0% 0 0)) {
:root {
/* coral */
--palette-coral-100: #ff7f50;
}
}
@media (prefers-color-scheme: dark) {
@supports not (color: oklch(0% 0 0)) {
:root {
/* coralDark */
--palette-coralDark-100: #ff6347;
}
}
}| Format | Output | Value |
|---|---|---|
hex | string | "#ff7f50" |
hex | digits | "ff7f50" |
hex | number | 16744272 (0xff7f50) |
rgb | string | "rgb(255 127 80)" |
rgb | array | [255, 127, 80] |
A format set to true generates its CSS value. A color with alpha carries it (#ff7f50aa, 0xff7f50aa, rgb(255 127 80 / 0.667), [255, 127, 80, 0.667]) unless the format sets alpha: true keeps it, a number from 0 to 1 sets it, false rejects the color.
The declaration is the string value of fallback, or of the first format that has one, gated by @supports not (color: oklch(0% 0 0)) and mirroring the color's atRule and selector. fallback: false declares nothing. It is emitted with the palette, before gradient and theme blocks, so a later declaration still wins.
A color's formats merge into the palette's per format, and false removes one. Tokens carry every generated value in color, under their format and output:
json
"color": {
"hex": { "string": "#ff7f50", "number": 16744272 },
"rgb": { "array": [255, 127, 80] }
}A color outside sRGB is gamut mapped for its sRGB values, and its token carries gamutMapped: true so the mapping is visible.
The palette is the only family that converts the colors it is given, so it is the only one that generates formats. Themes and gradients keep their authored values.
Derived colors with mix
Derive hover, subtle and alpha variants from a base color instead of hand-tuning near-duplicates. A mix value is accepted wherever a palette variant or a theme value takes a color, and is emitted as color-mix():
typescript
export default defineConfig({
colors: {
palette: {
value: {
accent: {
base: "oklch(55% 0.2 264)",
hover: { mix: { from: "palette.accent.base", with: "black", amount: 15 } },
},
},
settings: { color: { formats: { hex: true } } },
},
theme: {
light: {
value: {
action: {
value: {
subtle: {
mix: { from: "palette.accent.base", with: "transparent", amount: 88 },
},
},
},
},
},
},
},
});This will generate the following CSS :
css
/*____ CSSForge ____*/
:root {
/*____ Colors ____*/
/* Palette */
/* accent */
--palette-accent-base: oklch(55% 0.2 264);
--palette-accent-hover: color-mix(in oklch, var(--palette-accent-base), black 15%);
/* Themes */
/* Theme: light */
/* action */
--theme-light-action-subtle: color-mix(in oklch, var(--palette-accent-base), transparent 88%);
}
@supports not (color: oklch(0% 0 0)) {
:root {
/* accent */
--palette-accent-base: #3266e4;
--palette-accent-hover: #2651b8;
}
}fromandwithare a token path or a CSS color. A dotted path without spaces, parentheses or#, such as"palette.accent.base", is a token path, written like the paths invariables, and is emitted as itsvar()reference so overriding the base re-derives the variant. It must name a color declared before the mix. Anything else must be a CSS color colorjs.io parses, such as"black","#fff"or"transparent", and is emitted as written.currentColorand system colors have no static value, so they are rejected.amountis the percentage ofwith, from 0 to 100. Mixing withtransparentproduces the alpha variant:amount: 88keeps the color at 12% opacity.inis the interpolation space. Only"oklch"is accepted, the default.
The color-mix() value reaches the CSS and the tokens; the Style Dictionary resolved value substitutes the referenced colors. When a palette mix generates formats, its sRGB values are computed by mixing the colors in OKLCH the way the browser does, so a browser without oklch() support still gets a color. An achromatic operand such as white has no hue, so it takes the other color's hue instead of drifting. Unknown keys, an amount out of range, an unresolvable path and a value that is not a color are rejected with the configuration path.
Condition
You can conditionnally apply colors, gradients or themes by setting the atRule or the selector properties. Your variables will be wrapped within :root and the selectors will be placed outside of it.
Theme: Variant Name Only
When working with themes, you can choose to only include the variant name in the CSS variable name by setting variantNameOnly: true in the color definition settings. This is usually used in combination with selector to conditionnally apply themes.
- Default:
--theme-${themeName}-${colorName}-${variantName} - VariantOnly Name:
--${variantName} - Path :
theme.${themeName}.${colorName}.${variantName}
Theme: light-dark()
Instead of repeating every theme token under a selector, pair a light and a dark theme with theme.settings.lightDark. Each color is emitted once at :root as light-dark(<light>, <dark>), and the browser picks the value from the element's color-scheme. Theme settings sit beside the themes, so this needs the theme: { value, settings } form. In the short form, where themes are the keys of theme, settings is reserved and cannot name a theme:
typescript
export default defineConfig({
colors: {
palette: {
value: {
neutral: { white: "#ffffff", ink: "#1a1a1a" },
},
},
theme: {
value: {
light: {
value: {
background: {
value: { primary: "var(--white)" },
variables: { white: "palette.neutral.white" },
},
text: {
value: { body: { mix: { from: "palette.neutral.ink", with: "white", amount: 10 } } },
},
},
},
dark: {
value: {
background: {
value: { primary: "var(--ink)" },
variables: { ink: "palette.neutral.ink" },
},
text: {
value: { body: { mix: { from: "palette.neutral.white", with: "black", amount: 10 } } },
},
},
},
},
settings: {
lightDark: {
light: "light",
dark: "dark",
colorScheme: { light: '[data-theme="light"]', dark: '[data-theme="dark"]' },
},
},
},
},
});This will generate the following CSS :
css
/*____ CSSForge ____*/
:root {
/*____ Colors ____*/
/* Palette */
/* neutral */
--palette-neutral-white: oklch(100% 0 none);
--palette-neutral-ink: oklch(21.779% 0 none);
/* Themes */
/* Theme: light-dark(light, dark) */
color-scheme: light dark;
/* background */
--theme-background-primary: light-dark(var(--palette-neutral-white), var(--palette-neutral-ink));
/* text */
--theme-text-body: light-dark(color-mix(in oklch, var(--palette-neutral-ink), white 10%), color-mix(in oklch, var(--palette-neutral-white), black 10%));
}
[data-theme="light"] {
color-scheme: light;
}
[data-theme="dark"] {
color-scheme: dark;
}- The paired token drops the theme name:
--theme-${colorName}-${variantName}at the paththeme.${colorName}.${variantName}, or--${variantName}withvariantNameOnly. AvariantNameOnlyreference keeps working unchanged; a path written astheme.light.background.primarybecomestheme.background.primary, which every scheme now shares. Amixcan reference a paired token declared before it. - Both themes must declare the same colors and variants, and a paired color sets
variantNameOnlythe same way in both. A mismatch is rejected with the missing paths. - The paired themes are emitted at
:root, so aselectororatRuleon either of them is rejected. Other themes keep their own output, and withoutlightDarknothing changes. :rootgetscolor-scheme: light dark, so the page follows the user's preference.colorSchemeis optional: each selector it names gets a rule forcing that scheme, placed after:root, for a theme switcher.- The
light-dark()value reaches the CSS, the JSON and TypeScript tokens and the Style Dictionary output, whose resolved value substitutes both schemes' colors. Thecolor-schemedeclarations reach the CSS only. light-dark()only accepts colors, so pair color values only.- Add
<meta name="color-scheme" content="light dark">to the page<head>, so the browser picks the scheme before the CSS loads. light-dark()is supported in Chrome 123, Firefox 120 and Safari 17.5 (Baseline May 2024).