10 KiB
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"withvariant="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
// Nuxt — app.config.ts
export default defineAppConfig({
ui: {
colors: {
primary: 'indigo',
secondary: 'violet',
success: 'emerald',
error: 'rose',
neutral: 'zinc'
}
}
})
// 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
neutralkey — pick one that matches the aesthetic):slate— cool blue-gray, professional (default)gray— true neutral, cleanzinc— slightly cool, modern, techyneutral— perfectly balancedstone— warm gray, earthytaupe— warm brown-gray, sophisticatedmauve— purple-tinted gray, elegantmist— soft blue-gray, airyolive— green-tinted gray, natural
Adding custom brand colors
- Define all 11 shades in 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;
}
- 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:
// 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 texttext-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 withbg-inverted)
Backgrounds
bg-default— page backgroundbg-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 bordersborder-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 necessarilyvariant="solid"— usevariant="soft"or"outline"unless it's the primary action on a confirmation dialog - Button groups should use consistent variants — don't mix
solidandoutlinesiblings
Customizing components
ui prop
Override theme slots on a single instance — wins over global config and variants.
<UButton :ui="{ base: 'font-bold', trailingIcon: 'size-3 rotate-90' }" />
<UCard :ui="{ header: 'bg-muted', body: 'p-8' }" />
Rules for ui overrides:
- Prefer
defaultVariantsover 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 withrounded-*classes inuiorclasswhen you need a specific radius on a component.
class prop
Override the root (or base) slot only — simpler than ui for single-slot changes.
<UButton class="font-bold" />
Finding slot names
Read the generated theme file for any component:
- Nuxt:
.nuxt/ui/<component>.ts - Vue:
node_modules/.nuxt-ui/ui/<component>.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):
// 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 them instead, set the slot to a function, which receives the default classes as its argument so you can reuse part of them.
In global config it replaces the slot's own classes, so variants and compoundVariants still apply on top. In the ui and class props it runs after the variants, so it replaces the resolved classes, variants included.
<UButton :ui="{ label: () => 'text-base font-bold' }" />
// 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:
<UTheme :ui="{ button: { slots: { base: 'rounded-full' } } }">
<UButton label="Rounded" />
<UButton label="Also rounded" />
</UTheme>
Global defaultVariants
Override default size and color for all components at once:
// 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.
// 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:
// nuxt.config.ts
export default defineNuxtConfig({
ui: {
theme: {
prefix: 'tw'
}
}
})
/* 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:
// nuxt.config.ts
export default defineNuxtConfig({
ui: {
experimental: {
componentDetection: true
}
}
})
For dynamic components (e.g., <component :is="...">), pass an array of component names to guarantee they're included:
componentDetection: ['Modal', 'Dropdown', 'Popover']
CSS @theme customization
Customize Tailwind design tokens in main.css:
Fonts
@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
@theme {
--breakpoint-3xl: 1920px;
}
CSS variables
Nuxt UI exposes CSS variables you can override in main.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:
:root {
--ui-primary: var(--ui-color-primary-700);
}
.dark {
--ui-primary: var(--ui-color-primary-200);
}
You can use var(--ui-color-<name>-<shade>) 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:
:root {
--ui-primary: black;
}
.dark {
--ui-primary: white;
}