# Design System
## Semantic colors
Nuxt UI uses 7 semantic colors. Never use raw Tailwind palette colors in components — always use these semantic names.
| Color | Default | When to use |
|---|---|---|
| `primary` | green | CTAs, active states, brand accent, links |
| `secondary` | blue | Secondary actions, complementary highlights |
| `success` | green | Success messages, confirmations, positive states |
| `info` | blue | Informational alerts, tips, neutral highlights |
| `warning` | yellow | Warnings, caution states, pending actions |
| `error` | red | Errors, destructive actions, validation failures |
| `neutral` | slate | Text, borders, backgrounds, disabled states, chrome |
### Choosing colors for components
- **Primary action** on a page (submit, save, confirm) → `color="primary"`
- **Secondary actions** (cancel, back, alternative) → `color="neutral"` with `variant="outline"` or `"ghost"`
- **Destructive actions** (delete, remove) → `color="error"`
- **Status indicators** → match the semantic meaning: `success`, `warning`, `error`, `info`
- **Navigation and chrome** → `color="neutral"`
### Configuring colors
```ts
// Nuxt — app.config.ts
export default defineAppConfig({
ui: {
colors: {
primary: 'indigo',
secondary: 'violet',
success: 'emerald',
error: 'rose',
neutral: 'zinc'
}
}
})
```
```ts
// Vue — vite.config.ts
ui({
ui: {
colors: { primary: 'indigo', secondary: 'violet', neutral: 'zinc' }
}
})
```
Only colors that exist in your theme work — either Tailwind's defaults or custom colors defined with `@theme`.
Available color palettes:
- **Standard Tailwind**: red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose
- **Neutral palettes** (for `neutral` key — pick one that matches the aesthetic):
- `slate` — cool blue-gray, professional (default)
- `gray` — true neutral, clean
- `zinc` — slightly cool, modern, techy
- `neutral` — perfectly balanced
- `stone` — warm gray, earthy
- `taupe` — warm brown-gray, sophisticated
- `mauve` — purple-tinted gray, elegant
- `mist` — soft blue-gray, airy
- `olive` — green-tinted gray, natural
### Adding custom brand colors
1. Define all 11 shades in CSS:
```css
/* app/assets/css/main.css */
@theme static {
--color-brand-50: #fef2f2;
--color-brand-100: #fee2e2;
--color-brand-200: #fecaca;
--color-brand-300: #fca5a5;
--color-brand-400: #f87171;
--color-brand-500: #ef4444;
--color-brand-600: #dc2626;
--color-brand-700: #b91c1c;
--color-brand-800: #991b1b;
--color-brand-900: #7f1d1d;
--color-brand-950: #450a0a;
}
```
2. Assign it: `ui: { colors: { primary: 'brand' } }`
### Extending with new semantic color names
To add a color beyond the 7 defaults (e.g., `tertiary`), register it in `theme.colors`:
```ts
// nuxt.config.ts
export default defineNuxtConfig({
ui: {
theme: {
colors: ['primary', 'secondary', 'tertiary', 'info', 'success', 'warning', 'error']
}
}
})
```
## Semantic utility classes
Use these everywhere instead of raw palette colors:
### Text
- `text-default` — primary body text
- `text-muted` — secondary text (descriptions, hints)
- `text-toned` — medium-emphasis text (between muted and default)
- `text-dimmed` — tertiary text (placeholders, disabled)
- `text-highlighted` — emphasized text (headings, important labels)
- `text-inverted` — text on inverted backgrounds (pair with `bg-inverted`)
### Backgrounds
- `bg-default` — page background
- `bg-muted` — subtle backgrounds (hover states, alternating rows)
- `bg-elevated` — raised surfaces (cards, dropdowns)
- `bg-accented` — accent backgrounds (active states, selected items)
- `bg-inverted` — inverse background (dark on light, light on dark)
### Borders
- `border-default` — standard borders
- `border-muted` — subtle borders (dividers, separators)
- `border-accented` — accent borders (active states)
- `border-inverted` — inverse borders
## Variants
Most components accept a `variant` prop. Choose based on visual weight:
| Variant | Weight | When to use |
|---|---|---|
| `solid` | Highest | Primary actions, main CTAs |
| `outline` | Medium | Secondary actions, form fields |
| `soft` | Medium-low | Tags, badges, subtle buttons |
| `subtle` | Low | Background highlights, less prominent actions |
| `ghost` | Lowest | Inline actions, icon buttons, navigation items |
| `link` | Lowest | Text-only links inside content |
### Rules
- **One solid primary button per view** — everything else should be lower weight
- **Destructive buttons** use `color="error"` but not necessarily `variant="solid"` — use `variant="soft"` or `"outline"` unless it's the primary action on a confirmation dialog
- **Button groups** should use consistent variants — don't mix `solid` and `outline` siblings
## Customizing components
### `ui` prop
Override theme **slots** on a single instance — wins over global config and variants.
```vue
```
Rules for `ui` overrides:
- **Prefer `defaultVariants`** over slot class overrides when possible (e.g., changing default button variant/size).
- **Don't duplicate default classes** — check the generated theme file first to see what's already there.
- Border radius defaults come from `--ui-radius`, but you can override with `rounded-*` classes in `ui` or `class` when you need a specific radius on a component.
### `class` prop
Override the **root** (or `base`) slot only — simpler than `ui` for single-slot changes.
```vue
```
### Finding slot names
Read the generated theme file for any component:
- **Nuxt**: `.nuxt/ui/.ts`
- **Vue**: `node_modules/.nuxt-ui/ui/.ts`
These files show every available slot name, variant combination, and default class.
### Global config
Override `slots`, `variants`, `compoundVariants`, and `defaultVariants` globally in `app.config.ts` (Nuxt) or `vite.config.ts` (Vue):
```ts
// Nuxt — app.config.ts
export default defineAppConfig({
ui: {
button: {
slots: {
base: 'font-bold'
},
compoundVariants: [{
color: 'neutral',
variant: 'outline',
class: 'ring-default hover:bg-accented'
}],
defaultVariants: {
color: 'neutral',
variant: 'outline'
}
}
}
})
```
Tailwind Variants uses `tailwind-merge` under the hood — conflicting classes are resolved automatically.
### Replace instead of merge
Classes from the `ui` prop, the `class` prop, and global config are merged onto the component defaults. To replace a slot's defaults entirely instead, set it to a function in the `ui` prop or global config. It receives the resolved default classes as its argument, so you can reuse part of them.
```vue
```
```ts
// app.config.ts, applies to every instance
export default defineAppConfig({
ui: {
button: {
slots: {
label: () => 'text-base font-bold'
}
}
}
})
```
### Theme component
Override theme for a section of the component tree without affecting the rest of the app. Renders no DOM element — uses `provide`/`inject`:
```vue
```
### Global `defaultVariants`
Override default `size` and `color` for **all** components at once:
```ts
// nuxt.config.ts
export default defineNuxtConfig({
ui: {
theme: {
defaultVariants: {
size: 'lg',
color: 'neutral'
}
}
}
})
```
### `theme.transitions`
Controls whether interactive components get `transition-colors`. Enabled by default.
```ts
// nuxt.config.ts — disable transitions
export default defineNuxtConfig({
ui: {
theme: {
transitions: false
}
}
})
```
### `theme.prefix`
When using Tailwind CSS with a prefix, configure the same prefix in Nuxt UI so component classes match:
```ts
// nuxt.config.ts
export default defineNuxtConfig({
ui: {
theme: {
prefix: 'tw'
}
}
})
```
```css
/* app/assets/css/main.css */
@import "tailwindcss" prefix(tw);
@import "@nuxt/ui";
```
### Tree-shaking with `experimental.componentDetection`
Enable automatic component detection to only generate CSS for components you actually use:
```ts
// nuxt.config.ts
export default defineNuxtConfig({
ui: {
experimental: {
componentDetection: true
}
}
})
```
For dynamic components (e.g., ``), pass an array of component names to guarantee they're included:
```ts
componentDetection: ['Modal', 'Dropdown', 'Popover']
```
## CSS `@theme` customization
Customize Tailwind design tokens in `main.css`:
### Fonts
```css
@theme {
--font-sans: 'Public Sans', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
}
```
In Nuxt, fonts defined here are automatically loaded by `@nuxt/fonts`.
### Breakpoints
```css
@theme {
--breakpoint-3xl: 1920px;
}
```
## CSS variables
Nuxt UI exposes CSS variables you can override in `main.css`:
```css
:root {
--ui-radius: 0.25rem;
--ui-container: 80rem;
--ui-header-height: 4rem;
}
```
### Color shade overrides
Each semantic color defaults to shade 500 in light mode, 400 in dark mode. Override per-mode:
```css
:root {
--ui-primary: var(--ui-color-primary-700);
}
.dark {
--ui-primary: var(--ui-color-primary-200);
}
```
You can use `var(--ui-color--)` to reference shades from the active palette (e.g., `var(--ui-color-neutral-800)` maps to whichever neutral palette is configured).
### Black/white as primary
`black` and `white` have no shades, so they can't be used in config. Set them directly:
```css
:root {
--ui-primary: black;
}
.dark {
--ui-primary: white;
}
```