Theming

Customize colors, radii, and light and dark themes with tokens.

Theme arctis/ui with CSS variables and semantic tokens.

Want a visual starting point?

Soon, ui create will let you preview colors, radii, and fonts before adding a matching preset to your project. For now, the tokens below are the source of truth.

CSS variables are the recommended way to theme the components.

They provide semantic tokens such as background, foreground, and primary, which components use by default. Change a token in CSS and the UI follows, without rewriting classes across every component.

<div className="bg-background text-foreground" />

Keep CSS variables enabled in your config. This is the default:

components.json
{  "style": "ui",  "rsc": true,  "tailwind": {    "css": "src/app/globals.css",    "baseColor": "neutral",    "cssVariables": true  }}

Tailwind maps the tokens to utilities such as bg-background, text-foreground, and border-border.

For dark mode, override the same tokens under a .dark or html.dark selector. This documentation site uses the same pattern.

Token convention

Tokens use background and foreground pairs. The base token defines the surface, while the -foreground token defines the text and icons placed on it.

The surface token omits the word background. For example, primary pairs with primary-foreground.

--primary: #000000;--primary-foreground: #ffffff;

Use the pair together:

<div className="bg-primary text-primary-foreground">Hello</div>

For solid fills, use the pair (bg-destructive text-destructive-foreground). For softer surfaces, reuse the same color with opacity (border-destructive/25 bg-destructive/5 text-destructive).

Use text-muted-foreground for muted copy and text-muted-foreground/50 for disabled or faint chrome.

Theme tokens

Define these tokens in your global CSS under :root and .dark.

TokenWhat it controlsUsed by
background / foregroundDefault app background and text.Page shell, sections, default copy.
card / card-foregroundElevated surfaces and content on them.Cards, panels, alerts, attachments.
popover / popover-foregroundFloating surfaces and overlay content.Dropdown menus, popovers, floating panels.
primary / primary-foregroundHigh-emphasis actions and brand surfaces.Primary buttons, default badges, selected states.
secondary / secondary-foregroundLower-emphasis filled surfaces.Secondary badges, cancel actions, supporting UI.
muted / muted-foregroundSubtle surfaces and quieter text.Descriptions, placeholders, helper text, icons.
accent / accent-foregroundHover and active surfaces.Ghost badges, menu highlights, hover states.
destructive / destructive-foregroundDestructive actions and errors.Destructive buttons, badges, soft error surfaces.
success / success-foregroundPositive status and success feedback.Success alerts, status badges.
warning / warning-foregroundCaution and warning feedback.Warning alerts.
borderDefault borders and separators.Cards, tables, dividers, menus.
inputForm control borders and surfaces.Input, Textarea, Select.
ringFocus rings and outlines.Focusable controls across the system.
radiusBase corner radius scale.Buttons, inputs, cards, popovers.

Radius scale

--radius is the base radius token. Derive a small scale from it to keep corners consistent from a single source of truth.

src/app/globals.css
@theme inline {  --radius-sm: calc(var(--radius) * 0.6);  --radius-md: calc(var(--radius) * 0.8);  --radius-lg: var(--radius);  --radius-xl: calc(var(--radius) * 1.4);}

Change --radius once, and every derived size updates with it.

Adding new tokens

Define the variable in both :root and .dark, then expose it in @theme inline.

src/app/globals.css
:root {  --info: #2563eb;  --info-foreground: #ffffff;}.dark {  --info: #60a5fa;  --info-foreground: #000000;}@theme inline {  --color-info: var(--info);  --color-info-foreground: var(--info-foreground);}

You can then use bg-info and text-info-foreground in your components.

Default theme CSS

Use this starter scaffold as-is or adjust it to fit your project. It matches the neutral arctis/ui baseline used on this site.

src/app/globals.css
@import "tailwindcss";@custom-variant dark (&:where(.dark, .dark *));@theme inline {  --color-background: var(--background);  --color-foreground: var(--foreground);  --color-card: var(--card);  --color-card-foreground: var(--card-foreground);  --color-popover: var(--popover);  --color-popover-foreground: var(--popover-foreground);  --color-primary: var(--primary);  --color-primary-foreground: var(--primary-foreground);  --color-secondary: var(--secondary);  --color-secondary-foreground: var(--secondary-foreground);  --color-muted: var(--muted);  --color-muted-foreground: var(--muted-foreground);  --color-accent: var(--accent);  --color-accent-foreground: var(--accent-foreground);  --color-destructive: var(--destructive);  --color-destructive-foreground: var(--destructive-foreground);  --color-success: var(--success);  --color-success-foreground: var(--success-foreground);  --color-warning: var(--warning);  --color-warning-foreground: var(--warning-foreground);  --color-border: var(--border);  --color-input: var(--input);  --color-ring: var(--ring);  --radius-sm: calc(var(--radius) * 0.6);  --radius-md: calc(var(--radius) * 0.8);  --radius-lg: var(--radius);  --radius-xl: calc(var(--radius) * 1.4);}:root {  --radius: 0.625rem;  --background: #ffffff;  --foreground: #000000;  --card: var(--muted);  --card-foreground: #000000;  --popover: #ffffff;  --popover-foreground: #000000;  --primary: #000000;  --primary-foreground: #ffffff;  --secondary: #e4e4e7;  --secondary-foreground: #000000;  --muted: #f4f4f5;  --muted-foreground: #71717a;  --accent: #e4e4e7;  --accent-foreground: #000000;  --destructive: #dc2626;  --destructive-foreground: #ffffff;  --success: #059669;  --success-foreground: #ffffff;  --warning: #d97706;  --warning-foreground: #ffffff;  --border: #e4e4e7;  --input: #e4e4e7;  --ring: #a1a1aa;}.dark {  --background: #000000;  --foreground: #ffffff;  --card: var(--muted);  --card-foreground: #ffffff;  --popover: #141414;  --popover-foreground: #ffffff;  --primary: #ffffff;  --primary-foreground: #000000;  --secondary: #1f1f1f;  --secondary-foreground: #ffffff;  --muted: #141414;  --muted-foreground: #a1a1aa;  --accent: #1f1f1f;  --accent-foreground: #ffffff;  --destructive: #ef4444;  --destructive-foreground: #ffffff;  --success: #34d399;  --success-foreground: #000000;  --warning: #fbbf24;  --warning-foreground: #000000;  --border: rgb(255 255 255 / 0.1);  --input: rgb(255 255 255 / 0.15);  --ring: #71717a;}@layer base {  body {    @apply bg-background text-foreground;  }}

Without CSS variables

If you prefer utility colors, you can still use the components, but you will need to override classes more often. Theme tokens remain the recommended approach for dark mode and consistency.