Every part of Cotton UI's look is driven by CSS variables you can override. Grays use Tailwind's built-in zinc and work out of the box. A neutral accent ships by default, and radius, surfaces, shadows and the focus ring are all themeable tokens. Override any of them with a :root block (and a .dark block for dark mode), wherever you set your tokens. Start with the main ones below, then see All Tokens for the complete list.
The accent drives primary buttons, active states and focus rings. Cotton UI uses a 3-value accent system:
--color-accent - primary fills (button backgrounds, active states)--color-accent-content - hover states and readable accent text--color-accent-foreground - text and icons on accent fillsA neutral zinc accent ships by default. To brand the kit, override the accent tokens with a :root block and a .dark block for dark mode. This goes in the inline <style> from the no-build setup, or your app.css on a custom build, the same three tokens either way:
:root {
--color-accent: var(--color-blue-500);
--color-accent-content: var(--color-blue-600);
--color-accent-foreground: var(--color-white);
}
.dark {
--color-accent: var(--color-blue-400);
--color-accent-content: var(--color-blue-300);
--color-accent-foreground: var(--color-blue-950);
}
Use :root / .dark, not @theme: the kit already registers these tokens (so utilities like bg-accent and bg-accent/10 work), and a plain :root override beats the kit's baked-in value, where @theme would lose. Swap blue for any Tailwind hue, or your own values.
Prefer a live preview? The Theme Builder lets you tweak the accent, radius, surfaces and focus ring, then copies the generated CSS.
By default, a component's selected and active states use your accent colour: a checked radio, an on switch, a selected card. Adding :accent="False" turns those off for that component and renders them in a neutral tone instead, giving you a monochrome control without touching your theme.
The neutral tracks the current text colour, so it shows dark on a light background and light on a dark background, switching with light and dark mode on its own. There is nothing to set up.
If you want the neutral to be a fixed colour rather than the inherited text colour, define the --color-muted token (with a .dark value for dark mode). It is entirely optional: when it is unset, components fall back to the text colour.
:root {
--color-muted: var(--color-zinc-900);
}
.dark {
--color-muted: var(--color-zinc-100);
}
Two radius tokens control rounding. Form controls and structural surfaces are kept separate so you can, for example, round cards more than inputs:
--radius-control - form controls (inputs, buttons, segments)--radius-box - structural surfaces (cards, dialogs, menus):root {
--radius-control: 0.5rem; /* form controls */
--radius-box: 0.75rem; /* structural surfaces */
}
Surface tokens set the page base and the raised surfaces drawn on top of it. The faint tinted base lets white cards and menus read as elevated:
--color-bg - page base background--color-surface - raised surfaces (cards, dialogs, menus)--color-input-bg - form field fill (transparent in light so fields are border-defined; a faint lift in dark):root {
--color-bg: var(--color-zinc-50); /* page base */
--color-surface: #ffffff; /* cards, dialogs, menus */
--color-input-bg: transparent; /* form fields (border-defined) */
}
.dark {
--color-bg: var(--color-zinc-900);
--color-surface: var(--color-zinc-800);
}
The complete set of themeable tokens and their defaults. Override any of them in a :root block, and set dark-mode values under .dark.
| Token | Default | Dark | Purpose |
|---|---|---|---|
| Accent | |||
--color-accent |
zinc-900 | zinc-100 | Primary fills: button backgrounds, active states |
--color-accent-content |
zinc-700 | zinc-300 | Hover states and readable accent text |
--color-accent-foreground |
white | zinc-900 | Text and icons on accent fills |
--color-muted |
currentColor | Fill and border for :accent="False" controls. Falls back to the foreground when unset. |
|
| Surfaces | |||
--color-bg |
zinc-50 | zinc-900 | Page base background |
--color-surface |
white | zinc-800 | Raised surfaces: cards, dialogs, menus |
--color-input-bg |
transparent | +4% white | Form field fill: transparent in light (border-defined), a faint lift in dark |
--color-box-border |
zinc-200 | zinc-700 | Border on box surfaces: cards, dialogs, menus, popovers. Set transparent for a shadow-only look |
--color-card-border |
= box-border | = box-border | Card border only. Falls back to --color-box-border; override it alone (e.g. transparent) to affect just cards |
| Radius | |||
--radius-control |
0.375rem | Form controls: inputs, buttons, segments | |
--radius-box |
0.5rem | Structural surfaces: cards, dialogs, menus | |
| Shadows | |||
--shadow-input |
0 1px 2px 0 rgb(0 0 0 / 0.05) | Form input elevation (set to none to flatten) | |
--shadow-box |
0 1px 2px 0 rgb(0 0 0 / 0.05) | Card and dialog elevation | |
| Focus ring | |||
--focus-ring-width |
2px | Focus ring thickness | |
--focus-ring-offset |
0px | Gap between the control and the ring | |
--focus-ring-color |
= accent | Focus ring color | |
--focus-ring-offset-color |
white | zinc-900 | Color behind the offset gap |
Cotton UI uses Tailwind's built-in zinc for grays, which works with zero configuration. If you prefer a different gray family, remap zinc to another palette:
/* Remap zinc to slate */
@theme {
--color-zinc-50: var(--color-slate-50);
--color-zinc-100: var(--color-slate-100);
--color-zinc-200: var(--color-slate-200);
--color-zinc-300: var(--color-slate-300);
--color-zinc-400: var(--color-slate-400);
--color-zinc-500: var(--color-slate-500);
--color-zinc-600: var(--color-slate-600);
--color-zinc-700: var(--color-slate-700);
--color-zinc-800: var(--color-slate-800);
--color-zinc-900: var(--color-slate-900);
--color-zinc-950: var(--color-slate-950);
}
Theming approach inspired by Flux UI.