Files
2026-04-17 23:26:01 +00:00

428 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Theming
## Semantic colors
| Color | Default | Purpose |
|---|---|---|
| `primary` | green | CTAs, active states, brand |
| `secondary` | blue | Secondary actions |
| `success` | green | Success messages |
| `info` | blue | Informational |
| `warning` | yellow | Warnings |
| `error` | red | Errors, destructive actions |
| `neutral` | slate | Text, borders, disabled |
## 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' }
}
})
```
You can only use colors that exist in your theme — either [Tailwind's default colors](https://tailwindcss.com/docs/colors) or custom colors defined with `@theme`.
## Adding custom colors
1. Define all 11 shades in CSS:
```css
/* 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 as a semantic color value: `ui: { colors: { primary: 'brand' } }`
You can only use colors that have all shades defined — either from Tailwind's defaults or custom `@theme` definitions.
### Extending with new semantic color names
If you need a new semantic color beyond the defaults (e.g., `tertiary`), register it in `theme.colors`:
```ts
// Nuxt — nuxt.config.ts
export default defineNuxtConfig({
ui: {
theme: {
colors: ['primary', 'secondary', 'tertiary', 'info', 'success', 'warning', 'error']
}
}
})
```
```ts
// Vue — vite.config.ts
ui({
theme: {
colors: ['primary', 'secondary', 'tertiary', 'info', 'success', 'warning', 'error']
}
})
```
Then assign it: `ui: { colors: { tertiary: 'indigo' } }` and use it via the `color` prop: `<UButton color="tertiary">`.
## CSS utilities
### Text
| Class | Use | Light value | Dark value |
|---|---|---|---|
| `text-default` | Body text | `neutral-700` | `neutral-200` |
| `text-muted` | Secondary text | `neutral-500` | `neutral-400` |
| `text-dimmed` | Placeholders, hints | `neutral-400` | `neutral-500` |
| `text-toned` | Subtitles | `neutral-600` | `neutral-300` |
| `text-highlighted` | Headings, emphasis | `neutral-900` | `white` |
| `text-inverted` | On dark/light backgrounds | `white` | `neutral-900` |
### Background
| Class | Use | Light value | Dark value |
|---|---|---|---|
| `bg-default` | Page background | `white` | `neutral-900` |
| `bg-muted` | Subtle sections | `neutral-50` | `neutral-800` |
| `bg-elevated` | Cards, modals | `neutral-100` | `neutral-800` |
| `bg-accented` | Hover states | `neutral-200` | `neutral-700` |
| `bg-inverted` | Inverted sections | `neutral-900` | `white` |
### Border
| Class | Use | Light value | Dark value |
|---|---|---|---|
| `border-default` | Default borders | `neutral-200` | `neutral-800` |
| `border-muted` | Subtle borders | `neutral-200` | `neutral-700` |
| `border-accented` | Emphasized borders | `neutral-300` | `neutral-700` |
| `border-inverted` | Inverted borders | `neutral-900` | `white` |
### Semantic color utilities
Each semantic color (`primary`, `secondary`, `success`, `info`, `warning`, `error`) is available as a Tailwind utility: `text-primary`, `bg-primary`, `border-primary`, `ring-primary`, etc.
They resolve to shade **500** in light mode and shade **400** in dark mode (via `--ui-<color>` CSS variables). This is generated at runtime by the colors plugin — you don't need to write dark-mode variants manually.
To adjust which shade is used, override `--ui-primary` (or any semantic color) in your `main.css`:
```css
:root { --ui-primary: var(--ui-color-primary-600); }
.dark { --ui-primary: var(--ui-color-primary-300); }
```
### CSS variables
All customizable in `main.css`:
```css
:root {
--ui-radius: 0.25rem; /* base radius for all components */
--ui-container: 80rem; /* UContainer max-width */
--ui-header-height: 4rem; /* UHeader height */
--ui-primary: var(--ui-color-primary-500); /* adjust shade used */
}
.dark {
--ui-primary: var(--ui-color-primary-400);
}
```
### Solid colors (black/white)
```css
:root { --ui-primary: black; }
.dark { --ui-primary: white; }
```
## Component theme customization
### How it works
Components are styled with [Tailwind Variants](https://www.tailwind-variants.org/). The theme defines:
- **`slots`** — named style targets (e.g., `root`, `base`, `label`, `leadingIcon`)
- **`variants`** — styles applied based on props (e.g., `color`, `variant`, `size`)
- **`compoundVariants`** — styles for specific prop combinations (e.g., `color: 'primary'` + `variant: 'outline'`)
- **`defaultVariants`** — default prop values when none are specified
### Override priority
**`ui` prop / `class` prop > global config > theme defaults**
The `ui` prop overrides slots **after** variants are computed. If the `size: 'md'` variant applies `size-5` to `trailingIcon`, and you set `:ui="{ trailingIcon: 'size-3' }"`, the `size-3` wins.
Tailwind Variants uses [tailwind-merge](https://github.com/dcastil/tailwind-merge) under the hood so conflicting classes are resolved automatically.
### Understanding the generated theme
Every component's full resolved theme is generated at build time. Always read this file before customizing a component — it shows exactly what classes are applied where.
- **Nuxt**: `.nuxt/ui/<component>.ts`
- **Vue**: `node_modules/.nuxt-ui/ui/<component>.ts`
For example, the card theme:
```ts
{
slots: {
root: "rounded-lg overflow-hidden",
header: "p-4 sm:px-6",
body: "p-4 sm:p-6",
footer: "p-4 sm:px-6"
},
variants: {
variant: {
outline: { root: "bg-default ring ring-default divide-y divide-default" },
soft: { root: "bg-elevated/50 divide-y divide-default" }
}
},
defaultVariants: { variant: "outline" }
}
```
### Global config
Override the theme for all instances of a component:
```ts
// Nuxt — app.config.ts
export default defineAppConfig({
ui: {
button: {
slots: {
base: 'font-bold rounded-full'
},
variants: {
size: {
md: { leadingIcon: 'size-4' }
}
},
compoundVariants: [{
color: 'neutral',
variant: 'outline',
class: { base: 'ring-2' }
}],
defaultVariants: {
color: 'neutral',
variant: 'outline'
}
}
}
})
```
```ts
// Vue — vite.config.ts
ui({
ui: {
button: {
slots: { base: 'font-bold rounded-full' },
defaultVariants: { color: 'neutral', variant: 'outline' }
}
}
})
```
### Per-instance (`ui` prop)
Overrides slots after variant computation:
```vue
<UButton :ui="{ base: 'font-mono', trailingIcon: 'size-3 rotate-90' }" />
<UCard :ui="{ root: 'shadow-xl', body: 'p-8' }" />
```
### Per-instance (`class` prop)
Overrides the `root` or `base` slot:
```vue
<UButton class="rounded-none">Square</UButton>
```
Components without slots (e.g., `UContainer`, `USkeleton`, `UMain`) only have the `class` prop.
### Theme structure patterns
**Slots-based** (most components — `slots` is an object in the generated theme):
```ts
// global config
ui: {
button: {
slots: { base: 'font-bold' }
}
}
// per instance
<UButton :ui="{ base: 'font-bold' }" />
```
**Flat base** (`base` is a top-level string in the generated theme):
```ts
// global config
ui: {
container: {
base: 'max-w-lg'
}
}
// per instance — class prop only
<UContainer class="max-w-lg" />
```
Always check the generated theme file to see which pattern applies.
## Dark mode
```ts
const colorMode = useColorMode()
colorMode.preference = 'dark' // 'light', 'dark', 'system'
```
```vue
<UColorModeButton /> <!-- Toggle -->
<UColorModeSelect /> <!-- Dropdown -->
```
## Fonts
```css
/* assets/css/main.css */
@theme {
--font-sans: 'Public Sans', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
}
```
In Nuxt, fonts defined with `@theme` are automatically loaded by the `@nuxt/fonts` module.
## Brand customization playbook
Follow these steps to fully rebrand Nuxt UI (e.g., "make a Ghibli theme", "match our corporate brand"):
### Step 1 — Define the color palette
Pick colors that match the brand. Map them to semantic roles:
```ts
// app.config.ts (Nuxt) or vite.config.ts (Vue)
ui: {
colors: {
primary: 'emerald', // brand accent
secondary: 'amber', // secondary accent
success: 'green',
info: 'sky',
warning: 'orange',
error: 'rose',
neutral: 'stone' // affects all text, borders, backgrounds
}
}
```
If no Tailwind default color fits, define custom shades in CSS (see [Adding custom colors](#adding-custom-colors)):
```css
@theme static {
--color-forest-50: #f0fdf4;
/* ... all 11 shades (50950) ... */
--color-forest-950: #052e16;
}
```
Then use it: `primary: 'forest'`.
### Step 2 — Set fonts
```css
/* assets/css/main.css */
@theme {
--font-sans: 'Quicksand', system-ui, sans-serif;
}
```
### Step 3 — Adjust CSS variables
```css
:root {
--ui-radius: 0.75rem; /* rounder = softer/playful, smaller = sharper/corporate */
--ui-primary: var(--ui-color-primary-600); /* adjust which shade is used */
}
.dark {
--ui-primary: var(--ui-color-primary-400);
}
```
### Step 4 — Override key components globally
Read the generated theme files to find slot names, then apply global overrides:
```ts
// app.config.ts (Nuxt) or vite.config.ts (Vue)
ui: {
// ... colors from Step 1
button: {
slots: {
base: 'rounded-full font-semibold'
},
defaultVariants: {
variant: 'soft'
}
},
card: {
slots: {
root: 'rounded-2xl shadow-lg'
}
},
badge: {
slots: {
base: 'rounded-full'
}
}
}
```
> **Tip**: Read `.nuxt/ui/button.ts` (Nuxt) or `node_modules/.nuxt-ui/ui/button.ts` (Vue) to see all available slots and variants before overriding.
### Step 5 — Verify dark mode
Check that both modes look correct. Adjust `--ui-primary` shade per mode and test contrast. Use `useColorMode()` to toggle during development.
### Quick checklist
| Step | What to change | Where |
|---|---|---|
| Colors | `primary`, `secondary`, `neutral` | `app.config.ts` / `vite.config.ts` |
| Custom palette | 11 shades per color | `main.css` (`@theme static`) |
| Fonts | `--font-sans`, `--font-mono` | `main.css` (`@theme`) |
| Radius | `--ui-radius` | `main.css` (`:root`) |
| Primary shade | `--ui-primary` | `main.css` (`:root` + `.dark`) |
| Component shapes | Global slot overrides | `app.config.ts` / `vite.config.ts` |
| Dark mode | Verify contrast, adjust variables | `main.css` (`.dark`) |