Theme generator
One key color in; a complete theme out. Surfaces, text, controls, status colors and chart colors for light and dark, every pair checked, as a UISet skin, shadcn/ui variables or design tokens.
Preview
Light
Project settings
Atlas, production
Arrow keys move between cells. Enter sorts a column or edits a cell, Escape cancels. Shift with arrows selects cells. On a column header, Shift+F10 opens the column menu, Alt (Option on a Mac) with Left or Right resizes the column, and adding Shift moves it.
Dark
Project settings
Atlas, production
Arrow keys move between cells. Enter sorts a column or edits a cell, Escape cancels. Shift with arrows selects cells. On a column header, Shift+F10 opens the column menu, Alt (Option on a Mac) with Left or Right resizes the column, and adding Shift moves it.
Checks
Every pair the base skin draws, measured on the colors the files contain. Contrast is WCAG 2; chart colors are told apart in OKLab (ΔE ×100), with full color vision and with protan and deutan vision simulated.
| Check | Light | Dark |
|---|---|---|
| Text | ||
| Text on backgroundNeeds 4.5:1 | 17.47:1Passesui-foreground on ui-background | 17.02:1Passesui-foreground on ui-background |
| Text on muted surfacesNeeds 4.5:1 | 15.87:1Passesui-foreground on ui-muted | 14.85:1Passesui-foreground on ui-muted |
| Quiet text on backgroundNeeds 4.5:1 | 6.73:1Passesui-muted-foreground on ui-background | 7.99:1Passesui-muted-foreground on ui-background |
| Quiet text on muted surfacesNeeds 4.5:1 | 6.12:1Passesui-muted-foreground on ui-muted | 6.97:1Passesui-muted-foreground on ui-muted |
| Text on primaryNeeds 4.5:1 | 6.31:1Passesui-primary-foreground on ui-primary | 6.17:1Passesui-primary-foreground on ui-primary |
| Controls | ||
| Control edgesNeeds 3:1 | 3.89:1Passesui-input on ui-background | 4.11:1Passesui-input on ui-background |
| Focus ringNeeds 3:1 | 6.22:1Passesui-ring-border on ui-background | 6.50:1Passesui-ring-border on ui-background |
| Checked and selected controlsNeeds 3:1 | 6.22:1Passesui-primary on ui-background | 3.10:1Passesui-primary on ui-background |
| Status | ||
| Info as textNeeds 4.5:1 | 5.61:1Passestone-info on its badge | 10.89:1Passestone-info on its badge |
| Success as textNeeds 4.5:1 | 5.48:1Passestone-success on its badge | 11.51:1Passestone-success on its badge |
| Warning as textNeeds 4.5:1 | 5.24:1Passestone-warning on its badge | 11.72:1Passestone-warning on its badge |
| Danger as textNeeds 4.5:1 | 6.58:1Passestone-danger on its badge | 10.51:1Passestone-danger on its badge |
| Destructive as textNeeds 4.5:1 | 4.55:1Passesui-destructive on its button | 5.89:1Passesui-destructive on its button |
| Charts | ||
| Chart colors on backgroundNeeds 3:1 | 3.13:1Passeschart-6 on ui-background | 4.40:1Passeschart-4 on ui-background |
| Chart colors apart with protan and deutan visionNeeds ΔE 8 | ΔE 9.4Passeschart-2 and chart-4 | ΔE 10.3Passeschart-2 and chart-4 |
| Chart colors apart with full color visionNeeds ΔE 15 | ΔE 17.6Passeschart-1 and chart-6 | ΔE 16.4Passeschart-7 and chart-8 |
Adjusted to pass
- Destructive, light: moved to lightness 0.542 to hold 4.5:1.
- Primary, dark: moved to lightness 0.505 to hold 3:1 on the background.
Export
A skin file: every variable as light-dark(), in UISet’s skin layer. Import it after UISet’s CSS and name it on the root.
import "./custom.css"
<UISet skin="custom">
<App />
</UISet>/*
A UISet skin from the theme generator: key oklch(0.5 0.19 268), radius 0.5rem.
Use it with <UISet skin="custom">. Light and dark follow the theme prop, or the device.
Every pair checked in light and dark: text 4.5:1, control edges and focus 3:1,
chart colors 3:1 and told apart with protan and deutan vision.
Open it again: /themes/generator?key=0.5%2C0.19%2C268&tint=30&tintHue=key&radius=0.5&density=default&text=default&start=base&charts=key
*/
@layer ui-sdk, uiset.tokens, uiset.base, uiset.skin;
@layer uiset.skin {
[data-uiset][data-skin="custom"] {
--ui-background: light-dark(oklch(0.995 0.002 268), oklch(0.17 0.008 268));
--ui-foreground: light-dark(oklch(0.21 0.012 268), oklch(0.96 0.004 268));
--ui-muted: light-dark(oklch(0.962 0.006 268), oklch(0.235 0.01 268));
--ui-muted-foreground: light-dark(oklch(0.47 0.016 268), oklch(0.73 0.012 268));
--ui-border: light-dark(oklch(0.9 0.008 268), oklch(0.3 0.012 268));
--ui-input: light-dark(oklch(0.6 0.014 268), oklch(0.56 0.014 268));
--ui-primary: light-dark(oklch(0.5 0.19 268), oklch(0.505 0.19 268));
--ui-primary-foreground: light-dark(oklch(1 0 0), oklch(1 0 0));
--ui-ring: light-dark(oklch(0.5 0.19 268 / 0.3), oklch(0.505 0.19 268 / 0.35));
--ui-ring-border: light-dark(oklch(0.5 0.19 268), oklch(0.6783 0.1235 268));
--ui-destructive: light-dark(oklch(0.542 0.2201 25.5), oklch(0.712 0.1809 22.8));
--tone-info: light-dark(oklch(0.48 0.15 255), oklch(0.86 0.0771 240));
--tone-success: light-dark(oklch(0.47 0.12 150), oklch(0.87 0.11 150));
--tone-warning: light-dark(oklch(0.5 0.1084 70), oklch(0.89 0.11 85));
--tone-danger: light-dark(oklch(0.46 0.1876 27), oklch(0.86 0.0754 25));
--chart-1: light-dark(oklch(0.5 0.19 268), oklch(0.72 0.1446 268));
--chart-2: light-dark(oklch(0.56 0.1759 40), oklch(0.73 0.17 40));
--chart-3: light-dark(oklch(0.56 0.1109 170), oklch(0.73 0.1445 170));
--chart-4: light-dark(oklch(0.41 0.089 70), oklch(0.58 0.1202 80));
--chart-5: light-dark(oklch(0.65 0.19 350), oklch(0.64 0.17 340));
--chart-6: light-dark(oklch(0.65 0.1489 240), oklch(0.88 0.11 130));
--chart-7: light-dark(oklch(0.56 0.19 300), oklch(0.73 0.17 310));
--chart-8: light-dark(oklch(0.53 0.19 10), oklch(0.79 0.1248 10));
--chart-seq-1: light-dark(oklch(0.905 0.0456 268), oklch(0.338 0.0925 268));
--chart-seq-2: light-dark(oklch(0.812 0.0932 268), oklch(0.433 0.1149 268));
--chart-seq-3: light-dark(oklch(0.717 0.1392 268), oklch(0.527 0.1347 268));
--chart-seq-4: light-dark(oklch(0.622 0.19 268), oklch(0.622 0.1446 268));
--chart-seq-5: light-dark(oklch(0.527 0.177 268), oklch(0.717 0.1059 268));
--chart-seq-6: light-dark(oklch(0.433 0.151 268), oklch(0.812 0.0709 268));
--chart-seq-7: light-dark(oklch(0.338 0.1215 268), oklch(0.905 0.0368 268));
--ui-radius: 0.5rem;
--ui-spacing: 0.25rem;
--ui-font-size: 0.875rem;
--ui-shadow:
0 1px 2px light-dark(oklch(0.2 0.02 268 / 0.08), oklch(0 0 0 / 0.5)),
0 12px 32px -12px light-dark(oklch(0.2 0.04 268 / 0.22), oklch(0 0 0 / 0.7));
}
}
Tokens
| Variable | Light | Dark |
|---|---|---|
--ui-background | oklch(0.995 0.002 268)#fdfdff | oklch(0.17 0.008 268)#0e0f13 |
--ui-foreground | oklch(0.21 0.012 268)#16181e | oklch(0.96 0.004 268)#f0f2f4 |
--ui-muted | oklch(0.962 0.006 268)#f1f2f7 | oklch(0.235 0.01 268)#1c1e23 |
--ui-muted-foreground | oklch(0.47 0.016 268)#575b64 | oklch(0.73 0.012 268)#a4a8af |
--ui-border | oklch(0.9 0.008 268)#dcdee3 | oklch(0.3 0.012 268)#2b2e34 |
--ui-input | oklch(0.6 0.014 268)#7c8089 | oklch(0.56 0.014 268)#71747d |
--ui-primary | oklch(0.5 0.19 268)#3655cd | oklch(0.505 0.19 268)#3856cf |
--ui-primary-foreground | oklch(1 0 0)#ffffff | oklch(1 0 0)#ffffff |
--ui-ring | oklch(0.5 0.19 268 / 0.3)#3655cd | oklch(0.505 0.19 268 / 0.35)#3856cf |
--ui-ring-border | oklch(0.5 0.19 268)#3655cd | oklch(0.6783 0.1235 268)#7794e4 |
--ui-destructive | oklch(0.542 0.2201 25.5)#d1001f | oklch(0.712 0.1809 22.8)#ff6b6b |
--tone-info | oklch(0.48 0.15 255)#0e5caf | oklch(0.86 0.0771 240)#a3d8ff |
--tone-success | oklch(0.47 0.12 150)#146d34 | oklch(0.87 0.11 150)#9fe9ae |
--tone-warning | oklch(0.5 0.1084 70)#8a5600 | oklch(0.89 0.11 85)#fcd583 |
--tone-danger | oklch(0.46 0.1876 27)#a8000f | oklch(0.86 0.0754 25)#ffbeb8 |
--chart-1 | oklch(0.5 0.19 268)#3655cd | oklch(0.72 0.1446 268)#7e9fff |
--chart-2 | oklch(0.56 0.1759 40)#c54300 | oklch(0.73 0.17 40)#fe7d4e |
--chart-3 | oklch(0.56 0.1109 170)#00896a | oklch(0.73 0.1445 170)#00c49a |
--chart-4 | oklch(0.41 0.089 70)#684000 | oklch(0.58 0.1202 80)#9f7100 |
--chart-5 | oklch(0.65 0.19 350)#df539f | oklch(0.64 0.17 340)#cd5cac |
--chart-6 | oklch(0.65 0.1489 240)#0099e0 | oklch(0.88 0.11 130)#c0e699 |
--chart-7 | oklch(0.56 0.19 300)#8851d1 | oklch(0.73 0.17 310)#c886f6 |
--chart-8 | oklch(0.53 0.19 10)#bf2352 | oklch(0.79 0.1248 10)#ff98a8 |
--chart-seq-1 | oklch(0.905 0.0456 268)#d3dfff | oklch(0.338 0.0925 268)#233367 |
--chart-seq-2 | oklch(0.812 0.0932 268)#a8c0ff | oklch(0.433 0.1149 268)#364b8f |
--chart-seq-3 | oklch(0.717 0.1392 268)#7e9ffb | oklch(0.527 0.1347 268)#4a65b8 |
--chart-seq-4 | oklch(0.622 0.19 268)#567bf8 | oklch(0.622 0.1446 268)#6181dd |
--chart-seq-5 | oklch(0.527 0.177 268)#405fcf | oklch(0.717 0.1059 268)#87a1e6 |
--chart-seq-6 | oklch(0.433 0.151 268)#2e47a2 | oklch(0.812 0.0709 268)#aec1f0 |
--chart-seq-7 | oklch(0.338 0.1215 268)#1d3075 | oklch(0.905 0.0368 268)#d5dff9 |
--ui-radius | 0.5rem | |
--ui-spacing | 0.25rem | |
--ui-font-size | 0.875rem | |