Files
ui/docs/content/1.getting-started/3.theme.md
2025-03-03 15:36:36 +01:00

964 lines
22 KiB
Markdown

---
title: Theme
description: 'Learn how to customize Nuxt UI components using Tailwind CSS v4, CSS variables and the Tailwind Variants API for powerful and flexible theming.'
navigation.icon: i-lucide-swatch-book
---
## Tailwind CSS
Nuxt UI v3 uses Tailwind CSS v4, you can read the official [upgrade guide](https://tailwindcss.com/docs/upgrade-guide#changes-from-v3) to learn about all the breaking changes.
### `@theme`
Tailwind CSS v4 takes a CSS-first configuration approach, you now customize your theme with CSS variables inside a [`@theme`](https://tailwindcss.com/docs/functions-and-directives#theme-directive) directive to define your project's custom design tokens, like fonts, colors, and breakpoints:
::module-only
#ui
:::div
```css [app/assets/css/main.css]
@import "tailwindcss" theme(static);
@import "@nuxt/ui";
@theme static {
--font-sans: 'Public Sans', sans-serif;
--breakpoint-3xl: 1920px;
--color-green-50: #EFFDF5;
--color-green-100: #D9FBE8;
--color-green-200: #B3F5D1;
--color-green-300: #75EDAE;
--color-green-400: #00DC82;
--color-green-500: #00C16A;
--color-green-600: #00A155;
--color-green-700: #007F45;
--color-green-800: #016538;
--color-green-900: #0A5331;
--color-green-950: #052E16;
}
```
:::
#ui-pro
:::div
```css [app/assets/css/main.css]
@import "tailwindcss" theme(static);
@import "@nuxt/ui-pro";
@theme static {
--font-sans: 'Public Sans', sans-serif;
--breakpoint-3xl: 1920px;
--color-green-50: #EFFDF5;
--color-green-100: #D9FBE8;
--color-green-200: #B3F5D1;
--color-green-300: #75EDAE;
--color-green-400: #00DC82;
--color-green-500: #00C16A;
--color-green-600: #00A155;
--color-green-700: #007F45;
--color-green-800: #016538;
--color-green-900: #0A5331;
--color-green-950: #052E16;
}
```
:::
::
The `@theme` directive tells Tailwind to make new utilities and variants available based on these variables. It's the equivalent of the `theme.extend` key in Tailwind CSS v3 `tailwind.config.ts` file.
::note{to="https://tailwindcss.com/docs/theme" target="_blank"}
Learn more about customizing your theme in the theme variables documentation.
::
### `@source`
You can use the [`@source` directive](https://tailwindcss.com/docs/functions-and-directives#source-directive) to explicitly specify source files that aren't picked up by Tailwind's automatic content detection:
This can be useful when writing Tailwind CSS classes in markdown files with [`@nuxt/content`](https://github.com/nuxt/content) for example:
::module-only
#ui
:::div
```css [app/assets/css/main.css]
@import "tailwindcss" theme(static);
@import "@nuxt/ui";
@source "../../../content";
/* Use this if you're not using compatibilityVersion: 4: https://nuxt.com/docs/getting-started/upgrade#opting-in-to-nuxt-4 */
@source "../../content";
```
:::
#ui-pro
:::div
```css [app/assets/css/main.css]
@import "tailwindcss" theme(static);
@import "@nuxt/ui-pro";
@source "../../../content";
/* Use this if you're not using compatibilityVersion: 4: https://nuxt.com/docs/getting-started/upgrade#opting-in-to-nuxt-4 */
@source "../../content";
```
:::
::
::note{to="https://tailwindcss.com/docs/detecting-classes-in-source-files"}
Learn more about automatic content detection in the detecting classes in source files documentation.
::
## Design system
Nuxt UI extends Tailwind CSS's theming capabilities, providing a flexible design system with pre-configured color aliases and CSS variables. This allows for easy customization and quick adaptation of the UI to your brand's aesthetic.
### Colors
::framework-only
#nuxt
Nuxt UI leverages Nuxt [App Config](https://nuxt.com/docs/guide/directory-structure/app-config#app-config-file) to provide customizable color aliases based on [Tailwind CSS colors](https://tailwindcss.com/docs/customizing-colors#color-palette-reference):
#vue
Nuxt UI leverages Vite config to provide customizable color aliases based on [Tailwind CSS colors](https://tailwindcss.com/docs/customizing-colors#color-palette-reference):
::
| Color | Default | Description |
| --- | --- | --- |
| `primary`{color="primary"} | `green` | Main brand color, used as the default color for components. |
| `secondary`{color="secondary"} | `blue` | Secondary color to complement the primary color. |
| `success`{color="success"} | `green` | Used for success states. |
| `info`{color="info"} | `blue` | Used for informational states. |
| `warning`{color="warning"} | `yellow` | Used for warning states. |
| `error`{color="error"} | `red` | Used for form error validation states. |
| `neutral` | `slate` | Neutral color for backgrounds, text, etc. |
::framework-only
#nuxt
::div
You can configure these color aliases at runtime in your `app.config.ts` file under the `ui.colors` key, allowing for dynamic theme customization without requiring an application rebuild:
```ts [app.config.ts]
export default defineAppConfig({
ui: {
colors: {
primary: 'blue',
neutral: 'zinc'
}
}
})
```
::
#vue
::module-only
#ui
:::div
You can configure these color aliases at runtime in your `vite.config.ts` file under the `ui.colors` key:
```ts [vite.config.ts]
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
colors: {
primary: 'blue',
neutral: 'zinc'
}
}
})
]
})
```
:::
#ui-pro
:::div
You can configure these color aliases at runtime in your `vite.config.ts` file under the `uiPro.colors` key:
```ts [vite.config.ts]
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import uiPro from '@nuxt/ui-pro/vite'
export default defineConfig({
plugins: [
vue(),
uiPro({
ui: {
colors: {
primary: 'blue',
neutral: 'zinc'
}
}
})
]
})
```
:::
::
::
::note
Try the :prose-icon{name="i-lucide-swatch-book" class="text-(--ui-primary)"} theme picker in the header above to change `primary` and `neutral` colors.
::
These colors are used to style the components but also to generate the `color` variants:
::component-code{slug="button"}
---
props:
color: primary
slots:
default: Button
---
::
::framework-only
#nuxt
:::tip
You can add you own dynamic color aliases in your `app.config.ts`, you just have to make sure to define them in the [`ui.theme.colors`](/getting-started/installation/nuxt#themecolors) option in your `nuxt.config.ts` file.
```ts [app.config.ts]
export default defineAppConfig({
ui: {
colors: {
tertiary: 'indigo'
}
}
})
```
```ts [nuxt.config.ts]
export default defineNuxtConfig({
ui: {
theme: {
colors: ['primary', 'secondary', 'tertiary', 'info', 'success', 'warning', 'error']
}
}
})
```
:::
#vue
::module-only
#ui
:::tip
You can add you own dynamic color aliases in your `vite.config.ts`, you just have to make sure to also define them in the [`theme.colors`](/getting-started/installation/vue#themecolors) option of the `ui` plugin.
```ts [vite.config.ts]
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
colors: {
tertiary: 'indigo'
}
},
theme: {
colors: ['primary', 'secondary', 'tertiary', 'info', 'success', 'warning', 'error']
}
})
]
})
```
:::
#ui-pro
:::tip
You can add you own dynamic color aliases in your `vite.config.ts`, you just have to make sure to also define them in the [`theme.colors`](/getting-started/installation/vue#themecolors) option of the `uiPro` plugin.
```ts [vite.config.ts]
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import uiPro from '@nuxt/ui-pro/vite'
export default defineConfig({
plugins: [
vue(),
uiPro({
ui: {
colors: {
tertiary: 'indigo'
},
},
theme: {
colors: ['primary', 'secondary', 'tertiary', 'info', 'success', 'warning', 'error']
}
})
]
})
```
:::
::
::
### Tokens
Nuxt UI leverages a robust system of CSS variables as design tokens to ensure consistent and flexible component styling. These tokens form the foundation of the theming system, offering smooth support for both light and dark modes.
#### Color Shades
Nuxt UI automatically creates a CSS variable for each color alias you define which represent the default shade used in both light and dark modes:
::code-group
```css [Light]
:root {
--ui-primary: var(--ui-color-primary-500);
--ui-secondary: var(--ui-color-secondary-500);
--ui-success: var(--ui-color-success-500);
--ui-info: var(--ui-color-info-500);
--ui-warning: var(--ui-color-warning-500);
--ui-error: var(--ui-color-error-500);
}
```
```css [Dark]
.dark {
--ui-primary: var(--ui-color-primary-400);
--ui-secondary: var(--ui-color-secondary-400);
--ui-success: var(--ui-color-success-400);
--ui-info: var(--ui-color-info-400);
--ui-warning: var(--ui-color-warning-400);
--ui-error: var(--ui-color-error-400);
}
```
::
::note
You can use these variables in classes like `text-(--ui-primary)`, it will automatically adapt to the current color scheme.
::
::tip
You can change which shade is used for each color on light and dark mode:
::module-only
#ui
:::div{class="*:!mb-0 *:!mt-2.5"}
```css [app/assets/css/main.css]
@import "tailwindcss" theme(static);
@import "@nuxt/ui";
:root {
--ui-primary: var(--ui-color-primary-700);
}
.dark {
--ui-primary: var(--ui-color-primary-200);
}
```
:::
#ui-pro
:::div{class="*:!mb-0 *:!mt-2.5"}
```css [app/assets/css/main.css]
@import "tailwindcss" theme(static);
@import "@nuxt/ui-pro";
:root {
--ui-primary: var(--ui-color-primary-700);
}
.dark {
--ui-primary: var(--ui-color-primary-200);
}
```
:::
::
::
#### Black as Primary Color
::framework-only
#nuxt
:::p
You cannot set `primary: 'black'`{lang="ts-type"} in your [`app.config.ts`](#config) because this color has no shade, instead you can override the primary color in your `main.css` file to create a black & white theme:
:::
#vue
:::p
You cannot set `primary: 'black'`{lang="ts-type"} in your [`vite.config.ts`](#config) because this color has no shade, instead you can override the primary color in your `main.css` file to create a black & white theme:
:::
::
::module-only
#ui
:::div{class="*:!mb-0 *:!mt-2.5"}
```css [app/assets/css/main.css]
@import "tailwindcss" theme(static);
@import "@nuxt/ui";
:root {
--ui-primary: black;
}
.dark {
--ui-primary: white;
}
```
:::
#ui-pro
:::div{class="*:!mb-0 *:!mt-2.5"}
```css [app/assets/css/main.css]
@import "tailwindcss" theme(static);
@import "@nuxt/ui-pro";
:root {
--ui-primary: black;
}
.dark {
--ui-primary: white;
}
```
:::
::
#### Neutral Palette
Nuxt UI provides a comprehensive set of design tokens for the `neutral` color palette, ensuring consistent and accessible UI styling across both light and dark modes. These tokens offer fine-grained control over text, background, and border colors:
::code-group
```css [Light]
:root {
/* Least prominent text */
--ui-text-dimmed: var(--ui-color-neutral-400);
/* Slightly muted text */
--ui-text-muted: var(--ui-color-neutral-500);
/* Moderately prominent text */
--ui-text-toned: var(--ui-color-neutral-600);
/* Default text color */
--ui-text: var(--ui-color-neutral-700);
/* Most prominent text */
--ui-text-highlighted: var(--ui-color-neutral-900);
/* Main background color */
--ui-bg: var(--color-white);
/* Subtle background */
--ui-bg-muted: var(--ui-color-neutral-50);
/* Slightly elevated background */
--ui-bg-elevated: var(--ui-color-neutral-100);
/* More prominent background */
--ui-bg-accented: var(--ui-color-neutral-200);
/* Inverted background color */
--ui-bg-inverted: var(--ui-color-neutral-900);
/* Default border color */
--ui-border: var(--ui-color-neutral-200);
/* Subtle border */
--ui-border-muted: var(--ui-color-neutral-200);
/* More prominent border */
--ui-border-accented: var(--ui-color-neutral-300);
/* Inverted border color */
--ui-border-inverted: var(--ui-color-neutral-900);
}
```
```css [Dark]
.dark {
/* Least prominent text */
--ui-text-dimmed: var(--ui-color-neutral-500);
/* Slightly muted text */
--ui-text-muted: var(--ui-color-neutral-400);
/* Moderately prominent text */
--ui-text-toned: var(--ui-color-neutral-300);
/* Default text color */
--ui-text: var(--ui-color-neutral-200);
/* Most prominent text */
--ui-text-highlighted: var(--color-white);
/* Main background color */
--ui-bg: var(--ui-color-neutral-900);
/* Subtle background */
--ui-bg-muted: var(--ui-color-neutral-800);
/* Slightly elevated background */
--ui-bg-elevated: var(--ui-color-neutral-800);
/* More prominent background */
--ui-bg-accented: var(--ui-color-neutral-700);
/* Inverted background color */
--ui-bg-inverted: var(--color-white);
/* Default border color */
--ui-border: var(--ui-color-neutral-800);
/* Subtle border */
--ui-border-muted: var(--ui-color-neutral-700);
/* More prominent border */
--ui-border-accented: var(--ui-color-neutral-700);
/* Inverted border color */
--ui-border-inverted: var(--color-white);
}
```
::
::note
Nuxt UI automatically applies a text and background color on the `<body>` element of your app:
```css
body {
@apply antialiased text-(--ui-text) bg-(--ui-bg);
}
```
::
::tip
You can customize these CSS variables to tailor the appearance of your application:
::module-only
#ui
:::div{class="*:!mb-0 *:!mt-2.5"}
```css [app/assets/css/main.css]
@import "tailwindcss" theme(static);
@import "@nuxt/ui";
:root {
--ui-bg: var(--ui-color-neutral-50);
--ui-text: var(--ui-color-neutral-900);
}
.dark {
--ui-bg: var(--ui-color-neutral-950);
--ui-border: var(--ui-color-neutral-900);
}
```
:::
#ui-pro
:::div{class="*:!mb-0 *:!mt-2.5"}
```css [app/assets/css/main.css]
@import "tailwindcss" theme(static);
@import "@nuxt/ui-pro";
:root {
--ui-bg: var(--ui-color-neutral-50);
--ui-text: var(--ui-color-neutral-900);
}
.dark {
--ui-bg: var(--ui-color-neutral-950);
--ui-border: var(--ui-color-neutral-900);
}
```
:::
::
::
#### Border Radius
Nuxt UI uses a global `--ui-radius` CSS variable for consistent border rounding. Components use variations of this base value, like `rounded-[calc(var(--ui-radius)*2)]`, to create different levels of roundness throughout the UI:
```css
:root {
--ui-radius: var(--radius-sm);
}
```
::note
Try the :prose-icon{name="i-lucide-swatch-book" class="text-(--ui-primary)"} theme picker in the header above to change the base radius value.
::
::tip
You can customize the default radius value using the default Tailwind CSS variables or a value of your choice:
::module-only
#ui
:::div{class="*:!mb-0 *:!mt-2.5"}
```css [app/assets/css/main.css]
@import "tailwindcss" theme(static);
@import "@nuxt/ui";
:root {
--ui-radius: var(--radius-sm);
}
```
:::
#ui-pro
:::div{class="*:!mb-0 *:!mt-2.5"}
```css [app/assets/css/main.css]
@import "tailwindcss" theme(static);
@import "@nuxt/ui-pro";
:root {
--ui-radius: var(--radius-sm);
}
```
:::
::
::
#### Container
Nuxt UI uses a global `--ui-container` CSS variable to define the width of the container:
```css
:root {
--ui-container: var(--container-7xl);
}
```
::tip
You can customize the default container width using the default Tailwind CSS variables or a value of your choice:
::module-only
#ui
:::div{class="*:!mb-0 *:!mt-2.5"}
```css [app/assets/css/main.css]
@import "tailwindcss" theme(static);
@import "@nuxt/ui";
@theme {
--container-8xl: 90rem;
}
:root {
--ui-container: var(--container-8xl);
}
```
:::
#ui-pro
:::div{class="*:!mb-0 *:!mt-2.5"}
```css [app/assets/css/main.css]
@import "tailwindcss" theme(static);
@import "@nuxt/ui-pro";
@theme {
--container-8xl: 90rem;
}
:root {
--ui-container: var(--container-8xl);
}
```
:::
::
::
## Components theme
Nuxt UI components are styled using the [Tailwind Variants](https://www.tailwind-variants.org/) API, which provides a powerful way to create variants and manage component styles. Let's explore the key features of this API:
### Slots
Components in Nuxt UI can have multiple `slots`, each representing a distinct HTML element or section within the component. These slots allow for flexible content insertion and styling. Let's take the [Card](/components/card) component as an example:
::code-group
```ts [src/theme/card.ts]
export default {
slots: {
root: 'bg-(--ui-bg) ring ring-(--ui-border) divide-y divide-(--ui-border) rounded-[calc(var(--ui-radius)*2)]',
header: 'p-4 sm:px-6',
body: 'p-4 sm:p-6',
footer: 'p-4 sm:px-6'
}
}
```
```vue [src/runtime/components/Card.vue]
<template>
<div :class="ui.root({ class: [props.class, props.ui?.root] })">
<div :class="ui.header({ class: props.ui?.header })">
<slot name="header" />
</div>
<div :class="ui.body({ class: props.ui?.body })">
<slot />
</div>
<div :class="ui.footer({ class: props.ui?.footer })">
<slot name="footer" />
</div>
</div>
</template>
```
::
Some components don't have slots, they are just composed of a single root element. In this case, the theme only defines the `base` slot like the [Container](/components/container) component for example:
::code-group
```ts [src/theme/container.ts]
export default {
base: 'max-w-(--ui-container) mx-auto px-4 sm:px-6 lg:px-8'
}
```
```vue [src/runtime/components/Container.vue]
<template>
<div :class="container({ class: props.class })">
<slot />
</div>
</template>
```
::
::warning
Components without slots don't have a [`ui` prop](#ui-prop), only the [`class` prop](#class-prop) is available to override styles.
::
### Variants
Nuxt UI components use `variants` to change the `slots` styles based on props. Here's an example of the [Avatar](/components/avatar) component:
```ts [src/theme/avatar.ts]
export default {
slots: {
root: 'inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-(--ui-bg-elevated)',
image: 'h-full w-full rounded-[inherit] object-cover'
},
variants: {
size: {
sm: {
root: 'size-7 text-sm'
},
md: {
root: 'size-8 text-base'
},
lg: {
root: 'size-9 text-lg'
}
}
},
defaultVariants: {
size: 'md'
}
}
```
This way, the `size` prop will apply the corresponding styles to the `root` slot:
::component-code{slug="avatar"}
---
ignore:
- src
props:
src: 'https://github.com/nuxt.png'
size: lg
---
::
The `defaultVariants` property specifies the default values for each variant. It determines how a component looks and behaves when no prop is provided.
::framework-only
#nuxt
:::tip
These default values can be customized in your [`app.config.ts`](#config) to adjust the standard appearance of components throughout your application.
:::
#vue
:::tip
These default values can be customized in your [`vite.config.ts`](#config) to adjust the standard appearance of components throughout your application.
:::
::
## Customize theme
You have multiple ways to customize the appearance of Nuxt UI components, you can do it for all components at once or on a per-component basis.
::note
Tailwind Variants uses [`tailwind-merge`](https://github.com/dcastil/tailwind-merge) under the hood to merge classes so you don't have to worry about conflicting classes.
::
::tip
You can explore the theme for each component in two ways:
- Check the `Theme` section in the documentation of each individual component.
- Browse the source code directly in the GitHub repository at [`v3/src/theme`](https://github.com/nuxt/ui/tree/v3/src/theme).
::
### Config
::framework-only
#nuxt
::div
You can override the theme of components globally inside your `app.config.ts` by using the exact same structure as the theme object.
Let's say you want to change the font weight of all your buttons, you can do it like this:
```ts [app.config.ts]
export default defineAppConfig({
ui: {
button: {
slots: {
base: 'font-bold'
}
}
}
})
```
::
#vue
::module-only
#ui
:::div
You can override the theme of components globally inside your `vite.config.ts` by using the exact same structure as the theme object.
Let's say you want to change the font weight of all your buttons, you can do it like this:
```ts [vite.config.ts]
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
button: {
slots: {
base: 'font-bold'
}
}
}
})
]
})
```
:::
#ui-pro
:::div
You can override the theme of components globally inside your `vite.config.ts` by using the exact same structure as the theme object.
Let's say you want to change the font weight of all your buttons, you can do it like this:
```ts [vite.config.ts]
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import uiPro from '@nuxt/ui-pro/vite'
export default defineConfig({
plugins: [
vue(),
uiPro({
ui: {
button: {
slots: {
base: 'font-bold'
}
}
}
})
]
})
```
:::
::
::
::note
In this example, the `font-bold` class will override the default `font-medium` class on all buttons.
::
### Props
#### `ui` prop
You can also override a component's **slots** using the `ui` prop. This has priority over the global configuration and `variants` resolution.
::component-code{slug="button"}
---
prettier: true
ignore:
- ui.trailingIcon
- color
- variant
- size
- icon
props:
trailingIcon: i-lucide-chevron-right
size: md
color: neutral
variant: outline
ui:
trailingIcon: 'rotate-90 size-3'
slots:
default: |
Button
---
::
::note
In this example, the `trailingIcon` slot is overwritten with `size-3` even though the `md` size variant would apply a `size-5` class to it.
::
#### `class` prop
The `class` prop allows you to override the classes of the `root` or `base` slot. This has priority over the global configuration and `variants` resolution.
::component-code{slug="button"}
---
props:
class: 'font-bold rounded-full'
slots:
default: Button
---
::
::note
In this example, the `font-bold` class will override the default `font-medium` class on this button.
::