--name
```
::note{to="https://ui.nuxt.com/getting-started/i18n/nuxt#supported-languages"}
Learn more about **i18n** in the documentation.
::
## Submit a Pull Request (PR)
Before you start, check if there's an existing issue describing the problem or feature request you're working on. If there is, please leave a comment on the issue to let us know you're working on it.
If there isn't, open a new issue to discuss the problem or feature.
### Local Development
To begin local development, follow these steps:
::steps{level="4"}
#### Clone the `nuxt/ui` repository to your local machine
```sh
git clone -b v3 https://github.com/nuxt/ui.git
```
#### Enable [Corepack](https://github.com/nodejs/corepack){rel="nofollow"}
```sh
corepack enable
```
#### Install dependencies
```sh
pnpm install
```
#### Generate type stubs
```sh
pnpm run dev:prepare
```
#### Start development
- To work on the **documentation** located in the `docs` folder, run:
```sh
pnpm run docs
```
- To test the Nuxt components using the **playground**, run:
```sh
pnpm run dev
```
- To test the Vue components using the **playground**, run:
```sh
pnpm run dev:vue
```
::
::note{to="https://ui.nuxt.com/#cli"}
If you're working on implementing a new component, check the **CLI** section to kickstart the process.
::
### IDE Setup
We recommend using VSCode alongside the [ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint){rel="nofollow"}. You can enable auto-fix and formatting when saving your code. Here's how:
```json
{
"editor.codeActionsOnSave": {
"source.fixAll": false,
"source.fixAll.eslint": true
}
}
```
::warning
Since ESLint is already configured to format the code, there's no need for duplicating functionality with **Prettier**. If you have it installed in your editor, we recommend disabling it to avoid conflicts.
::
### Linting
You can use the `lint` command to check for linting errors:
```sh
pnpm run lint # check for linting errors
pnpm run lint:fix # fix linting errors
```
### Type Checking
We use TypeScript for type checking. You can use the `typecheck` command to check for type errors:
```sh
pnpm run typecheck
```
### Testing
Before submitting a PR, ensure that you run the tests for both `nuxt` and `vue`:
```sh
pnpm run test # for Nuxt
pnpm run test:vue # for Vue
```
::tip
If you have to update the snapshots, press `u` after the tests have finished running.
::
### Commit Conventions
We use [Conventional Commits](https://www.conventionalcommits.org/){rel="nofollow"} for commit messages, which allows a changelog to be auto-generated based on the commits. Please read the [guide](https://www.conventionalcommits.org/en/v1.0.0/#summary){rel="nofollow"} through if you aren't familiar with it already.
- Use `fix` and `feat` for code changes that affect functionality or logic
- Use `docs` for documentation changes and `chore` for maintenance tasks
### Making a Pull Request
- Follow along the [instructions](https://github.com/nuxt/ui/blob/v3/.github/PULL_REQUEST_TEMPLATE.md?plain=1){rel="nofollow"} provided when creating a PR
- Ensure your PR's title adheres to the [Conventional Commits](https://www.conventionalcommits.org/){rel="nofollow"} since it will be used once the code is merged.
- Multiple commits are fine; no need to rebase or force push. We'll use `Squash and Merge` when merging.
- Ensure `lint`, `typecheck` and `tests` work before submitting the PR. Avoid making unrelated changes.
We'll review it promptly. If assigned to a maintainer, they'll review it carefully. Ignore the red text; it's for tracking purposes.
## Thanks
Thank you again for being interested in this project! You are awesome! โค๏ธ
# App
## Usage
This component implements Reka UI [ConfigProvider](https://reka-ui.com/docs/utilities/config-provider){rel="nofollow"} to provide global configuration to all components:
- Enables all primitives to inherit global reading direction.
- Enables changing the behavior of scroll body when setting body lock.
- Much more controls to prevent layout shifts.
It's also using [ToastProvider](https://reka-ui.com/docs/components/toast#provider){rel="nofollow"} and [TooltipProvider](https://reka-ui.com/docs/components/tooltip#provider){rel="nofollow"} to provide global toasts and tooltips, as well as programmatic modals and slideovers.
Use it at the root of your app:
```vue [app.vue]
```
::framework-only
#nuxt
:::tip{to="https://ui.nuxt.com/getting-started/i18n/nuxt#locale"}
Learn how to use the `locale` prop to change the locale of your app.
:::
#vue
:::tip{to="https://ui.nuxt.com/getting-started/i18n/vue#locale"}
Learn how to use the `locale` prop to change the locale of your app.
:::
::
## API
### Props
```ts
/**
* Props for the App component
*/
interface AppProps {
tooltip?: TooltipProviderProps | undefined;
toaster?: ToasterProps | null | undefined;
locale?: Locale | undefined;
/**
* @default "\"body\""
*/
portal?: string | HTMLElement | undefined;
/**
* The global scroll body behavior of your application. This will be inherited by the related primitives.
*/
scrollBody?: boolean | ScrollBodyOption | undefined;
/**
* The global `nonce` value of your application. This will be inherited by the related primitives.
*/
nonce?: string | undefined;
}
```
### Slots
```ts
/**
* Slots for the App component
*/
interface AppSlots {
default(): any;
}
```
## Changelog
::component-changelog
::
# Accordion
## Usage
### Items
Use the `items` prop as an array of objects with the following properties:
- `label?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `icon?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `trailingIcon?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `content?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `value?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `disabled?: boolean`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- [`slot?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}](https://ui.nuxt.com/#with-custom-slot)
- `class?: any`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `ui?: { item?: ClassNameValue, header?: ClassNameValue, trigger?: ClassNameValue, leadingIcon?: ClassNameValue, label?: ClassNameValue, trailingIcon?: ClassNameValue, content?: ClassNameValue, body?: ClassNameValue }`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
```vue
```
### Multiple
Set the `type` prop to `multiple` to allow multiple items to be active at the same time. Defaults to `single`.
```vue
```
### Collapsible
When `type` is `single`, you can set the `collapsible` prop to `false` to prevent the active item from collapsing.
```vue
```
### Unmount
Use the `unmount-on-hide` prop to prevent the content from being unmounted when the accordion is collapsed. Defaults to `true`.
```vue
```
::note
You can inspect the DOM to see each item's content being rendered.
::
### Disabled
Use the `disabled` property to disable the Accordion.
You can also disable a specific item by using the `disabled` property in the item object.
```vue
```
### Trailing Icon
Use the `trailing-icon` prop to customize the trailing [Icon](https://ui.nuxt.com/components/icon) of each item. Defaults to `i-lucide-chevron-down`.
::tip
You can also set an icon for a specific item by using the `trailingIcon` property in the item object.
::
```vue
```
::framework-only
#nuxt
:::tip{to="https://ui.nuxt.com/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.chevronDown` key.
:::
#vue
:::tip{to="https://ui.nuxt.com/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.chevronDown` key.
:::
::
## Examples
### Control active item(s)
You can control the active item(s) by using the `default-value` prop or the `v-model` directive with the index of the item.
```vue [AccordionModelValueExample.vue]
```
::tip
You can also pass the `value` of one of the items if provided.
::
::caution
When `type="multiple"`, ensure to pass an array to the `default-value` prop or the `v-model` directive.
::
### With drag and drop
Use the [`useSortable`](https://vueuse.org/integrations/useSortable/){rel="nofollow"} composable from [`@vueuse/integrations`](https://vueuse.org/integrations/README.html){rel="nofollow"} to enable drag and drop functionality on the Accordion. This integration wraps [Sortable.js](https://sortablejs.github.io/Sortable/){rel="nofollow"} to provide a seamless drag and drop experience.
```vue [AccordionDragAndDropExample.vue]
```
### With body slot
Use the `#body` slot to customize the body of each item.
```vue [AccordionBodySlotExample.vue]
This is the {{ item.label }} panel.
```
::tip
The `#body` slot includes some pre-defined styles, use the [`#content` slot](https://ui.nuxt.com/#with-content-slot) if you want to start from scratch.
::
### With content slot
Use the `#content` slot to customize the content of each item.
```vue [AccordionContentSlotExample.vue]
This is the {{ item.label }} panel.
```
### With custom slot
Use the `slot` property to customize a specific item.
You will have access to the following slots:
- `#{{ item.slot }}`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `#{{ item.slot }}-body`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
```vue [AccordionCustomSlotExample.vue]
{{ item.content }}
```
## API
### Props
```ts
/**
* Props for the Accordion component
*/
interface AccordionProps {
/**
* The element or component this component should render as.
*/
as?: any;
items?: AccordionItem[] | undefined;
/**
* The icon displayed on the right side of the trigger.
*/
trailingIcon?: string | undefined;
/**
* The key used to get the label from the item.
* @default "\"label\""
*/
labelKey?: string | undefined;
ui?: { root?: ClassNameValue; item?: ClassNameValue; header?: ClassNameValue; trigger?: ClassNameValue; content?: ClassNameValue; body?: ClassNameValue; leadingIcon?: ClassNameValue; trailingIcon?: ClassNameValue; label?: ClassNameValue; } | undefined;
/**
* When type is "single", allows closing content when clicking trigger for an open item.
* When type is "multiple", this prop has no effect.
* @default "true"
*/
collapsible?: boolean | undefined;
/**
* The default active value of the item(s).
*
* Use when you do not need to control the state of the item(s).
*/
defaultValue?: string | string[] | undefined;
/**
* The controlled value of the active item(s).
*
* Use this when you need to control the state of the items. Can be binded with `v-model`
*/
modelValue?: string | string[] | undefined;
/**
* Determines whether a "single" or "multiple" items can be selected at a time.
*
* This prop will overwrite the inferred type from `modelValue` and `defaultValue`.
* @default "\"single\""
*/
type?: SingleOrMultipleType | undefined;
/**
* When `true`, prevents the user from interacting with the accordion and all its items
*/
disabled?: boolean | undefined;
/**
* When `true`, the element will be unmounted on closed state.
* @default "true"
*/
unmountOnHide?: boolean | undefined;
}
```
### Slots
```ts
/**
* Slots for the Accordion component
*/
interface AccordionSlots {
leading(): any;
default(): any;
trailing(): any;
content(): any;
body(): any;
}
```
### Emits
```ts
/**
* Emitted events for the Accordion component
*/
interface AccordionEmits {
update:modelValue: (payload: [value: string | string[] | undefined]) => void;
}
```
## Theme
```ts [app.config.ts]
export default defineAppConfig({
ui: {
accordion: {
slots: {
root: 'w-full',
item: 'border-b border-default last:border-b-0',
header: 'flex',
trigger: 'group flex-1 flex items-center gap-1.5 font-medium text-sm py-3.5 focus-visible:outline-primary min-w-0',
content: 'data-[state=open]:animate-[accordion-down_200ms_ease-out] data-[state=closed]:animate-[accordion-up_200ms_ease-out] overflow-hidden focus:outline-none',
body: 'text-sm pb-3.5',
leadingIcon: 'shrink-0 size-5',
trailingIcon: 'shrink-0 size-5 ms-auto group-data-[state=open]:rotate-180 transition-transform duration-200',
label: 'text-start break-words'
},
variants: {
disabled: {
true: {
trigger: 'cursor-not-allowed opacity-75'
}
}
}
}
}
})
```
## Changelog
::component-changelog
::
# Alert
## Usage
### Title
Use the `title` prop to set the title of the Alert.
```vue
```
### Description
Use the `description` prop to set the description of the Alert.
```vue
```
### Icon
Use the `icon` prop to show an [Icon](https://ui.nuxt.com/components/icon).
```vue
```
### Avatar
Use the `avatar` prop to show an [Avatar](https://ui.nuxt.com/components/avatar).
```vue
```
### Color
Use the `color` prop to change the color of the Alert.
```vue
```
### Variant
Use the `variant` prop to change the variant of the Alert.
```vue
```
### Close
Use the `close` prop to display a [Button](https://ui.nuxt.com/components/button) to dismiss the Alert.
::tip
An `update:open` event will be emitted when the close button is clicked.
::
```vue
```
You can pass any property from the [Button](https://ui.nuxt.com/components/button) component to customize it.
```vue
```
### Close Icon
Use the `close-icon` prop to customize the close button [Icon](https://ui.nuxt.com/components/icon). Defaults to `i-lucide-x`.
```vue
```
::framework-only
#nuxt
:::tip{to="https://ui.nuxt.com/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.close` key.
:::
#vue
:::tip{to="https://ui.nuxt.com/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.close` key.
:::
::
### Actions
Use the `actions` prop to add some [Button](https://ui.nuxt.com/components/button) actions to the Alert.
```vue
```
### Orientation
Use the `orientation` prop to change the orientation of the Alert.
```vue
```
## Examples
### `class` prop
Use the `class` prop to override the base styles of the Alert.
```vue
```
### `ui` prop
Use the `ui` prop to override the slots styles of the Alert.
```vue
```
## API
### Props
```ts
/**
* Props for the Alert component
*/
interface AlertProps {
/**
* The element or component this component should render as.
*/
as?: any;
title?: string | undefined;
description?: string | undefined;
icon?: string | undefined;
avatar?: AvatarProps | undefined;
color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral" | undefined;
variant?: "solid" | "outline" | "soft" | "subtle" | undefined;
/**
* The orientation between the content and the actions.
* @default "\"vertical\""
*/
orientation?: "vertical" | "horizontal" | undefined;
/**
* Display a list of actions:
* - under the title and description when orientation is `vertical`
* - next to the close button when orientation is `horizontal`
* `{ size: 'xs' }`{lang="ts-type"}
*/
actions?: ButtonProps[] | undefined;
/**
* Display a close button to dismiss the alert.
* `{ size: 'md', color: 'neutral', variant: 'link' }`{lang="ts-type"}
*/
close?: boolean | Partial | undefined;
/**
* The icon displayed in the close button.
*/
closeIcon?: string | undefined;
ui?: { root?: ClassNameValue; wrapper?: ClassNameValue; title?: ClassNameValue; description?: ClassNameValue; icon?: ClassNameValue; avatar?: ClassNameValue; avatarSize?: ClassNameValue; actions?: ClassNameValue; close?: ClassNameValue; } | undefined;
}
```
### Slots
```ts
/**
* Slots for the Alert component
*/
interface AlertSlots {
leading(): any;
title(): any;
description(): any;
actions(): any;
close(): any;
}
```
### Emits
```ts
/**
* Emitted events for the Alert component
*/
interface AlertEmits {
update:open: (payload: [value: boolean]) => void;
}
```
## Theme
```ts [app.config.ts]
export default defineAppConfig({
ui: {
alert: {
slots: {
root: 'relative overflow-hidden w-full rounded-lg p-4 flex gap-2.5',
wrapper: 'min-w-0 flex-1 flex flex-col',
title: 'text-sm font-medium',
description: 'text-sm opacity-90',
icon: 'shrink-0 size-5',
avatar: 'shrink-0',
avatarSize: '2xl',
actions: 'flex flex-wrap gap-1.5 shrink-0',
close: 'p-0'
},
variants: {
color: {
primary: '',
secondary: '',
success: '',
info: '',
warning: '',
error: '',
neutral: ''
},
variant: {
solid: '',
outline: '',
soft: '',
subtle: ''
},
orientation: {
horizontal: {
root: 'items-center',
actions: 'items-center'
},
vertical: {
root: 'items-start',
actions: 'items-start mt-2.5'
}
},
title: {
true: {
description: 'mt-1'
}
}
},
compoundVariants: [
{
color: 'primary',
variant: 'solid',
class: {
root: 'bg-primary text-inverted'
}
},
{
color: 'secondary',
variant: 'solid',
class: {
root: 'bg-secondary text-inverted'
}
},
{
color: 'success',
variant: 'solid',
class: {
root: 'bg-success text-inverted'
}
},
{
color: 'info',
variant: 'solid',
class: {
root: 'bg-info text-inverted'
}
},
{
color: 'warning',
variant: 'solid',
class: {
root: 'bg-warning text-inverted'
}
},
{
color: 'error',
variant: 'solid',
class: {
root: 'bg-error text-inverted'
}
},
{
color: 'primary',
variant: 'outline',
class: {
root: 'text-primary ring ring-inset ring-primary/25'
}
},
{
color: 'secondary',
variant: 'outline',
class: {
root: 'text-secondary ring ring-inset ring-secondary/25'
}
},
{
color: 'success',
variant: 'outline',
class: {
root: 'text-success ring ring-inset ring-success/25'
}
},
{
color: 'info',
variant: 'outline',
class: {
root: 'text-info ring ring-inset ring-info/25'
}
},
{
color: 'warning',
variant: 'outline',
class: {
root: 'text-warning ring ring-inset ring-warning/25'
}
},
{
color: 'error',
variant: 'outline',
class: {
root: 'text-error ring ring-inset ring-error/25'
}
},
{
color: 'primary',
variant: 'soft',
class: {
root: 'bg-primary/10 text-primary'
}
},
{
color: 'secondary',
variant: 'soft',
class: {
root: 'bg-secondary/10 text-secondary'
}
},
{
color: 'success',
variant: 'soft',
class: {
root: 'bg-success/10 text-success'
}
},
{
color: 'info',
variant: 'soft',
class: {
root: 'bg-info/10 text-info'
}
},
{
color: 'warning',
variant: 'soft',
class: {
root: 'bg-warning/10 text-warning'
}
},
{
color: 'error',
variant: 'soft',
class: {
root: 'bg-error/10 text-error'
}
},
{
color: 'primary',
variant: 'subtle',
class: {
root: 'bg-primary/10 text-primary ring ring-inset ring-primary/25'
}
},
{
color: 'secondary',
variant: 'subtle',
class: {
root: 'bg-secondary/10 text-secondary ring ring-inset ring-secondary/25'
}
},
{
color: 'success',
variant: 'subtle',
class: {
root: 'bg-success/10 text-success ring ring-inset ring-success/25'
}
},
{
color: 'info',
variant: 'subtle',
class: {
root: 'bg-info/10 text-info ring ring-inset ring-info/25'
}
},
{
color: 'warning',
variant: 'subtle',
class: {
root: 'bg-warning/10 text-warning ring ring-inset ring-warning/25'
}
},
{
color: 'error',
variant: 'subtle',
class: {
root: 'bg-error/10 text-error ring ring-inset ring-error/25'
}
},
{
color: 'neutral',
variant: 'solid',
class: {
root: 'text-inverted bg-inverted'
}
},
{
color: 'neutral',
variant: 'outline',
class: {
root: 'text-highlighted bg-default ring ring-inset ring-default'
}
},
{
color: 'neutral',
variant: 'soft',
class: {
root: 'text-highlighted bg-elevated/50'
}
},
{
color: 'neutral',
variant: 'subtle',
class: {
root: 'text-highlighted bg-elevated/50 ring ring-inset ring-accented'
}
}
],
defaultVariants: {
color: 'primary',
variant: 'solid'
}
}
}
})
```
## Changelog
::component-changelog
::
# AuthForm
## Usage
Built on top of the [Form](https://ui.nuxt.com/components/form) component, the `AuthForm` component can be used in your pages or wrapped in a [PageCard](https://ui.nuxt.com/components/page-card).
The form will construct itself based on the `fields` prop and the state will be handled internally. You can pass all the props you would pass to a [FormField](https://ui.nuxt.com/components/form-field#props) or an [Input](https://ui.nuxt.com/components/input#props) to each field.
```vue [AuthFormExample.vue]
```
### Title
Use the `title` prop to set the title of the form.
```vue
```
### Description
Use the `description` prop to set the description of the form.
```vue
```
### Icon
Use the `icon` prop to set the icon of the form.
```vue
```
### Providers
Use the `providers` prop to add providers to the form.
You can pass any property from the [Button](https://ui.nuxt.com/components/button) component such as `variant`, `color`, `to`, etc.
```vue
```
### Separator
Use the `separator` prop to customize the [Separator](https://ui.nuxt.com/components/separator) between the providers and the fields. Defaults to `or`.
```vue
```
You can pass any property from the [Separator](https://ui.nuxt.com/components/separator#props) component to customize it.
```vue
```
### Submit
Use the `submit` prop to change the submit button of the form.
You can pass any property from the [Button](https://ui.nuxt.com/components/button) component such as `variant`, `color`, `to`, etc.
```vue
```
## Examples
### Within a page
You can wrap the `AuthForm` component with the [PageCard](https://ui.nuxt.com/components/page-card) component to display it within a `login.vue` page for example.
```vue [AuthFormPageExample.vue]
Don't have an account? Sign up .
Forgot password?
By signing in, you agree to our Terms of Service .
```
### OTP / 2FA Example
You can add a One-Time Password (OTP) field for two-factor authentication by using the `otp` type in your fields array. The `otp` property allows you to pass any prop supported by the [PinInput](https://ui.nuxt.com/components/pin-input#api) component.
```vue
```
## API
### Props
```ts
/**
* Props for the AuthForm component
*/
interface AuthFormProps {
/**
* The element or component this component should render as.
*/
as?: any;
/**
* The icon displayed above the title.
*/
icon?: string | undefined;
title?: string | undefined;
description?: string | undefined;
fields?: AuthFormField[] | undefined;
/**
* Display a list of Button under the description.
* `{ color: 'neutral', variant: 'subtle', block: true }`{lang="ts-type"}
*/
providers?: ButtonProps[] | undefined;
/**
* The text displayed in the separator.
* @default "\"or\""
*/
separator?: string | SeparatorProps | undefined;
/**
* Display a submit button at the bottom of the form.
* `{ label: 'Continue', block: true }`{lang="ts-type"}
*/
submit?: ButtonProps | undefined;
schema?: FormSchema | undefined;
validate?: ((state: Partial) => FormError[] | Promise[]>) | undefined;
validateOn?: FormInputEvents[] | undefined;
validateOnInputDelay?: number | undefined;
disabled?: boolean | undefined;
loading?: boolean | undefined;
loadingAuto?: boolean | undefined;
ui?: { root?: ClassNameValue; header?: ClassNameValue; leading?: ClassNameValue; leadingIcon?: ClassNameValue; title?: ClassNameValue; description?: ClassNameValue; body?: ClassNameValue; providers?: ClassNameValue; checkbox?: ClassNameValue; select?: ClassNameValue; password?: ClassNameValue; otp?: ClassNameValue; input?: ClassNameValue; separator?: ClassNameValue; form?: ClassNameValue; footer?: ClassNameValue; } | undefined;
}
```
### Slots
```ts
/**
* Slots for the AuthForm component
*/
interface AuthFormSlots {
header(): any;
leading(): any;
title(): any;
description(): any;
providers(): any;
validation(): any;
submit(): any;
footer(): any;
}
```
### Emits
```ts
/**
* Emitted events for the AuthForm component
*/
interface AuthFormEmits {
submit: (payload: [FormSubmitEvent]) => void;
}
```
### Expose
You can access the typed component instance (exposing formRef and state) using [`useTemplateRef`](https://vuejs.org/api/composition-api-helpers.html#usetemplateref){rel="nofollow"}. For example, in a separate form (e.g. a "reset" form) you can do:
```vue
```
This gives you access to the following (exposed) properties:
| Name | Type |
| ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `formRef`{.language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} | `Ref`{.language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} |
| `state`{.language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} | `Reactive`{.language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} |
## Theme
```ts [app.config.ts]
export default defineAppConfig({
uiPro: {
authForm: {
slots: {
root: 'w-full space-y-6',
header: 'flex flex-col text-center',
leading: 'mb-2',
leadingIcon: 'size-8 shrink-0 inline-block',
title: 'text-xl text-pretty font-semibold text-highlighted',
description: 'mt-1 text-base text-pretty text-muted',
body: 'gap-y-6 flex flex-col',
providers: 'space-y-3',
checkbox: '',
select: '',
password: 'w-full',
otp: 'w-full justify-center',
input: 'w-full',
separator: '',
form: 'space-y-5',
footer: 'text-sm text-center text-muted mt-2'
}
}
}
})
```
# Avatar
## Usage
The Avatar uses the `` component when [`@nuxt/image`](https://github.com/nuxt/image){rel="nofollow"} is installed, falling back to `img` otherwise.
::note
You can pass any property from the HTML `
` element such as `alt`, `loading`, etc.
::
### Src
Use the `src` prop to set the image URL.
```vue
```
### Size
Use the `size` prop to set the size of the Avatar.
```vue
```
::note
The `
` element's `width` and `height` are automatically set based on the `size` prop.
::
### Icon
Use the `icon` prop to display a fallback [Icon](https://ui.nuxt.com/components/icon).
```vue
```
### Text
Use the `text` prop to display a fallback text.
```vue
```
### Alt
When no icon or text is provided, the **initials** of the `alt` prop is used as fallback.
```vue
```
::note
The `alt` prop is passed to the `img` element as the `alt` attribute.
::
### Chip
Use the `chip` prop to display a chip around the Avatar.
```vue
```
## Examples
### With tooltip
You can use a [Tooltip](https://ui.nuxt.com/components/tooltip) component to display a tooltip when hovering the Avatar.
```vue [AvatarTooltipExample.vue]
```
## API
### Props
```ts
/**
* Props for the Avatar component
*/
interface AvatarProps {
/**
* The element or component this component should render as.
* @default "\"span\""
*/
as?: any;
src?: string | undefined;
alt?: string | undefined;
icon?: string | undefined;
text?: string | undefined;
size?: "md" | "3xs" | "2xs" | "xs" | "sm" | "lg" | "xl" | "2xl" | "3xl" | undefined;
chip?: boolean | ChipProps | undefined;
ui?: { root?: ClassNameValue; image?: ClassNameValue; fallback?: ClassNameValue; icon?: ClassNameValue; } | undefined;
}
```
## Theme
```ts [app.config.ts]
export default defineAppConfig({
ui: {
avatar: {
slots: {
root: 'inline-flex items-center justify-center shrink-0 select-none rounded-full align-middle bg-elevated',
image: 'h-full w-full rounded-[inherit] object-cover',
fallback: 'font-medium leading-none text-muted truncate',
icon: 'text-muted shrink-0'
},
variants: {
size: {
'3xs': {
root: 'size-4 text-[8px]'
},
'2xs': {
root: 'size-5 text-[10px]'
},
xs: {
root: 'size-6 text-xs'
},
sm: {
root: 'size-7 text-sm'
},
md: {
root: 'size-8 text-base'
},
lg: {
root: 'size-9 text-lg'
},
xl: {
root: 'size-10 text-xl'
},
'2xl': {
root: 'size-11 text-[22px]'
},
'3xl': {
root: 'size-12 text-2xl'
}
}
},
defaultVariants: {
size: 'md'
}
}
}
})
```
## Changelog
::component-changelog
::
# AvatarGroup
## Usage
Wrap multiple [Avatar](https://ui.nuxt.com/components/avatar) within an AvatarGroup to stack them.
```vue
```
### Size
Use the `size` prop to change the size of all the avatars.
```vue
```
### Max
Use the `max` prop to limit the number of avatars displayed. The rest is displayed as an `+X` avatar.
```vue
```
## Examples
### With tooltip
Wrap each avatar with a [Tooltip](https://ui.nuxt.com/components/tooltip) to display a tooltip on hover.
```vue [AvatarGroupTooltipExample.vue]
```
### With chip
Wrap each avatar with a [Chip](https://ui.nuxt.com/components/chip) to display a chip around the avatar.
```vue [AvatarGroupChipExample.vue]
```
### With link
Wrap each avatar with a [Link](https://ui.nuxt.com/components/link) to make them clickable.
```vue [AvatarGroupLinkExample.vue]
```
## API
### Props
```ts
/**
* Props for the AvatarGroup component
*/
interface AvatarGroupProps {
/**
* The element or component this component should render as.
*/
as?: any;
size?: "md" | "3xs" | "2xs" | "xs" | "sm" | "lg" | "xl" | "2xl" | "3xl" | undefined;
/**
* The maximum number of avatars to display.
*/
max?: string | number | undefined;
ui?: { root?: ClassNameValue; base?: ClassNameValue; } | undefined;
}
```
### Slots
```ts
/**
* Slots for the AvatarGroup component
*/
interface AvatarGroupSlots {
default(): any;
}
```
## Theme
```ts [app.config.ts]
export default defineAppConfig({
ui: {
avatarGroup: {
slots: {
root: 'inline-flex flex-row-reverse justify-end',
base: 'relative rounded-full ring-bg first:me-0'
},
variants: {
size: {
'3xs': {
base: 'ring -me-0.5'
},
'2xs': {
base: 'ring -me-0.5'
},
xs: {
base: 'ring -me-0.5'
},
sm: {
base: 'ring-2 -me-1.5'
},
md: {
base: 'ring-2 -me-1.5'
},
lg: {
base: 'ring-2 -me-1.5'
},
xl: {
base: 'ring-3 -me-2'
},
'2xl': {
base: 'ring-3 -me-2'
},
'3xl': {
base: 'ring-3 -me-2'
}
}
},
defaultVariants: {
size: 'md'
}
}
}
})
```
## Changelog
::component-changelog
::
# Badge
## Usage
### Label
Use the default slot to set the label of the Badge.
```vue
Badge
```
You can achieve the same result by using the `label` prop.
```vue
```
### Color
Use the `color` prop to change the color of the Badge.
```vue
Badge
```
### Variant
Use the `variant` props to change the variant of the Badge.
```vue
Badge
```
### Size
Use the `size` prop to change the size of the Badge.
```vue
Badge
```
### Icon
Use the `icon` prop to show an [Icon](https://ui.nuxt.com/components/icon) inside the Badge.
```vue
Badge
```
Use the `leading` and `trailing` props to set the icon position or the `leading-icon` and `trailing-icon` props to set a different icon for each position.
```vue
Badge
```
### Avatar
Use the `avatar` prop to show an [Avatar](https://ui.nuxt.com/components/avatar) inside the Badge.
```vue
Badge
```
## Examples
### `class` prop
Use the `class` prop to override the base styles of the Badge.
```vue
Badge
```
## API
### Props
```ts
/**
* Props for the Badge component
*/
interface BadgeProps {
/**
* The element or component this component should render as.
* @default "\"span\""
*/
as?: any;
label?: string | number | undefined;
color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral" | undefined;
variant?: "solid" | "outline" | "soft" | "subtle" | undefined;
size?: "md" | "xs" | "sm" | "lg" | "xl" | undefined;
/**
* Render the badge with equal padding on all sides.
*/
square?: boolean | undefined;
ui?: { base?: ClassNameValue; label?: ClassNameValue; leadingIcon?: ClassNameValue; leadingAvatar?: ClassNameValue; leadingAvatarSize?: ClassNameValue; trailingIcon?: ClassNameValue; } | undefined;
/**
* Display an icon based on the `leading` and `trailing` props.
*/
icon?: string | undefined;
/**
* Display an avatar on the left side.
*/
avatar?: AvatarProps | undefined;
/**
* When `true`, the icon will be displayed on the left side.
*/
leading?: boolean | undefined;
/**
* Display an icon on the left side.
*/
leadingIcon?: string | undefined;
/**
* When `true`, the icon will be displayed on the right side.
*/
trailing?: boolean | undefined;
/**
* Display an icon on the right side.
*/
trailingIcon?: string | undefined;
}
```
### Slots
```ts
/**
* Slots for the Badge component
*/
interface BadgeSlots {
leading(): any;
default(): any;
trailing(): any;
}
```
## Theme
```ts [app.config.ts]
export default defineAppConfig({
ui: {
badge: {
slots: {
base: 'font-medium inline-flex items-center',
label: 'truncate',
leadingIcon: 'shrink-0',
leadingAvatar: 'shrink-0',
leadingAvatarSize: '',
trailingIcon: 'shrink-0'
},
variants: {
buttonGroup: {
horizontal: 'not-only:first:rounded-e-none not-only:last:rounded-s-none not-last:not-first:rounded-none focus-visible:z-[1]',
vertical: 'not-only:first:rounded-b-none not-only:last:rounded-t-none not-last:not-first:rounded-none focus-visible:z-[1]'
},
color: {
primary: '',
secondary: '',
success: '',
info: '',
warning: '',
error: '',
neutral: ''
},
variant: {
solid: '',
outline: '',
soft: '',
subtle: ''
},
size: {
xs: {
base: 'text-[8px]/3 px-1 py-0.5 gap-1 rounded-sm',
leadingIcon: 'size-3',
leadingAvatarSize: '3xs',
trailingIcon: 'size-3'
},
sm: {
base: 'text-[10px]/3 px-1.5 py-1 gap-1 rounded-sm',
leadingIcon: 'size-3',
leadingAvatarSize: '3xs',
trailingIcon: 'size-3'
},
md: {
base: 'text-xs px-2 py-1 gap-1 rounded-md',
leadingIcon: 'size-4',
leadingAvatarSize: '3xs',
trailingIcon: 'size-4'
},
lg: {
base: 'text-sm px-2 py-1 gap-1.5 rounded-md',
leadingIcon: 'size-5',
leadingAvatarSize: '2xs',
trailingIcon: 'size-5'
},
xl: {
base: 'text-base px-2.5 py-1 gap-1.5 rounded-md',
leadingIcon: 'size-6',
leadingAvatarSize: '2xs',
trailingIcon: 'size-6'
}
},
square: {
true: ''
}
},
compoundVariants: [
{
color: 'primary',
variant: 'solid',
class: 'bg-primary text-inverted'
},
{
color: 'secondary',
variant: 'solid',
class: 'bg-secondary text-inverted'
},
{
color: 'success',
variant: 'solid',
class: 'bg-success text-inverted'
},
{
color: 'info',
variant: 'solid',
class: 'bg-info text-inverted'
},
{
color: 'warning',
variant: 'solid',
class: 'bg-warning text-inverted'
},
{
color: 'error',
variant: 'solid',
class: 'bg-error text-inverted'
},
{
color: 'primary',
variant: 'outline',
class: 'text-primary ring ring-inset ring-primary/50'
},
{
color: 'secondary',
variant: 'outline',
class: 'text-secondary ring ring-inset ring-secondary/50'
},
{
color: 'success',
variant: 'outline',
class: 'text-success ring ring-inset ring-success/50'
},
{
color: 'info',
variant: 'outline',
class: 'text-info ring ring-inset ring-info/50'
},
{
color: 'warning',
variant: 'outline',
class: 'text-warning ring ring-inset ring-warning/50'
},
{
color: 'error',
variant: 'outline',
class: 'text-error ring ring-inset ring-error/50'
},
{
color: 'primary',
variant: 'soft',
class: 'bg-primary/10 text-primary'
},
{
color: 'secondary',
variant: 'soft',
class: 'bg-secondary/10 text-secondary'
},
{
color: 'success',
variant: 'soft',
class: 'bg-success/10 text-success'
},
{
color: 'info',
variant: 'soft',
class: 'bg-info/10 text-info'
},
{
color: 'warning',
variant: 'soft',
class: 'bg-warning/10 text-warning'
},
{
color: 'error',
variant: 'soft',
class: 'bg-error/10 text-error'
},
{
color: 'primary',
variant: 'subtle',
class: 'bg-primary/10 text-primary ring ring-inset ring-primary/25'
},
{
color: 'secondary',
variant: 'subtle',
class: 'bg-secondary/10 text-secondary ring ring-inset ring-secondary/25'
},
{
color: 'success',
variant: 'subtle',
class: 'bg-success/10 text-success ring ring-inset ring-success/25'
},
{
color: 'info',
variant: 'subtle',
class: 'bg-info/10 text-info ring ring-inset ring-info/25'
},
{
color: 'warning',
variant: 'subtle',
class: 'bg-warning/10 text-warning ring ring-inset ring-warning/25'
},
{
color: 'error',
variant: 'subtle',
class: 'bg-error/10 text-error ring ring-inset ring-error/25'
},
{
color: 'neutral',
variant: 'solid',
class: 'text-inverted bg-inverted'
},
{
color: 'neutral',
variant: 'outline',
class: 'ring ring-inset ring-accented text-default bg-default'
},
{
color: 'neutral',
variant: 'soft',
class: 'text-default bg-elevated'
},
{
color: 'neutral',
variant: 'subtle',
class: 'ring ring-inset ring-accented text-default bg-elevated'
},
{
size: 'xs',
square: true,
class: 'p-0.5'
},
{
size: 'sm',
square: true,
class: 'p-1'
},
{
size: 'md',
square: true,
class: 'p-1'
},
{
size: 'lg',
square: true,
class: 'p-1'
},
{
size: 'xl',
square: true,
class: 'p-1'
}
],
defaultVariants: {
color: 'primary',
variant: 'solid',
size: 'md'
}
}
}
})
```
## Changelog
::component-changelog
::
# Banner
## Usage
### Title
Use the `title` prop to display a title on the Banner.
```vue
```
### Icon
Use the `icon` prop to display an icon on the Banner.
```vue
```
### Color
Use the `color` prop to change the color of the Banner.
```vue
```
### Close
Use the `close` prop to display a [Button](https://ui.nuxt.com/components/button) to dismiss the Banner. Defaults to `false`.
::tip
A `close` event will be emitted when the close button is clicked.
::
```vue [BannerExample.vue]
```
::note
When closed, `banner-${id}` will be stored in the local storage to prevent it from being displayed again. :br For the example above, `banner-example` will be stored in the local storage.
::
### Close Icon
Use the `close-icon` prop to customize the close button [Icon](https://ui.nuxt.com/components/icon). Defaults to `i-lucide-x`.
```vue [BannerExample.vue]
```
::framework-only
#nuxt
:::tip{to="https://ui.nuxt.com/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.close` key.
:::
#vue
:::tip{to="https://ui.nuxt.com/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.close` key.
:::
::
### Actions
Use the `actions` prop to add some [Button](https://ui.nuxt.com/components/button) actions to the Banner.
```vue
```
::note
The action buttons default to `color="neutral"` and `size="xs"`. You can customize these values by passing them directly to each action button.
::
### Link
You can pass any property from the [``](https://nuxt.com/docs/api/components/nuxt-link){rel="nofollow"} component such as `to`, `target`, `rel`, etc.
```vue
```
::note
The `NuxtLink` component will inherit all other attributes you pass to the `User` component.
::
## Examples
### Within `app.vue`
Use the Banner component in your `app.vue` or in a layout:
```vue [app.vue] {3}
```
## API
### Props
```ts
/**
* Props for the Banner component
*/
interface BannerProps {
/**
* The element or component this component should render as.
*/
as?: any;
/**
* A unique id saved to local storage to remember if the banner has been dismissed.
* Change this value to show the banner again.
*/
id?: string | undefined;
/**
* The icon displayed next to the title.
*/
icon?: string | undefined;
title?: string | undefined;
/**
* Display a list of actions next to the title.
* `{ color: 'neutral', size: 'xs' }`{lang="ts-type"}
*/
actions?: ButtonProps[] | undefined;
to?: string | RouteLocationAsRelativeGeneric | RouteLocationAsPathGeneric | undefined;
target?: "_blank" | "_parent" | "_self" | "_top" | (string & {}) | null | undefined;
color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral" | undefined;
/**
* Display a close button to dismiss the banner.
* `{ size: 'md', color: 'neutral', variant: 'ghost' }`{lang="ts-type"}
*/
close?: boolean | Partial | undefined;
/**
* The icon displayed in the close button.
*/
closeIcon?: string | undefined;
ui?: { root?: ClassNameValue; container?: ClassNameValue; left?: ClassNameValue; center?: ClassNameValue; right?: ClassNameValue; icon?: ClassNameValue; title?: ClassNameValue; actions?: ClassNameValue; close?: ClassNameValue; } | undefined;
}
```
### Slots
```ts
/**
* Slots for the Banner component
*/
interface BannerSlots {
leading(): any;
title(): any;
actions(): any;
close(): any;
}
```
### Emits
```ts
/**
* Emitted events for the Banner component
*/
interface BannerEmits {
close: (payload: []) => void;
}
```
## Theme
```ts [app.config.ts]
export default defineAppConfig({
uiPro: {
banner: {
slots: {
root: [
'relative z-50 w-full',
'transition-colors'
],
container: 'flex items-center justify-between gap-3 h-12',
left: 'hidden lg:flex-1 lg:flex lg:items-center',
center: 'flex items-center gap-1.5 min-w-0',
right: 'lg:flex-1 flex items-center justify-end',
icon: 'size-5 shrink-0 text-inverted pointer-events-none',
title: 'text-sm text-inverted font-medium truncate',
actions: 'flex gap-1.5 shrink-0 isolate',
close: 'text-inverted hover:bg-default/10 focus-visible:bg-default/10 -me-1.5 lg:me-0'
},
variants: {
color: {
primary: {
root: 'bg-primary'
},
secondary: {
root: 'bg-secondary'
},
success: {
root: 'bg-success'
},
info: {
root: 'bg-info'
},
warning: {
root: 'bg-warning'
},
error: {
root: 'bg-error'
},
neutral: {
root: 'bg-inverted'
}
},
to: {
true: ''
}
},
compoundVariants: [
{
color: 'primary',
to: true,
class: {
root: 'hover:bg-primary/90'
}
},
{
color: 'secondary',
to: true,
class: {
root: 'hover:bg-secondary/90'
}
},
{
color: 'success',
to: true,
class: {
root: 'hover:bg-success/90'
}
},
{
color: 'info',
to: true,
class: {
root: 'hover:bg-info/90'
}
},
{
color: 'warning',
to: true,
class: {
root: 'hover:bg-warning/90'
}
},
{
color: 'error',
to: true,
class: {
root: 'hover:bg-error/90'
}
},
{
color: 'neutral',
to: true,
class: {
root: 'hover:bg-inverted/90'
}
}
],
defaultVariants: {
color: 'primary'
}
}
}
})
```
# BlogPost
## Usage
The BlogPost component provides a flexible way to display an `` element with customizable content including title, description, image, etc.
::code-preview
:::u-blog-post
---
authors:
- name: Anthony Fu
description: antfu7
avatar:
src: https://github.com/antfu.png
to: https://github.com/antfu
target: _blank
class: w-96
date: 2024-11-25
description: Discover Nuxt Icon v1 - a modern, versatile, and customizable icon
solution for your Nuxt projects.
image: https://nuxt.com/assets/blog/nuxt-icon/cover.png
target: _blank
title: Introducing Nuxt Icon v1
to: https://nuxt.com/blog/nuxt-icon-v1-0
---
:::
::
::tip{to="https://ui.nuxt.com/components/blog-posts"}
Use the [`BlogPosts`](https://ui.nuxt.com/components/blog-posts) component to display multiple blog posts in a responsive grid layout.
::
### Title
Use the `title` prop to display the title of the BlogPost.
```vue
```
### Description
Use the `description` prop to display the description of the BlogPost.
```vue
```
### Date
Use the `date` prop to display the date of the BlogPost.
::tip
The date is automatically formatted to the [current locale](https://ui.nuxt.com/getting-started/i18n/nuxt#locale). You can either pass a `Date` object or a string.
::
```vue
```
### Badge
Use the `badge` prop to display a [Badge](https://ui.nuxt.com/components/badge) in the BlogPost.
```vue
```
You can pass any property from the [Badge](https://ui.nuxt.com/components/badge#props) component to customize it.
```vue
```
### Image
Use the `image` prop to display an image in the BlogPost.
::note
If [`@nuxt/image`](https://image.nuxt.com/get-started/installation){rel="nofollow"} is installed, the `` component will be used instead of the native `img` tag.
::
```vue
```
### Authors
Use the `authors` prop to display a list of [User](https://ui.nuxt.com/components/user) in the BlogPost as an array of objects with the following properties:
- `name?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `description?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `avatar?: Omit`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `chip?: boolean | Omit`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `size?: UserProps['size']`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `orientation?: UserProps['orientation']`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
You can pass any property from the [Link](https://ui.nuxt.com/components/link#props) component such as `to`, `target`, etc.
```vue
```
When the `authors` prop has more than one item, the [AvatarGroup](https://ui.nuxt.com/components/avatar-group) component is used.
```vue
```
### Link
You can pass any property from the [``](https://nuxt.com/docs/api/components/nuxt-link){rel="nofollow"} component such as `to`, `target`, `rel`, etc.
```vue
```
### Variant
Use the `variant` prop to change the style of the BlogPost.
```vue
```
::note
The styling will be different wether you provide a `to` prop or an `image`.
::
### Orientation
Use the `orientation` prop to change the BlogPost orientation. Defaults to `vertical`.
```vue
```
## API
### Props
```ts
/**
* Props for the BlogPost component
*/
interface BlogPostProps {
/**
* The element or component this component should render as.
* @default "\"article\""
*/
as?: any;
title?: string | undefined;
description?: string | undefined;
/**
* The date of the blog post. Can be a string or a Date object.
*/
date?: string | Date | undefined;
/**
* Display a badge on the blog post.
* Can be a string or an object.
* `{ color: 'neutral', variant: 'subtle' }`{lang="ts-type"}
*/
badge?: string | BadgeProps | undefined;
/**
* The authors of the blog post.
*/
authors?: UserProps[] | undefined;
/**
* The image of the blog post. Can be a string or an object.
*/
image?: string | Partial | undefined;
/**
* The orientation of the blog post.
* @default "\"vertical\""
*/
orientation?: "horizontal" | "vertical" | undefined;
variant?: "outline" | "soft" | "subtle" | "ghost" | "naked" | undefined;
to?: string | RouteLocationAsRelativeGeneric | RouteLocationAsPathGeneric | undefined;
target?: "_blank" | "_parent" | "_self" | "_top" | (string & {}) | null | undefined;
onClick?: ((event: MouseEvent) => void | Promise) | undefined;
ui?: { root?: ClassNameValue; header?: ClassNameValue; body?: ClassNameValue; footer?: ClassNameValue; image?: ClassNameValue; title?: ClassNameValue; description?: ClassNameValue; authors?: ClassNameValue; avatar?: ClassNameValue; meta?: ClassNameValue; date?: ClassNameValue; badge?: ClassNameValue; } | undefined;
}
```
### Slots
```ts
/**
* Slots for the BlogPost component
*/
interface BlogPostSlots {
date(): any;
badge(): any;
title(): any;
description(): any;
authors(): any;
header(): any;
body(): any;
footer(): any;
}
```
## Theme
```ts [app.config.ts]
export default defineAppConfig({
uiPro: {
blogPost: {
slots: {
root: 'relative group/blog-post flex flex-col rounded-lg overflow-hidden',
header: 'relative overflow-hidden aspect-[16/9] w-full pointer-events-none',
body: 'min-w-0 flex-1 flex flex-col',
footer: '',
image: 'object-cover object-top w-full h-full',
title: 'text-xl text-pretty font-semibold text-highlighted',
description: 'mt-1 text-base text-pretty',
authors: 'pt-4 mt-auto flex flex-wrap gap-x-3 gap-y-1.5',
avatar: '',
meta: 'flex items-center gap-2 mb-2',
date: 'text-sm',
badge: ''
},
variants: {
orientation: {
horizontal: {
root: 'lg:grid lg:grid-cols-2 lg:items-center gap-x-8',
body: 'justify-center p-4 sm:p-6 lg:px-0'
},
vertical: {
root: 'flex flex-col',
body: 'p-4 sm:p-6'
}
},
variant: {
outline: {
root: 'bg-default ring ring-default',
date: 'text-toned',
description: 'text-muted'
},
soft: {
root: 'bg-elevated/50',
date: 'text-muted',
description: 'text-toned'
},
subtle: {
root: 'bg-elevated/50 ring ring-default',
date: 'text-muted',
description: 'text-toned'
},
ghost: {
date: 'text-toned',
description: 'text-muted',
header: 'shadow-lg rounded-lg'
},
naked: {
root: 'p-0 sm:p-0',
date: 'text-toned',
description: 'text-muted',
header: 'shadow-lg rounded-lg'
}
},
to: {
true: {
root: [
'transition'
],
image: 'transform transition-transform duration-200 group-hover/blog-post:scale-110',
avatar: 'transform transition-transform duration-200 hover:scale-115'
}
},
image: {
true: ''
}
},
compoundVariants: [
{
variant: 'outline',
to: true,
class: {
root: 'hover:bg-elevated/50'
}
},
{
variant: 'soft',
to: true,
class: {
root: 'hover:bg-elevated'
}
},
{
variant: 'subtle',
to: true,
class: {
root: 'hover:bg-elevated hover:ring-accented'
}
},
{
variant: 'ghost',
to: true,
class: {
root: 'hover:bg-elevated/50',
header: [
'group-hover/blog-post:shadow-none',
'transition-all'
]
}
},
{
variant: 'ghost',
to: true,
orientation: 'vertical',
class: {
header: 'group-hover/blog-post:rounded-b-none'
}
},
{
variant: 'ghost',
to: true,
orientation: 'horizontal',
class: {
header: 'group-hover/blog-post:rounded-r-none'
}
},
{
orientation: 'vertical',
image: false,
variant: 'naked',
class: {
body: 'p-0 sm:p-0'
}
}
],
defaultVariants: {
variant: 'outline'
}
}
}
})
```
# BlogPosts
## Usage
The BlogPosts component provides a flexible layout to display a list of [BlogPost](https://ui.nuxt.com/components/blog-post) components using either the default slot or the `posts` prop.
```vue {2,8}
```
### Posts
Use the `posts` prop as an array of objects with the properties of the [BlogPost](https://ui.nuxt.com/components/blog-post#props) component.
```vue
```
### Orientation
Use the `orientation` prop to change the orientation of the BlogPosts. Defaults to `horizontal`.
```vue
```
::tip
When using the `posts` prop instead of the default slot, the `orientation` of the posts is automatically reversed, `horizontal` to `vertical` and vice versa.
::
## Examples
::note
While these examples use [Nuxt Content](https://content.nuxt.com){rel="nofollow"}, the components can be integrated with any content management system.
::
### Within a page
Use the BlogPosts component in a page to create a blog page:
```vue [pages/blog/index.vue] {11-18}
```
::note
In this example, the `posts` are fetched using `queryCollection` from the `@nuxt/content` module.
::
::tip
The `to` prop is overridden here since `@nuxt/content` uses the `path` property.
::
## API
### Props
```ts
/**
* Props for the BlogPosts component
*/
interface BlogPostsProps {
/**
* The element or component this component should render as.
*/
as?: any;
posts?: BlogPostProps[] | undefined;
/**
* The orientation of the blog posts.
* @default "\"horizontal\""
*/
orientation?: "horizontal" | "vertical" | undefined;
}
```
### Slots
```ts
/**
* Slots for the BlogPosts component
*/
interface BlogPostsSlots {
default(): any;
}
```
## Theme
```ts [app.config.ts]
export default defineAppConfig({
uiPro: {
blogPosts: {
base: 'flex flex-col gap-8 lg:gap-y-16',
variants: {
orientation: {
horizontal: 'sm:grid sm:grid-cols-2 lg:grid-cols-3',
vertical: ''
}
}
}
}
})
```
# Breadcrumb
## Usage
### Items
Use the `items` prop as an array of objects with the following properties:
- `label?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `icon?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `avatar?: AvatarProps`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- [`slot?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}](https://ui.nuxt.com/#with-custom-slot)
- `class?: any`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `ui?: { item?: ClassNameValue, link?: ClassNameValue, linkLeadingIcon?: ClassNameValue, linkLeadingAvatar?: ClassNameValue, linkLabel?: ClassNameValue, separator?: ClassNameValue, separatorIcon?: ClassNameValue }`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
You can pass any property from the [Link](https://ui.nuxt.com/components/link#props) component such as `to`, `target`, etc.
```vue
```
::note
A `span` is rendered instead of a link when the `to` property is not defined.
::
### Separator Icon
Use the `separator-icon` prop to customize the [Icon](https://ui.nuxt.com/components/icon) between each item. Defaults to `i-lucide-chevron-right`.
```vue
```
::framework-only
#nuxt
:::tip{to="https://ui.nuxt.com/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.chevronRight` key.
:::
#vue
:::tip{to="https://ui.nuxt.com/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.chevronRight` key.
:::
::
## Examples
### With separator slot
Use the `#separator` slot to customize the separator between each item.
```vue [BreadcrumbSeparatorSlotExample.vue]
/
```
### With custom slot
Use the `slot` property to customize a specific item.
You will have access to the following slots:
- `#{{ item.slot }}`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `#{{ item.slot }}-leading`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `#{{ item.slot }}-label`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `#{{ item.slot }}-trailing`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
```vue [BreadcrumbCustomSlotExample.vue]
```
::tip{to="https://ui.nuxt.com/#slots"}
You can also use the `#item`, `#item-leading`, `#item-label` and `#item-trailing` slots to customize all items.
::
## API
### Props
```ts
/**
* Props for the Breadcrumb component
*/
interface BreadcrumbProps {
/**
* The element or component this component should render as.
* @default "\"nav\""
*/
as?: any;
items?: BreadcrumbItem[] | undefined;
/**
* The icon to use as a separator.
*/
separatorIcon?: string | undefined;
/**
* The key used to get the label from the item.
* @default "\"label\""
*/
labelKey?: string | undefined;
ui?: { root?: ClassNameValue; list?: ClassNameValue; item?: ClassNameValue; link?: ClassNameValue; linkLeadingIcon?: ClassNameValue; linkLeadingAvatar?: ClassNameValue; linkLeadingAvatarSize?: ClassNameValue; linkLabel?: ClassNameValue; separator?: ClassNameValue; separatorIcon?: ClassNameValue; } | undefined;
}
```
### Slots
```ts
/**
* Slots for the Breadcrumb component
*/
interface BreadcrumbSlots {
item(): any;
item-leading(): any;
item-label(): any;
item-trailing(): any;
separator(): any;
}
```
## Theme
```ts [app.config.ts]
export default defineAppConfig({
ui: {
breadcrumb: {
slots: {
root: 'relative min-w-0',
list: 'flex items-center gap-1.5',
item: 'flex min-w-0',
link: 'group relative flex items-center gap-1.5 text-sm min-w-0 focus-visible:outline-primary',
linkLeadingIcon: 'shrink-0 size-5',
linkLeadingAvatar: 'shrink-0',
linkLeadingAvatarSize: '2xs',
linkLabel: 'truncate',
separator: 'flex',
separatorIcon: 'shrink-0 size-5 text-muted'
},
variants: {
active: {
true: {
link: 'text-primary font-semibold'
},
false: {
link: 'text-muted font-medium'
}
},
disabled: {
true: {
link: 'cursor-not-allowed opacity-75'
}
},
to: {
true: ''
}
},
compoundVariants: [
{
disabled: false,
active: false,
to: true,
class: {
link: [
'hover:text-default',
'transition-colors'
]
}
}
]
}
}
})
```
## Changelog
::component-changelog
::
# Button
## Usage
### Label
Use the default slot to set the label of the Button.
```vue
Button
```
You can achieve the same result by using the `label` prop.
```vue
```
### Color
Use the `color` prop to change the color of the Button.
```vue
Button
```
### Variant
Use the `variant` prop to change the variant of the Button.
```vue
Button
```
### Size
Use the `size` prop to change the size of the Button.
```vue
Button
```
### Icon
Use the `icon` prop to show an [Icon](https://ui.nuxt.com/components/icon) inside the Button.
```vue
Button
```
Use the `leading` and `trailing` props to set the icon position or the `leading-icon` and `trailing-icon` props to set a different icon for each position.
```vue
Button
```
The `label` as prop or slot is optional so you can use the Button as an icon-only button.
```vue
```
### Avatar
Use the `avatar` prop to show an [Avatar](https://ui.nuxt.com/components/avatar) inside the Button.
```vue
Button
```
The `label` as prop or slot is optional so you can use the Button as an avatar-only button.
```vue
```
### Link
You can pass any property from the [Link](https://ui.nuxt.com/components/link#props) component such as `to`, `target`, etc.
```vue
Button
```
When the Button is a link or when using the `active` prop, you can use the `active-color` and `active-variant` props to customize the active state.
```vue
Button
```
You can also use the `active-class` and `inactive-class` props to customize the active state.
```vue
Button
```
::tip
You can configure these styles globally in your `app.config.ts` file under the `ui.button.variants.active` key.
```ts
export default defineAppConfig({
ui: {
button: {
variants: {
active: {
true: {
base: 'font-bold'
}
}
}
}
}
})
```
::
### Loading
Use the `loading` prop to show a loading icon and disable the Button.
```vue
Button
```
Use the `loading-auto` prop to show the loading icon automatically while the `@click` promise is pending.
```vue [ButtonLoadingAutoExample.vue]
Button
```
This also works with the [Form](https://ui.nuxt.com/components/form) component.
```vue [ButtonLoadingAutoFormExample.vue]
Submit
```
### Loading Icon
Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-loader-circle`.
```vue
Button
```
::framework-only
#nuxt
:::tip{to="https://ui.nuxt.com/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.loading` key.
:::
#vue
:::tip{to="https://ui.nuxt.com/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.loading` key.
:::
::
### Disabled
Use the `disabled` prop to disable the Button.
```vue
Button
```
## Examples
### `class` prop
Use the `class` prop to override the base styles of the Button.
```vue
Button
```
### `ui` prop
Use the `ui` prop to override the slots styles of the Button.
```vue
Button
```
## API
### Props
```ts
/**
* Props for the Button component
*/
interface ButtonProps {
label?: string | undefined;
color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral" | undefined;
activeColor?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral" | undefined;
variant?: "link" | "solid" | "outline" | "soft" | "subtle" | "ghost" | undefined;
activeVariant?: "link" | "solid" | "outline" | "soft" | "subtle" | "ghost" | undefined;
size?: "md" | "xs" | "sm" | "lg" | "xl" | undefined;
/**
* Render the button with equal padding on all sides.
*/
square?: boolean | undefined;
/**
* Render the button full width.
*/
block?: boolean | undefined;
/**
* Set loading state automatically based on the `@click` promise state
*/
loadingAuto?: boolean | undefined;
onClick?: ((event: MouseEvent) => void | Promise) | ((event: MouseEvent) => void | Promise)[] | undefined;
ui?: { base?: ClassNameValue; label?: ClassNameValue; leadingIcon?: ClassNameValue; leadingAvatar?: ClassNameValue; leadingAvatarSize?: ClassNameValue; trailingIcon?: ClassNameValue; } | undefined;
/**
* Display an icon based on the `leading` and `trailing` props.
*/
icon?: string | undefined;
/**
* Display an avatar on the left side.
*/
avatar?: AvatarProps | undefined;
/**
* When `true`, the icon will be displayed on the left side.
*/
leading?: boolean | undefined;
/**
* Display an icon on the left side.
*/
leadingIcon?: string | undefined;
/**
* When `true`, the icon will be displayed on the right side.
*/
trailing?: boolean | undefined;
/**
* Display an icon on the right side.
*/
trailingIcon?: string | undefined;
/**
* When `true`, the loading icon will be displayed.
*/
loading?: boolean | undefined;
/**
* The icon when the `loading` prop is `true`.
*/
loadingIcon?: string | undefined;
/**
* The element or component this component should render as when not a link.
*/
as?: any;
/**
* Calls `router.replace` instead of `router.push`.
*/
replace?: boolean | undefined;
/**
* Route Location the link should navigate to when clicked on.
*/
to?: string | RouteLocationAsRelativeGeneric | RouteLocationAsPathGeneric | undefined;
/**
* Class to apply when the link is active
*/
activeClass?: string | undefined;
/**
* Class to apply when the link is exact active
*/
exactActiveClass?: string | undefined;
/**
* Value passed to the attribute `aria-current` when the link is exact active.
*/
ariaCurrentValue?: "page" | "step" | "location" | "date" | "time" | "true" | "false" | undefined;
/**
* Pass the returned promise of `router.push()` to `document.startViewTransition()` if supported.
*/
viewTransition?: boolean | undefined;
/**
* The type of the button when not a link.
*/
type?: "reset" | "submit" | "button" | undefined;
disabled?: boolean | undefined;
/**
* Force the link to be active independent of the current route.
*/
active?: boolean | undefined;
/**
* Will only be active if the current route is an exact match.
*/
exact?: boolean | undefined;
/**
* Allows controlling how the current route query sets the link as active.
*/
exactQuery?: boolean | "partial" | undefined;
/**
* Will only be active if the current route hash is an exact match.
*/
exactHash?: boolean | undefined;
/**
* The class to apply when the link is inactive.
*/
inactiveClass?: string | undefined;
/**
* An alias for `to`. If used with `to`, `href` will be ignored
*/
href?: string | RouteLocationAsRelativeGeneric | RouteLocationAsPathGeneric | undefined;
/**
* Forces the link to be considered as external (true) or internal (false). This is helpful to handle edge-cases
*/
external?: boolean | undefined;
/**
* Where to display the linked URL, as the name for a browsing context.
*/
target?: "_blank" | "_parent" | "_self" | "_top" | (string & {}) | null | undefined;
/**
* A rel attribute value to apply on the link. Defaults to "noopener noreferrer" for external links.
*/
rel?: (string & {}) | "noopener" | "noreferrer" | "nofollow" | "sponsored" | "ugc" | null | undefined;
/**
* If set to true, no rel attribute will be added to the link
*/
noRel?: boolean | undefined;
/**
* A class to apply to links that have been prefetched.
*/
prefetchedClass?: string | undefined;
/**
* When enabled will prefetch middleware, layouts and payloads of links in the viewport.
*/
prefetch?: boolean | undefined;
/**
* Allows controlling when to prefetch links. By default, prefetch is triggered only on visibility.
*/
prefetchOn?: "visibility" | "interaction" | Partial<{ visibility: boolean; interaction: boolean; }> | undefined;
/**
* Escape hatch to disable `prefetch` attribute.
*/
noPrefetch?: boolean | undefined;
}
```
::callout
---
icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v3/src/runtime/components/Link.vue#L13
---
The `Button` component extends the `Link` component. Check out the source code on GitHub.
::
### Slots
```ts
/**
* Slots for the Button component
*/
interface ButtonSlots {
leading(): any;
default(): any;
trailing(): any;
}
```
## Theme
```ts [app.config.ts]
export default defineAppConfig({
ui: {
button: {
slots: {
base: [
'rounded-md font-medium inline-flex items-center disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75',
'transition-colors'
],
label: 'truncate',
leadingIcon: 'shrink-0',
leadingAvatar: 'shrink-0',
leadingAvatarSize: '',
trailingIcon: 'shrink-0'
},
variants: {
buttonGroup: {
horizontal: 'not-only:first:rounded-e-none not-only:last:rounded-s-none not-last:not-first:rounded-none focus-visible:z-[1]',
vertical: 'not-only:first:rounded-b-none not-only:last:rounded-t-none not-last:not-first:rounded-none focus-visible:z-[1]'
},
color: {
primary: '',
secondary: '',
success: '',
info: '',
warning: '',
error: '',
neutral: ''
},
variant: {
solid: '',
outline: '',
soft: '',
subtle: '',
ghost: '',
link: ''
},
size: {
xs: {
base: 'px-2 py-1 text-xs gap-1',
leadingIcon: 'size-4',
leadingAvatarSize: '3xs',
trailingIcon: 'size-4'
},
sm: {
base: 'px-2.5 py-1.5 text-xs gap-1.5',
leadingIcon: 'size-4',
leadingAvatarSize: '3xs',
trailingIcon: 'size-4'
},
md: {
base: 'px-2.5 py-1.5 text-sm gap-1.5',
leadingIcon: 'size-5',
leadingAvatarSize: '2xs',
trailingIcon: 'size-5'
},
lg: {
base: 'px-3 py-2 text-sm gap-2',
leadingIcon: 'size-5',
leadingAvatarSize: '2xs',
trailingIcon: 'size-5'
},
xl: {
base: 'px-3 py-2 text-base gap-2',
leadingIcon: 'size-6',
leadingAvatarSize: 'xs',
trailingIcon: 'size-6'
}
},
block: {
true: {
base: 'w-full justify-center',
trailingIcon: 'ms-auto'
}
},
square: {
true: ''
},
leading: {
true: ''
},
trailing: {
true: ''
},
loading: {
true: ''
},
active: {
true: {
base: ''
},
false: {
base: ''
}
}
},
compoundVariants: [
{
color: 'primary',
variant: 'solid',
class: 'text-inverted bg-primary hover:bg-primary/75 active:bg-primary/75 disabled:bg-primary aria-disabled:bg-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary'
},
{
color: 'secondary',
variant: 'solid',
class: 'text-inverted bg-secondary hover:bg-secondary/75 active:bg-secondary/75 disabled:bg-secondary aria-disabled:bg-secondary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-secondary'
},
{
color: 'success',
variant: 'solid',
class: 'text-inverted bg-success hover:bg-success/75 active:bg-success/75 disabled:bg-success aria-disabled:bg-success focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-success'
},
{
color: 'info',
variant: 'solid',
class: 'text-inverted bg-info hover:bg-info/75 active:bg-info/75 disabled:bg-info aria-disabled:bg-info focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-info'
},
{
color: 'warning',
variant: 'solid',
class: 'text-inverted bg-warning hover:bg-warning/75 active:bg-warning/75 disabled:bg-warning aria-disabled:bg-warning focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-warning'
},
{
color: 'error',
variant: 'solid',
class: 'text-inverted bg-error hover:bg-error/75 active:bg-error/75 disabled:bg-error aria-disabled:bg-error focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-error'
},
{
color: 'primary',
variant: 'outline',
class: 'ring ring-inset ring-primary/50 text-primary hover:bg-primary/10 active:bg-primary/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent focus:outline-none focus-visible:ring-2 focus-visible:ring-primary'
},
{
color: 'secondary',
variant: 'outline',
class: 'ring ring-inset ring-secondary/50 text-secondary hover:bg-secondary/10 active:bg-secondary/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent focus:outline-none focus-visible:ring-2 focus-visible:ring-secondary'
},
{
color: 'success',
variant: 'outline',
class: 'ring ring-inset ring-success/50 text-success hover:bg-success/10 active:bg-success/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent focus:outline-none focus-visible:ring-2 focus-visible:ring-success'
},
{
color: 'info',
variant: 'outline',
class: 'ring ring-inset ring-info/50 text-info hover:bg-info/10 active:bg-info/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent focus:outline-none focus-visible:ring-2 focus-visible:ring-info'
},
{
color: 'warning',
variant: 'outline',
class: 'ring ring-inset ring-warning/50 text-warning hover:bg-warning/10 active:bg-warning/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent focus:outline-none focus-visible:ring-2 focus-visible:ring-warning'
},
{
color: 'error',
variant: 'outline',
class: 'ring ring-inset ring-error/50 text-error hover:bg-error/10 active:bg-error/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent focus:outline-none focus-visible:ring-2 focus-visible:ring-error'
},
{
color: 'primary',
variant: 'soft',
class: 'text-primary bg-primary/10 hover:bg-primary/15 active:bg-primary/15 focus:outline-none focus-visible:bg-primary/15 disabled:bg-primary/10 aria-disabled:bg-primary/10'
},
{
color: 'secondary',
variant: 'soft',
class: 'text-secondary bg-secondary/10 hover:bg-secondary/15 active:bg-secondary/15 focus:outline-none focus-visible:bg-secondary/15 disabled:bg-secondary/10 aria-disabled:bg-secondary/10'
},
{
color: 'success',
variant: 'soft',
class: 'text-success bg-success/10 hover:bg-success/15 active:bg-success/15 focus:outline-none focus-visible:bg-success/15 disabled:bg-success/10 aria-disabled:bg-success/10'
},
{
color: 'info',
variant: 'soft',
class: 'text-info bg-info/10 hover:bg-info/15 active:bg-info/15 focus:outline-none focus-visible:bg-info/15 disabled:bg-info/10 aria-disabled:bg-info/10'
},
{
color: 'warning',
variant: 'soft',
class: 'text-warning bg-warning/10 hover:bg-warning/15 active:bg-warning/15 focus:outline-none focus-visible:bg-warning/15 disabled:bg-warning/10 aria-disabled:bg-warning/10'
},
{
color: 'error',
variant: 'soft',
class: 'text-error bg-error/10 hover:bg-error/15 active:bg-error/15 focus:outline-none focus-visible:bg-error/15 disabled:bg-error/10 aria-disabled:bg-error/10'
},
{
color: 'primary',
variant: 'subtle',
class: 'text-primary ring ring-inset ring-primary/25 bg-primary/10 hover:bg-primary/15 active:bg-primary/15 disabled:bg-primary/10 aria-disabled:bg-primary/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary'
},
{
color: 'secondary',
variant: 'subtle',
class: 'text-secondary ring ring-inset ring-secondary/25 bg-secondary/10 hover:bg-secondary/15 active:bg-secondary/15 disabled:bg-secondary/10 aria-disabled:bg-secondary/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-secondary'
},
{
color: 'success',
variant: 'subtle',
class: 'text-success ring ring-inset ring-success/25 bg-success/10 hover:bg-success/15 active:bg-success/15 disabled:bg-success/10 aria-disabled:bg-success/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-success'
},
{
color: 'info',
variant: 'subtle',
class: 'text-info ring ring-inset ring-info/25 bg-info/10 hover:bg-info/15 active:bg-info/15 disabled:bg-info/10 aria-disabled:bg-info/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-info'
},
{
color: 'warning',
variant: 'subtle',
class: 'text-warning ring ring-inset ring-warning/25 bg-warning/10 hover:bg-warning/15 active:bg-warning/15 disabled:bg-warning/10 aria-disabled:bg-warning/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-warning'
},
{
color: 'error',
variant: 'subtle',
class: 'text-error ring ring-inset ring-error/25 bg-error/10 hover:bg-error/15 active:bg-error/15 disabled:bg-error/10 aria-disabled:bg-error/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-error'
},
{
color: 'primary',
variant: 'ghost',
class: 'text-primary hover:bg-primary/10 active:bg-primary/10 focus:outline-none focus-visible:bg-primary/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent'
},
{
color: 'secondary',
variant: 'ghost',
class: 'text-secondary hover:bg-secondary/10 active:bg-secondary/10 focus:outline-none focus-visible:bg-secondary/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent'
},
{
color: 'success',
variant: 'ghost',
class: 'text-success hover:bg-success/10 active:bg-success/10 focus:outline-none focus-visible:bg-success/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent'
},
{
color: 'info',
variant: 'ghost',
class: 'text-info hover:bg-info/10 active:bg-info/10 focus:outline-none focus-visible:bg-info/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent'
},
{
color: 'warning',
variant: 'ghost',
class: 'text-warning hover:bg-warning/10 active:bg-warning/10 focus:outline-none focus-visible:bg-warning/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent'
},
{
color: 'error',
variant: 'ghost',
class: 'text-error hover:bg-error/10 active:bg-error/10 focus:outline-none focus-visible:bg-error/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent'
},
{
color: 'primary',
variant: 'link',
class: 'text-primary hover:text-primary/75 active:text-primary/75 disabled:text-primary aria-disabled:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary'
},
{
color: 'secondary',
variant: 'link',
class: 'text-secondary hover:text-secondary/75 active:text-secondary/75 disabled:text-secondary aria-disabled:text-secondary focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-secondary'
},
{
color: 'success',
variant: 'link',
class: 'text-success hover:text-success/75 active:text-success/75 disabled:text-success aria-disabled:text-success focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-success'
},
{
color: 'info',
variant: 'link',
class: 'text-info hover:text-info/75 active:text-info/75 disabled:text-info aria-disabled:text-info focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-info'
},
{
color: 'warning',
variant: 'link',
class: 'text-warning hover:text-warning/75 active:text-warning/75 disabled:text-warning aria-disabled:text-warning focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-warning'
},
{
color: 'error',
variant: 'link',
class: 'text-error hover:text-error/75 active:text-error/75 disabled:text-error aria-disabled:text-error focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-error'
},
{
color: 'neutral',
variant: 'solid',
class: 'text-inverted bg-inverted hover:bg-inverted/90 active:bg-inverted/90 disabled:bg-inverted aria-disabled:bg-inverted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-inverted'
},
{
color: 'neutral',
variant: 'outline',
class: 'ring ring-inset ring-accented text-default bg-default hover:bg-elevated active:bg-elevated disabled:bg-default aria-disabled:bg-default focus:outline-none focus-visible:ring-2 focus-visible:ring-inverted'
},
{
color: 'neutral',
variant: 'soft',
class: 'text-default bg-elevated hover:bg-accented/75 active:bg-accented/75 focus:outline-none focus-visible:bg-accented/75 disabled:bg-elevated aria-disabled:bg-elevated'
},
{
color: 'neutral',
variant: 'subtle',
class: 'ring ring-inset ring-accented text-default bg-elevated hover:bg-accented/75 active:bg-accented/75 disabled:bg-elevated aria-disabled:bg-elevated focus:outline-none focus-visible:ring-2 focus-visible:ring-inverted'
},
{
color: 'neutral',
variant: 'ghost',
class: 'text-default hover:bg-elevated active:bg-elevated focus:outline-none focus-visible:bg-elevated hover:disabled:bg-transparent dark:hover:disabled:bg-transparent hover:aria-disabled:bg-transparent dark:hover:aria-disabled:bg-transparent'
},
{
color: 'neutral',
variant: 'link',
class: 'text-muted hover:text-default active:text-default disabled:text-muted aria-disabled:text-muted focus:outline-none focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-inverted'
},
{
size: 'xs',
square: true,
class: 'p-1'
},
{
size: 'sm',
square: true,
class: 'p-1.5'
},
{
size: 'md',
square: true,
class: 'p-1.5'
},
{
size: 'lg',
square: true,
class: 'p-2'
},
{
size: 'xl',
square: true,
class: 'p-2'
},
{
loading: true,
leading: true,
class: {
leadingIcon: 'animate-spin'
}
},
{
loading: true,
leading: false,
trailing: true,
class: {
trailingIcon: 'animate-spin'
}
}
],
defaultVariants: {
color: 'primary',
variant: 'solid',
size: 'md'
}
}
}
})
```
## Changelog
::component-changelog
::
# ButtonGroup
## Usage
Wrap multiple [Button](https://ui.nuxt.com/components/button) within a ButtonGroup to group them together.
```vue
```
### Size
Use the `size` prop to change the size of all the buttons.
```vue
```
### Orientation
Use the `orientation` prop to change the orientation of the buttons. Defaults to `horizontal`.
```vue
```
## Examples
### With input
You can use components like [Input](https://ui.nuxt.com/components/input), [InputMenu](https://ui.nuxt.com/components/input-menu), [Select](https://ui.nuxt.com/components/select) [SelectMenu](https://ui.nuxt.com/components/select-menu), etc. within a button group.
```vue
```
### With tooltip
You can use a [Tooltip](https://ui.nuxt.com/components/tooltip) within a button group.
```vue [ButtonGroupTooltipExample.vue]
```
### With dropdown
You can use a [DropdownMenu](https://ui.nuxt.com/components/dropdown-menu) within a button group.
```vue [ButtonGroupDropdownExample.vue]
```
### With badge
You can use a [Badge](https://ui.nuxt.com/components/badge) within a button group.
```vue [ButtonGroupBadgeExample.vue]
```
## API
### Props
```ts
/**
* Props for the ButtonGroup component
*/
interface ButtonGroupProps {
/**
* The element or component this component should render as.
*/
as?: any;
size?: "md" | "xs" | "sm" | "lg" | "xl" | undefined;
/**
* The orientation the buttons are laid out.
* @default "\"horizontal\""
*/
orientation?: "horizontal" | "vertical" | undefined;
ui?: {} | undefined;
}
```
### Slots
```ts
/**
* Slots for the ButtonGroup component
*/
interface ButtonGroupSlots {
default(): any;
}
```
## Theme
```ts [app.config.ts]
export default defineAppConfig({
ui: {
buttonGroup: {
base: 'relative',
variants: {
size: {
xs: '',
sm: '',
md: '',
lg: '',
xl: ''
},
orientation: {
horizontal: 'inline-flex -space-x-px',
vertical: 'flex flex-col -space-y-px'
}
}
}
}
})
```
## Changelog
::component-changelog
::
# Calendar
::note
This component relies on the [`@internationalized/date`](https://react-spectrum.adobe.com/internationalized/date/index.html){rel="nofollow"} package which provides objects and functions for representing and manipulating dates and times in a locale-aware manner.
::
## Usage
Use the `v-model` directive to control the selected date.
```vue
```
Use the `default-value` prop to set the initial value when you do not need to control its state.
```vue
```
### Multiple
Use the `multiple` prop to allow multiple selections.
```vue
```
### Range
Use the `range` prop to select a range of dates.
```vue
```
### Color
Use the `color` prop to change the color of the calendar.
```vue
```
### Size
Use the `size` prop to change the size of the calendar.
```vue
```
### Disabled
Use the `disabled` prop to disable the calendar.
```vue
```
### Number Of Months
Use the `numberOfMonths` prop to change the number of months in the calendar.
```vue
```
### Month Controls
Use the `month-controls` prop to show the month controls. Defaults to `true`.
```vue
```
### Year Controls
Use the `year-controls` prop to show the year controls. Defaults to `true`.
```vue
```
### Fixed Weeks
Use the `fixed-weeks` prop to display the calendar with fixed weeks.
```vue
```
## Examples
### With chip events
Use the [Chip](https://ui.nuxt.com/components/chip) component to add events to specific days.
```vue [CalendarEventsExample.vue]
{{ day.day }}
```
### With disabled dates
Use the `is-date-disabled` prop with a function to mark specific dates as disabled.
```vue [CalendarDisabledDatesExample.vue]
```
### With unavailable dates
Use the `is-date-unavailable` prop with a function to mark specific dates as unavailable.
```vue [CalendarUnavailableDatesExample.vue]
```
### With min/max dates
Use the `min-value` and `max-value` props to limit the dates.
```vue [CalendarMinMaxDatesExample.vue]
```
### With other calendar systems
You can use other calenders from `@internationalized/date` to implement a different calendar system.
```vue [CalendarOtherSystemExample.vue]
```
::note
---
to: https://react-spectrum.adobe.com/internationalized/date/Calendar.html#implementations
---
You can check all the available calendars on `@internationalized/date` docs.
::
### With external controls
You can control the calendar with external controls by manipulating the date passed in the `v-model`.
```vue [CalendarExternalControlsExample.vue]
Prev
Next
```
### As a DatePicker
Use a [Button](https://ui.nuxt.com/components/button) and a [Popover](https://ui.nuxt.com/components/popover) component to create a date picker.
```vue [CalendarDatePickerExample.vue]
{{ modelValue ? df.format(modelValue.toDate(getLocalTimeZone())) : 'Select a date' }}
```
### As a DateRangePicker
Use a [Button](https://ui.nuxt.com/components/button) and a [Popover](https://ui.nuxt.com/components/popover) component to create a date range picker.
```vue [CalendarDateRangePickerExample.vue]
{{ df.format(modelValue.start.toDate(getLocalTimeZone())) }} - {{ df.format(modelValue.end.toDate(getLocalTimeZone())) }}
{{ df.format(modelValue.start.toDate(getLocalTimeZone())) }}
Pick a date
```
## API
### Props
```ts
/**
* Props for the Calendar component
*/
interface CalendarProps {
/**
* The element or component this component should render as.
*/
as?: any;
/**
* The icon to use for the next year control.
*/
nextYearIcon?: string | undefined;
/**
* Configure the next year button.
* `{ color: 'neutral', variant: 'ghost' }`{lang="ts-type"}
*/
nextYear?: ButtonProps | undefined;
/**
* The icon to use for the next month control.
*/
nextMonthIcon?: string | undefined;
/**
* Configure the next month button.
* `{ color: 'neutral', variant: 'ghost' }`{lang="ts-type"}
*/
nextMonth?: ButtonProps | undefined;
/**
* The icon to use for the previous year control.
*/
prevYearIcon?: string | undefined;
/**
* Configure the prev year button.
* `{ color: 'neutral', variant: 'ghost' }`{lang="ts-type"}
*/
prevYear?: ButtonProps | undefined;
/**
* The icon to use for the previous month control.
*/
prevMonthIcon?: string | undefined;
/**
* Configure the prev month button.
* `{ color: 'neutral', variant: 'ghost' }`{lang="ts-type"}
*/
prevMonth?: ButtonProps | undefined;
color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral" | undefined;
size?: "md" | "xs" | "sm" | "lg" | "xl" | undefined;
/**
* Whether or not a range of dates can be selected
*/
range?: boolean | undefined;
/**
* Whether or not multiple dates can be selected
*/
multiple?: boolean | undefined;
/**
* Show month controls
* @default "true"
*/
monthControls?: boolean | undefined;
/**
* Show year controls
* @default "true"
*/
yearControls?: boolean | undefined;
defaultValue?: DateValue | DateRange | DateValue[] | undefined;
modelValue?: DateValue | DateRange | DateValue[] | null | undefined;
ui?: { root?: ClassNameValue; header?: ClassNameValue; body?: ClassNameValue; heading?: ClassNameValue; grid?: ClassNameValue; gridRow?: ClassNameValue; gridWeekDaysRow?: ClassNameValue; gridBody?: ClassNameValue; headCell?: ClassNameValue; cell?: ClassNameValue; cellTrigger?: ClassNameValue; } | undefined;
/**
* The default placeholder date
*/
defaultPlaceholder?: DateValue | undefined;
/**
* The placeholder date, which is used to determine what month to display when no date is selected. This updates as the user navigates the calendar and can be used to programmatically control the calendar view
*/
placeholder?: DateValue | undefined;
/**
* When combined with `isDateUnavailable`, determines whether non-contiguous ranges, i.e. ranges containing unavailable dates, may be selected.
*/
allowNonContiguousRanges?: boolean | undefined;
/**
* This property causes the previous and next buttons to navigate by the number of months displayed at once, rather than one month
*/
pagedNavigation?: boolean | undefined;
/**
* Whether or not to prevent the user from deselecting a date without selecting another date first
*/
preventDeselect?: boolean | undefined;
/**
* The maximum number of days that can be selected in a range
*/
maximumDays?: number | undefined;
/**
* The day of the week to start the calendar on
*/
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined;
/**
* The format to use for the weekday strings provided via the weekdays slot prop
*/
weekdayFormat?: WeekDayFormat | undefined;
/**
* Whether or not to always display 6 weeks in the calendar
* @default "true"
*/
fixedWeeks?: boolean | undefined;
/**
* The maximum date that can be selected
*/
maxValue?: DateValue | undefined;
/**
* The minimum date that can be selected
*/
minValue?: DateValue | undefined;
/**
* The number of months to display at once
*/
numberOfMonths?: number | undefined;
/**
* Whether or not the calendar is disabled
*/
disabled?: boolean | undefined;
/**
* Whether or not the calendar is readonly
*/
readonly?: boolean | undefined;
/**
* If true, the calendar will focus the selected day, today, or the first day of the month depending on what is visible when the calendar is mounted
*/
initialFocus?: boolean | undefined;
/**
* A function that returns whether or not a date is disabled
*/
isDateDisabled?: Matcher | undefined;
/**
* A function that returns whether or not a date is unavailable
*/
isDateUnavailable?: Matcher | undefined;
/**
* A function that returns whether or not a date is hightable
*/
isDateHighlightable?: Matcher | undefined;
/**
* A function that returns the next page of the calendar. It receives the current placeholder as an argument inside the component.
*/
nextPage?: ((placeholder: DateValue) => DateValue) | undefined;
/**
* A function that returns the previous page of the calendar. It receives the current placeholder as an argument inside the component.
*/
prevPage?: ((placeholder: DateValue) => DateValue) | undefined;
/**
* Whether or not to disable days outside the current view.
*/
disableDaysOutsideCurrentView?: boolean | undefined;
/**
* Which part of the range should be fixed
*/
fixedDate?: "start" | "end" | undefined;
}
```
### Slots
```ts
/**
* Slots for the Calendar component
*/
interface CalendarSlots {
heading(): any;
day(): any;
week-day(): any;
}
```
### Emits
```ts
/**
* Emitted events for the Calendar component
*/
interface CalendarEmits {
update:modelValue: (payload: [date: DateValue | DateRange | DateValue[] | null | undefined]) => void;
update:placeholder: (payload: [date: DateValue] & [date: DateValue]) => void;
update:validModelValue: (payload: [date: DateRange]) => void;
update:startValue: (payload: [date: DateValue | undefined]) => void;
}
```
## Theme
```ts [app.config.ts]
export default defineAppConfig({
ui: {
calendar: {
slots: {
root: '',
header: 'flex items-center justify-between',
body: 'flex flex-col space-y-4 pt-4 sm:flex-row sm:space-x-4 sm:space-y-0',
heading: 'text-center font-medium truncate mx-auto',
grid: 'w-full border-collapse select-none space-y-1 focus:outline-none',
gridRow: 'grid grid-cols-7 place-items-center',
gridWeekDaysRow: 'mb-1 grid w-full grid-cols-7',
gridBody: 'grid',
headCell: 'rounded-md',
cell: 'relative text-center',
cellTrigger: [
'm-0.5 relative flex items-center justify-center rounded-full whitespace-nowrap focus-visible:ring-2 focus:outline-none data-disabled:text-muted data-unavailable:line-through data-unavailable:text-muted data-unavailable:pointer-events-none data-[selected]:text-inverted data-today:font-semibold data-[outside-view]:text-muted',
'transition'
]
},
variants: {
color: {
primary: {
headCell: 'text-primary',
cellTrigger: 'focus-visible:ring-primary data-[selected]:bg-primary data-today:not-data-[selected]:text-primary data-[highlighted]:bg-primary/20 hover:not-data-[selected]:bg-primary/20'
},
secondary: {
headCell: 'text-secondary',
cellTrigger: 'focus-visible:ring-secondary data-[selected]:bg-secondary data-today:not-data-[selected]:text-secondary data-[highlighted]:bg-secondary/20 hover:not-data-[selected]:bg-secondary/20'
},
success: {
headCell: 'text-success',
cellTrigger: 'focus-visible:ring-success data-[selected]:bg-success data-today:not-data-[selected]:text-success data-[highlighted]:bg-success/20 hover:not-data-[selected]:bg-success/20'
},
info: {
headCell: 'text-info',
cellTrigger: 'focus-visible:ring-info data-[selected]:bg-info data-today:not-data-[selected]:text-info data-[highlighted]:bg-info/20 hover:not-data-[selected]:bg-info/20'
},
warning: {
headCell: 'text-warning',
cellTrigger: 'focus-visible:ring-warning data-[selected]:bg-warning data-today:not-data-[selected]:text-warning data-[highlighted]:bg-warning/20 hover:not-data-[selected]:bg-warning/20'
},
error: {
headCell: 'text-error',
cellTrigger: 'focus-visible:ring-error data-[selected]:bg-error data-today:not-data-[selected]:text-error data-[highlighted]:bg-error/20 hover:not-data-[selected]:bg-error/20'
},
neutral: {
headCell: 'text-highlighted',
cellTrigger: 'focus-visible:ring-inverted data-[selected]:bg-inverted data-today:not-data-[selected]:text-highlighted data-[highlighted]:bg-inverted/20 hover:not-data-[selected]:bg-inverted/10'
}
},
size: {
xs: {
heading: 'text-xs',
cell: 'text-xs',
headCell: 'text-[10px]',
cellTrigger: 'size-7',
body: 'space-y-2 pt-2'
},
sm: {
heading: 'text-xs',
headCell: 'text-xs',
cell: 'text-xs',
cellTrigger: 'size-7'
},
md: {
heading: 'text-sm',
headCell: 'text-xs',
cell: 'text-sm',
cellTrigger: 'size-8'
},
lg: {
heading: 'text-md',
headCell: 'text-md',
cellTrigger: 'size-9 text-md'
},
xl: {
heading: 'text-lg',
headCell: 'text-lg',
cellTrigger: 'size-10 text-lg'
}
}
},
defaultVariants: {
size: 'md',
color: 'primary'
}
}
}
})
```
## Changelog
::component-changelog
::
# Card
## Usage
```vue [CardExample.vue]
```
### Variant
Use the `variant` prop to change the variant of the Card.
```vue
```
## API
### Props
```ts
/**
* Props for the Card component
*/
interface CardProps {
/**
* The element or component this component should render as.
*/
as?: any;
variant?: "solid" | "outline" | "soft" | "subtle" | undefined;
ui?: { root?: ClassNameValue; header?: ClassNameValue; body?: ClassNameValue; footer?: ClassNameValue; } | undefined;
}
```
### Slots
```ts
/**
* Slots for the Card component
*/
interface CardSlots {
header(): any;
default(): any;
footer(): any;
}
```
## Theme
```ts [app.config.ts]
export default defineAppConfig({
ui: {
card: {
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: {
solid: {
root: 'bg-inverted text-inverted'
},
outline: {
root: 'bg-default ring ring-default divide-y divide-default'
},
soft: {
root: 'bg-elevated/50 divide-y divide-default'
},
subtle: {
root: 'bg-elevated/50 ring ring-default divide-y divide-default'
}
}
},
defaultVariants: {
variant: 'outline'
}
}
}
})
```
## Changelog
::component-changelog
::
# Carousel
## Usage
### Items
Use the `items` prop as an array and render each item using the default slot:
::note
Use your mouse to drag the carousel horizontally on desktop.
::
```vue [CarouselItemsExample.vue]
```
You can also pass an array of objects with the following properties:
- `class?: any`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `ui?: { item?: ClassNameValue }`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
You can control how many items are visible by using the [`basis`](https://tailwindcss.com/docs/flex-basis){rel="nofollow"} / [`width`](https://tailwindcss.com/docs/width){rel="nofollow"} utility classes on the `item`:
```vue [CarouselItemsMultipleExample.vue]
```
### Orientation
Use the `orientation` prop to change the orientation of the Progress. Defaults to `horizontal`.
::note
Use your mouse to drag the carousel vertically on desktop.
::
```vue [CarouselOrientationExample.vue]
```
::caution
You need to specify a `height` on the container in vertical orientation.
::
### Arrows
Use the `arrows` prop to display prev and next buttons.
```vue [CarouselArrowsExample.vue]
```
### Prev / Next
Use the `prev` and `next` props to customize the prev and next buttons with any [Button](https://ui.nuxt.com/components/button) props.
```vue [CarouselPrevNextExample.vue]
```
### Prev / Next Icons
Use the `prev-icon` and `next-icon` props to customize the buttons [Icon](https://ui.nuxt.com/components/icon). Defaults to `i-lucide-arrow-left` / `i-lucide-arrow-right`.
```vue [CarouselPrevNextIconExample.vue]
```
::framework-only
#nuxt
:::tip{to="https://ui.nuxt.com/getting-started/icons/nuxt#theme"}
You can customize these icons globally in your `app.config.ts` under `ui.icons.arrowLeft` / `ui.icons.arrowRight` key.
:::
#vue
:::tip{to="https://ui.nuxt.com/getting-started/icons/vue#theme"}
You can customize these icons globally in your `vite.config.ts` under `ui.icons.arrowLeft` / `ui.icons.arrowRight` key.
:::
::
### Dots
Use the `dots` prop to display a list of dots to scroll to a specific slide.
```vue [CarouselDotsExample.vue]
```
The number of dots is based on the number of slides displayed in the view:
```vue [CarouselDotsMultipleExample.vue]
```
## Plugins
The Carousel component implements the official [Embla Carousel plugins](https://www.embla-carousel.com/plugins/){rel="nofollow"}.
### Autoplay
This plugin is used to extend Embla Carousel with **autoplay** functionality.
Use the `autoplay` prop as a boolean or an object to configure the [Autoplay plugin](https://www.embla-carousel.com/plugins/autoplay/){rel="nofollow"}.
```vue [CarouselAutoplayExample.vue]
```
::note
In this example, we're using the `loop` prop for an infinite carousel.
::
### Auto Scroll
This plugin is used to extend Embla Carousel with **auto scroll** functionality.
Use the `auto-scroll` prop as a boolean or an object to configure the [Auto Scroll plugin](https://www.embla-carousel.com/plugins/auto-scroll/){rel="nofollow"}.
```vue [CarouselAutoScrollExample.vue]
```
::note
In this example, we're using the `loop` prop for an infinite carousel.
::
### Auto Height
This plugin is used to extend Embla Carousel with **auto height** functionality. It changes the height of the carousel container to fit the height of the highest slide in view.
Use the `auto-height` prop as a boolean or an object to configure the [Auto Height plugin](https://www.embla-carousel.com/plugins/auto-height/){rel="nofollow"}.
```vue [CarouselAutoHeightExample.vue]
```
::note
In this example, we add the `transition-[height]` class on the container to animate the height change.
::
### Class Names
Class Names is a **class name toggle** utility plugin for Embla Carousel that enables you to automate the toggling of class names on your carousel.
Use the `class-names` prop as a boolean or an object to configure the [Class Names plugin](https://www.embla-carousel.com/plugins/class-names/){rel="nofollow"}.
```vue [CarouselClassNamesExample.vue]
```
::note
In this example, we add the `transition-opacity [&:not(.is-snapped)]:opacity-10` classes on the `item` to animate the opacity change.
::
### Fade
This plugin is used to replace the Embla Carousel scroll functionality with **fade transitions**.
Use the `fade` prop as a boolean or an object to configure the [Fade plugin](https://www.embla-carousel.com/plugins/fade/){rel="nofollow"}.
```vue [CarouselFadeExample.vue]
```
### Wheel Gestures
This plugin is used to extend Embla Carousel with the ability to **use the mouse/trackpad wheel** to navigate the carousel.
Use the `wheel-gestures` prop as a boolean or an object to configure the [Wheel Gestures plugin](https://www.embla-carousel.com/plugins/wheel-gestures/){rel="nofollow"}.
::note
Use your mouse wheel to scroll the carousel.
::
```vue [CarouselWheelGesturesExample.vue]
```
## Examples
### With thumbnails
You can use the [`emblaApi`](https://ui.nuxt.com/#expose) function [scrollTo](https://www.embla-carousel.com/api/methods/#scrollto){rel="nofollow"} to display thumbnails under the carousel that allows you to navigate to a specific slide.
```vue [CarouselThumbnailsExample.vue]
```
## API
### Props
```ts
/**
* Props for the Carousel component
*/
interface CarouselProps {
/**
* The element or component this component should render as.
*/
as?: any;
/**
* Configure the prev button when arrows are enabled.
*/
prev?: ButtonProps | undefined;
/**
* The icon displayed in the prev button.
*/
prevIcon?: string | undefined;
/**
* Configure the next button when arrows are enabled.
*/
next?: ButtonProps | undefined;
/**
* The icon displayed in the next button.
*/
nextIcon?: string | undefined;
/**
* Display prev and next buttons to scroll the carousel.
* @default "false"
*/
arrows?: boolean | undefined;
/**
* Display dots to scroll to a specific slide.
* @default "false"
*/
dots?: boolean | undefined;
/**
* The orientation of the carousel.
* @default "\"horizontal\""
*/
orientation?: "vertical" | "horizontal" | undefined;
items?: CarouselItem[] | undefined;
/**
* Enable Autoplay plugin
* @default "false"
*/
autoplay?: boolean | Partial> | undefined;
/**
* Enable Auto Scroll plugin
* @default "false"
*/
autoScroll?: boolean | Partial> | undefined;
/**
* Enable Auto Height plugin
* @default "false"
*/
autoHeight?: boolean | Partial, "breakpoints">; }; }>> | undefined;
/**
* Enable Class Names plugin
* @default "false"
*/
classNames?: boolean | Partial> | undefined;
/**
* Enable Fade plugin
* @default "false"
*/
fade?: boolean | Partial, "breakpoints">; }; }>> | undefined;
/**
* Enable Wheel Gestures plugin
* @default "false"
*/
wheelGestures?: boolean | WheelGesturesPluginOptions | undefined;
ui?: { root?: ClassNameValue; viewport?: ClassNameValue; container?: ClassNameValue; item?: ClassNameValue; controls?: ClassNameValue; arrows?: ClassNameValue; prev?: ClassNameValue; next?: ClassNameValue; dots?: ClassNameValue; dot?: ClassNameValue; } | undefined;
/**
* @default "\"center\""
*/
align?: AlignmentOptionType | undefined;
/**
* @default "\"trimSnaps\""
*/
containScroll?: ScrollContainOptionType | undefined;
/**
* @default "1"
*/
slidesToScroll?: SlidesToScrollOptionType | undefined;
/**
* @default "false"
*/
dragFree?: boolean | undefined;
/**
* @default "10"
*/
dragThreshold?: number | undefined;
/**
* @default "0"
*/
inViewThreshold?: number | number[] | undefined;
/**
* @default "false"
*/
loop?: boolean | undefined;
/**
* @default "false"
*/
skipSnaps?: boolean | undefined;
/**
* @default "25"
*/
duration?: number | undefined;
/**
* @default "0"
*/
startIndex?: number | undefined;
/**
* @default "true"
*/
watchDrag?: DragHandlerOptionType | undefined;
/**
* @default "true"
*/
watchResize?: ResizeHandlerOptionType | undefined;
/**
* @default "true"
*/
watchSlides?: SlidesHandlerOptionType | undefined;
/**
* @default "true"
*/
watchFocus?: FocusHandlerOptionType | undefined;
/**
* @default "true"
*/
active?: boolean | undefined;
/**
* @default "{}"
*/
breakpoints?: { [key: string]: Omit | null; containScroll: ScrollContainOptionType; direction: AxisDirectionOptionType; slidesToScroll: SlidesToScrollOptionType; dragFree: boolean; dragThreshold: number; inViewThreshold: number | number[] | undefined; loop: boolean; skipSnaps: boolean; duration: number; startIndex: number; watchDrag: DragHandlerOptionType; watchResize: ResizeHandlerOptionType; watchSlides: SlidesHandlerOptionType; watchFocus: FocusHandlerOptionType; }>>, "breakpoints">; } | undefined;
}
```
### Slots
```ts
/**
* Slots for the Carousel component
*/
interface CarouselSlots {
default(): any;
}
```
### Emits
```ts
/**
* Emitted events for the Carousel component
*/
interface CarouselEmits {
select: (payload: [selectedIndex: number]) => void;
}
```
### Expose
You can access the typed component instance using [`useTemplateRef`](https://vuejs.org/api/composition-api-helpers.html#usetemplateref){rel="nofollow"}.
```vue
```
This will give you access to the following:
| Name | Type |
| ------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `emblaRef`{.language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} | `Ref`{.language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} |
| `emblaApi`{.language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} | [`Ref`{.language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}](https://www.embla-carousel.com/api/methods/#typescript){rel="nofollow"} |
## Theme
```ts [app.config.ts]
export default defineAppConfig({
ui: {
carousel: {
slots: {
root: 'relative focus:outline-none',
viewport: 'overflow-hidden',
container: 'flex items-start',
item: 'min-w-0 shrink-0 basis-full',
controls: '',
arrows: '',
prev: 'absolute rounded-full',
next: 'absolute rounded-full',
dots: 'absolute inset-x-0 -bottom-7 flex flex-wrap items-center justify-center gap-3',
dot: [
'cursor-pointer size-3 bg-accented rounded-full',
'transition'
]
},
variants: {
orientation: {
vertical: {
container: 'flex-col -mt-4',
item: 'pt-4',
prev: 'top-4 sm:-top-12 left-1/2 -translate-x-1/2 rotate-90 rtl:-rotate-90',
next: 'bottom-4 sm:-bottom-12 left-1/2 -translate-x-1/2 rotate-90 rtl:-rotate-90'
},
horizontal: {
container: 'flex-row -ms-4',
item: 'ps-4',
prev: 'start-4 sm:-start-12 top-1/2 -translate-y-1/2',
next: 'end-4 sm:-end-12 top-1/2 -translate-y-1/2'
}
},
active: {
true: {
dot: 'data-[state=active]:bg-inverted'
}
}
}
}
}
})
```
## Changelog
::component-changelog
::
# ChangelogVersion
## Usage
The ChangelogVersion component provides a flexible way to display an `` element with customizable content including title, description, image, etc.
::code-preview
:::u-changelog-version
---
authors:
- name: Benjamin Canac
description: "@benjamincanac"
avatar:
src: https://github.com/benjamincanac.png
to: https://x.com/benjamincanac
target: _blank
- name: Sebastien Chopin
description: "@atinux"
avatar:
src: https://github.com/atinux.png
to: https://x.com/atinux
target: _blank
- name: Hugo Richard
description: "@hugorcd__"
avatar:
src: https://github.com/hugorcd.png
to: https://x.com/hugorcd__
target: _blank
ui:
container: max-w-lg
class: w-full
date: 2025-03-12
description: Nuxt UI v3 is out! After 1500+ commits, this major redesign brings
improved accessibility, Tailwind CSS v4 support, and full Vue compatibility.
image: https://nuxt.com/assets/blog/nuxt-ui-v3.png
target: _blank
title: Introducing Nuxt UI v3
to: https://nuxt.com/blog/nuxt-ui-v3
---
:::
::
::tip{to="https://ui.nuxt.com/components/changelog-versions"}
Use the [`ChangelogVersions`](https://ui.nuxt.com/components/changelog-versions) component to display multiple changelog versions in a timeline with an indicator bar on the left.
::
### Title
Use the `title` prop to display the title of the ChangelogVersion.
```vue
```
### Description
Use the `description` prop to display the description of the ChangelogVersion.
```vue
```
### Date
Use the `date` prop to display the date of the ChangelogVersion.
::tip
The date is automatically formatted to the [current locale](https://ui.nuxt.com/getting-started/i18n/nuxt#locale). You can either pass a `Date` object or a string.
::
```vue
```
### Badge
Use the `badge` prop to display a [Badge](https://ui.nuxt.com/components/badge) on the ChangelogVersion.
```vue
```
You can pass any property from the [Badge](https://ui.nuxt.com/components/badge#props) component to customize it.
```vue
```
### Image
Use the `image` prop to display an image in the BlogPost.
::note
If [`@nuxt/image`](https://image.nuxt.com/get-started/installation){rel="nofollow"} is installed, the `` component will be used instead of the native `img` tag.
::
```vue
```
### Authors
Use the `authors` prop to display a list of [User](https://ui.nuxt.com/components/user) in the ChangelogVersion as an array of objects with the following properties:
- `name?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `description?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `avatar?: Omit`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `chip?: boolean | Omit`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `size?: UserProps['size']`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `orientation?: UserProps['orientation']`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
You can pass any property from the [Link](https://ui.nuxt.com/components/link#props) component such as `to`, `target`, etc.
```vue
```
### Link
You can pass any property from the [``](https://nuxt.com/docs/api/components/nuxt-link){rel="nofollow"} component such as `to`, `target`, `rel`, etc.
```vue
```
### Indicator
Use the `indicator` prop to hide the indicator dot on the left. Defaults to `true`.
```vue
```
::note
When the `indicator` prop is `false`, the date will be displayed over the title.
::
## Examples
### With body slot
You can use the `body` slot to display custom content between the image and the authors with:
- the [MDC](https://github.com/nuxt-modules/mdc?tab=readme-ov-file#mdc){rel="nofollow"} component from `@nuxtjs/mdc` to display some markdown.
- the [ContentRenderer](https://content.nuxt.com/docs/components/content-renderer){rel="nofollow"} component from `@nuxt/content` to render the content of the page or list.
- or use the `:u-changelog-version` component directly in your content with markdown inside the `body` slot as Nuxt UI Pro provides pre-styled prose components.
```vue [ChangelogVersionMarkdownExample.vue]
```
## API
### Props
```ts
/**
* Props for the ChangelogVersion component
*/
interface ChangelogVersionProps {
/**
* The element or component this component should render as.
* @default "\"article\""
*/
as?: any;
title?: string | undefined;
description?: string | undefined;
/**
* The date of the changelog version. Can be a string or a Date object.
*/
date?: string | Date | undefined;
/**
* Display a badge on the changelog version.
* Can be a string or an object.
* `{ color: 'neutral', variant: 'solid' }`{lang="ts-type"}
*/
badge?: string | BadgeProps | undefined;
/**
* The authors of the changelog version.
*/
authors?: UserProps[] | undefined;
/**
* The image of the changelog version. Can be a string or an object.
*/
image?: string | Partial | undefined;
/**
* Display an indicator dot on the left.
* @default "true"
*/
indicator?: boolean | undefined;
to?: string | RouteLocationAsRelativeGeneric | RouteLocationAsPathGeneric | undefined;
target?: "_blank" | "_parent" | "_self" | "_top" | (string & {}) | null | undefined;
onClick?: ((event: MouseEvent) => void | Promise) | undefined;
ui?: { root?: ClassNameValue; container?: ClassNameValue; header?: ClassNameValue; meta?: ClassNameValue; date?: ClassNameValue; badge?: ClassNameValue; title?: ClassNameValue; description?: ClassNameValue; imageWrapper?: ClassNameValue; image?: ClassNameValue; authors?: ClassNameValue; footer?: ClassNameValue; indicator?: ClassNameValue; dot?: ClassNameValue; dotInner?: ClassNameValue; } | undefined;
}
```
### Slots
```ts
/**
* Slots for the ChangelogVersion component
*/
interface ChangelogVersionSlots {
header(): any;
badge(): any;
date(): any;
title(): any;
description(): any;
image(): any;
body(): any;
footer(): any;
authors(): any;
actions(): any;
indicator(): any;
}
```
## Theme
```ts [app.config.ts]
export default defineAppConfig({
uiPro: {
changelogVersion: {
slots: {
root: 'relative',
container: 'flex flex-col mx-auto max-w-2xl',
header: '',
meta: 'flex items-center gap-3 mb-2',
date: 'text-sm/6 text-toned truncate',
badge: '',
title: 'relative text-xl text-pretty font-semibold text-highlighted',
description: 'text-base text-pretty text-muted mt-1',
imageWrapper: 'relative overflow-hidden rounded-lg aspect-[16/9] mt-5 group/changelog-version-image',
image: 'object-cover object-top w-full h-full',
authors: 'flex flex-wrap gap-x-4 gap-y-1.5',
footer: 'border-t border-default pt-5 flex items-center justify-between',
indicator: 'absolute left-0 top-0 w-32 hidden lg:flex items-center justify-end gap-3 min-w-0',
dot: 'size-4 rounded-full bg-default ring ring-default flex items-center justify-center my-1',
dotInner: 'size-2 rounded-full bg-primary'
},
variants: {
body: {
false: {
footer: 'mt-5'
}
},
badge: {
false: {
meta: 'lg:hidden'
}
},
to: {
true: {
image: 'transform transition-transform duration-200 group-hover/changelog-version-image:scale-105'
}
},
hidden: {
true: {
date: 'lg:hidden'
}
}
}
}
}
})
```
# ChangelogVersions
## Usage
The ChangelogVersions component provides a flexible layout to display a list of [ChangelogVersion](https://ui.nuxt.com/components/changelog-version) components using either the default slot or the `versions` prop.
```vue {2,8}
```
### Versions
Use the `versions` prop as an array of objects with the properties of the [ChangelogVersion](https://ui.nuxt.com/components/changelog-version#props) component.
```vue
```
### Indicator
Use the `indicator` prop to hide the indicator bar on the left. Defaults to `true`.
```vue
```
### Indicator Motion
Use the `indicator-motion` prop to customize or hide the motion effect on the indicator bar. Defaults to `true` with `{ damping: 30, restDelta: 0.001 }` [spring transition options](https://motion.dev/docs/vue-transitions#spring){rel="nofollow"}.
```vue
```
## Examples
::note
While these examples use [Nuxt Content](https://content.nuxt.com){rel="nofollow"}, the components can be integrated with any content management system.
::
### Within a page
Use the ChangelogVersions component in a page to create a changelog page:
```vue [pages/changelog.vue] {10-17}
```
::note
In this example, the `versions` are fetched using `queryCollection` from the `@nuxt/content` module.
::
::tip
The `to` prop is overridden here since `@nuxt/content` uses the `path` property.
::
### With sticky indicator
You can use the `ui` prop and the different slots to make the indicators sticky:
```vue [ChangelogVersionsStickyExample.vue]
{{ new Date(version.date).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }) }}
```
## API
### Props
```ts
/**
* Props for the ChangelogVersions component
*/
interface ChangelogVersionsProps {
/**
* The element or component this component should render as.
*/
as?: any;
versions?: ChangelogVersionProps[] | undefined;
/**
* Display an indicator bar on the left.
* @default "true"
*/
indicator?: boolean | undefined;
/**
* Enable scrolling motion effect on the indicator bar.
* `{ damping: 30, restDelta: 0.001 }`{lang="ts-type"}
* @default "true"
*/
indicatorMotion?: boolean | SpringOptions | undefined;
ui?: { root?: ClassNameValue; container?: ClassNameValue; indicator?: ClassNameValue; beam?: ClassNameValue; } | undefined;
}
```
### Slots
```ts
/**
* Slots for the ChangelogVersions component
*/
interface ChangelogVersionsSlots {
default(): any;
indicator(): any;
header(): any;
badge(): any;
date(): any;
title(): any;
description(): any;
image(): any;
body(): any;
footer(): any;
authors(): any;
actions(): any;
}
```
::tip
You can use all the slots of the [`ChangelogVersion`](https://ui.nuxt.com/components/changelog-version#slots) component inside ChangelogVersions, they are automatically forwarded allowing you to customize individual versions when using the `versions` prop.
```vue {3-5}
```
::
## Theme
```ts [app.config.ts]
export default defineAppConfig({
uiPro: {
changelogVersions: {
slots: {
root: 'relative',
container: 'flex flex-col gap-y-8 sm:gap-y-12 lg:gap-y-16',
indicator: 'absolute hidden lg:block overflow-hidden inset-y-3 left-32 h-full w-px bg-border -ml-[8.5px]',
beam: 'absolute left-0 top-0 w-full bg-primary will-change-[height]'
}
}
}
})
```
# ChatMessage
## Usage
The ChatMessage component renders an `` element for a `user` or `assistant` chat message.
::code-preview
:::u-chat-message
---
avatar:
src: https://github.com/benjamincanac.png
content: Hello! Tell me more about building AI chatbots with Nuxt UI Pro.
side: right
variant: soft
---
:::
::
::tip{to="https://ui.nuxt.com/components/chat-messages"}
Use the [`ChatMessages`](https://ui.nuxt.com/components/chat-messages) component to display a list of chat messages.
::
### Content
Use the `content` prop to display the message content.
```vue
```
### Side
Use the `side` prop to display the message on the left or right.
```vue
```
::note
When using the [`ChatMessages`](https://ui.nuxt.com/components/chat-messages) component, the `side` prop is set to `left` for `assistant` messages and `right` for `user` messages.
::
### Variant
Use the `variant` prop to change style of the message.
```vue
```
::note
When using the [`ChatMessages`](https://ui.nuxt.com/components/chat-messages) component, the `variant` prop is set to `naked` for `assistant` messages and `soft` for `user` messages.
::
### Icon
Use the `icon` prop to display an [Icon](https://ui.nuxt.com/components/icon) component next to the message.
```vue
```
### Avatar
Use the `avatar` prop to display an [Avatar](https://ui.nuxt.com/components/avatar) component next to the message.
```vue
```
You can also use the `avatar.icon` prop to display an icon as the avatar.
```vue
```
### Actions
Use the `actions` prop to display actions below the message that will be displayed when hovering over the message.
```vue
```
## API
### Props
```ts
/**
* Props for the ChatMessage component
*/
interface ChatMessageProps {
/**
* A unique identifier for the message.
*/
id: string;
/**
* Text content of the message. Use parts when possible.
*/
content: string;
/**
* The 'data' role is deprecated.
*/
role: "data" | "user" | "system" | "assistant";
/**
* The element or component this component should render as.
* @default "\"article\""
*/
as?: any;
icon?: string | undefined;
avatar?: (AvatarProps & { [key: string]: any; }) | undefined;
variant?: "solid" | "outline" | "soft" | "subtle" | "naked" | undefined;
side?: "right" | "left" | undefined;
/**
* Display a list of actions under the message.
* The `label` will be used in a tooltip.
* `{ size: 'xs', color: 'neutral', variant: 'ghost' }`{lang="ts-type"}
*/
actions?: (Omit & { onClick?: ((e: MouseEvent, message: Message) => void) | undefined; })[] | undefined;
/**
* Render the message in a compact style.
* This is done automatically when used inside a `UChatPalette`{lang="ts-type"}.
*/
compact?: boolean | undefined;
ui?: { root?: ClassNameValue; container?: ClassNameValue; leading?: ClassNameValue; leadingIcon?: ClassNameValue; leadingAvatar?: ClassNameValue; leadingAvatarSize?: ClassNameValue; content?: ClassNameValue; actions?: ClassNameValue; } | undefined;
/**
* The timestamp of the message.
*/
createdAt?: Date | undefined;
/**
* Reasoning for the message.
*/
reasoning?: string | undefined;
/**
* Additional attachments to be sent along with the message.
*/
experimental_attachments?: Attachment[] | undefined;
/**
* For data messages.
*/
data?: JSONValue | undefined;
/**
* Additional message-specific information added on the server via StreamData
*/
annotations?: JSONValue[] | undefined;
/**
* Tool invocations (that can be tool calls or tool results, depending on whether or not the invocation has finished)
* that the assistant made as part of this message.
*/
toolInvocations?: ToolInvocation[] | undefined;
/**
* The parts of the message. Use this for rendering the message in the UI.
*
* Assistant messages can have text, reasoning and tool invocation parts.
* User messages can have text parts.
*/
parts?: (TextUIPart | ReasoningUIPart | ToolInvocationUIPart | SourceUIPart | FileUIPart | StepStartUIPart)[] | undefined;
}
```
### Slots
```ts
/**
* Slots for the ChatMessage component
*/
interface ChatMessageSlots {
leading(): any;
content(): any;
actions(): any;
}
```
## Theme
```ts [app.config.ts]
export default defineAppConfig({
uiPro: {
chatMessage: {
slots: {
root: 'group/message relative w-full',
container: 'relative flex items-start group-data-[role=user]/message:max-w-[75%]',
leading: 'inline-flex items-center justify-center min-h-6',
leadingIcon: 'shrink-0',
leadingAvatar: 'shrink-0',
leadingAvatarSize: '',
content: 'relative text-pretty min-w-0',
actions: [
'opacity-0 group-hover/message:opacity-100 absolute bottom-0 flex items-center',
'transition-opacity'
]
},
variants: {
variant: {
solid: {
content: 'bg-inverted text-inverted'
},
outline: {
content: 'bg-default ring ring-default'
},
soft: {
content: 'bg-elevated/50'
},
subtle: {
content: 'bg-elevated/50 ring ring-default'
},
naked: {
content: ''
}
},
side: {
left: {
container: 'rtl:justify-end'
},
right: {
container: 'ltr:justify-end ms-auto'
}
},
leading: {
true: ''
},
actions: {
true: ''
},
compact: {
true: {
root: 'scroll-mt-3',
container: 'gap-1.5 pb-3',
leadingIcon: 'size-5',
leadingAvatarSize: '2xs'
},
false: {
root: 'scroll-mt-4 sm:scroll-mt-6',
container: 'gap-3 pb-8',
leadingIcon: 'size-8',
leadingAvatarSize: 'md'
}
}
},
compoundVariants: [
{
compact: true,
actions: true,
class: {
container: 'pb-8'
}
},
{
leading: true,
compact: false,
side: 'left',
class: {
actions: 'left-11'
}
},
{
leading: true,
compact: true,
side: 'left',
class: {
actions: 'left-6.5'
}
},
{
variant: [
'solid',
'outline',
'soft',
'subtle'
],
compact: false,
class: {
content: 'px-4 py-3 rounded-lg min-h-12',
leading: 'mt-2'
}
},
{
variant: [
'solid',
'outline',
'soft',
'subtle'
],
compact: true,
class: {
content: 'px-2 py-1 rounded-lg min-h-8',
leading: 'mt-1'
}
}
],
defaultVariants: {
variant: 'naked'
}
}
}
})
```
# ChatMessages
## Usage
The ChatMessages component displays a list of [ChatMessage](https://ui.nuxt.com/components/chat-message) components using either the default slot or the `messages` prop.
```vue {2,8}
```
::callout{icon="i-lucide-rocket"}
This component is purpose-built for AI chatbots with features like:
- Initial scroll to the bottom upon loading ([`shouldScrollToBottom`](https://ui.nuxt.com/#should-scroll-to-bottom)).
- Continuous scrolling down as new messages arrive ([`shouldAutoScroll`](https://ui.nuxt.com/#should-auto-scroll)).
- An "Auto scroll" button appears when scrolled up, allowing users to jump back to the latest messages ([`autoScroll`](https://ui.nuxt.com/#auto-scroll)).
- A loading indicator displays while the assistant is processing ([`status`](https://ui.nuxt.com/#status)).
- Submitted messages are scrolled to the top of the viewport and the height of the last user message is dynamically adjusted.
::
### Messages
Use the `messages` prop to display a list of chat messages.
```vue
```
### Status
Use the `status` prop to display a visual indicator when the assistant is processing.
```vue
```
::note
Here's the detail of the different statuses sent by the `useChat` composable:
- `submitted`: The message has been sent to the API and we're awaiting the start of the response stream.
- `streaming`: The response is actively streaming in from the API, receiving chunks of data.
- `ready`: The full response has been received and processed; a new user message can be submitted.
- `error`: An error occurred during the API request, preventing successful completion.
::
### User
Use the `user` prop to change the [ChatMessage](https://ui.nuxt.com/components/chat-message) props for `user` messages. Defaults to:
- `side: 'right'`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `variant: 'soft'`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
```vue
```
### Assistant
Use the `assistant` prop to change the [ChatMessage](https://ui.nuxt.com/components/chat-message) props for `assistant` messages. Defaults to:
- `side: 'left'`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `variant: 'naked'`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
```vue
```
### Auto Scroll
Use the `auto-scroll` prop to customize or hide the auto scroll button (with `false` value) displayed when scrolling to the top of the chat. Defaults to:
- `color: 'neutral'`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
- `variant: 'outline'`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}
You can pass any property from the [Button](https://ui.nuxt.com/components/button) component to customize it.
```vue
```
### Auto Scroll Icon
Use the `auto-scroll-icon` prop to customize the auto scroll button [Icon](https://ui.nuxt.com/components/icon). Defaults to `i-lucide-arrow-down`.
```vue
```
::framework-only
#nuxt
:::tip{to="https://ui.nuxt.com/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.arrowDown` key.
:::
#vue
:::tip{to="https://ui.nuxt.com/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.arrowDown` key.
:::
::
### Should Auto Scroll
Use the `should-auto-scroll` prop to enable/disable continuous auto scroll while messages are streaming. Defaults to `false`.
```vue
```
### Should Scroll To Bottom
Use the `should-scroll-to-bottom` prop to enable/disable bottom auto scroll when the component is mounted. Defaults to `true`.
```vue
```
## Examples
::note{target="_blank" to="https://sdk.vercel.ai/docs/getting-started/nuxt"}
These chat components are designed to be used with the `useChat` composable from **Vercel AI SDK**.
::
::callout
---
icon: i-simple-icons-github
target: _blank
to: https://github.com/nuxt-ui-templates/chat
---
Check out the source code of our **AI Chat template** on GitHub for a real-life example.
::
### Within a page
Use the ChatMessages component with the `useChat` composable to display a list of chat messages within a page.
Pass the `messages` prop alongside the `status` prop that will be used for the auto scroll and the indicator display.
```vue [pages/[id\\].vue] {4,11-15}
```
::note
In this example, we use the `MDC` component from [`@nuxtjs/mdc`](https://github.com/nuxt-modules/mdc){rel="nofollow"} to render the content of the message. As Nuxt UI Pro provides pre-styled prose components, your content will be automatically styled.
::
## API
### Props
```ts
/**
* Props for the ChatMessages component
*/
interface ChatMessagesProps {
messages?: Message[] | undefined;
status?: "error" | "submitted" | "streaming" | "ready" | undefined;
/**
* Whether to automatically scroll to the bottom when a message is streaming.
* @default "false"
*/
shouldAutoScroll?: boolean | undefined;
/**
* Whether to scroll to the bottom on mounted.
* @default "true"
*/
shouldScrollToBottom?: boolean | undefined;
/**
* Display an auto scroll button.
* `{ size: 'md', color: 'neutral', variant: 'outline' }`{lang="ts-type"}
* @default "true"
*/
autoScroll?: boolean | Partial | undefined;
/**
* The icon displayed in the auto scroll button.
*/
autoScrollIcon?: string | undefined;
/**
* The `user` messages props.
* `{ side: 'right', variant: 'soft' }`{lang="ts-type"}
*/
user?: Pick | undefined;
/**
* The `assistant` messages props.
* `{ side: 'left', variant: 'naked' }`{lang="ts-type"}
*/
assistant?: Pick | undefined;
/**
* Render the messages in a compact style.
* This is done automatically when used inside a `UChatPalette`{lang="ts-type"}.
*/
compact?: boolean | undefined;
/**
* The spacing offset for the last message in px. Can be useful when the prompt is sticky for example.
* @default "0"
*/
spacingOffset?: number | undefined;
ui?: { root?: ClassNameValue; indicator?: ClassNameValue; viewport?: ClassNameValue; autoScroll?: ClassNameValue; } | undefined;
}
```
### Slots
```ts
/**
* Slots for the ChatMessages component
*/
interface ChatMessagesSlots {
default(): any;
indicator(): any;
viewport(): any;
content(): any;
leading(): any;
actions(): any;
}
```
::tip
You can use all the slots of the [`ChatMessage`](https://ui.nuxt.com/components/chat-message#slots) component inside ChatMessages, they are automatically forwarded allowing you to customize individual messages when using the `messages` prop.
```vue {3-5}
```
::
## Theme
```ts [app.config.ts]
export default defineAppConfig({
uiPro: {
chatMessages: {
slots: {
root: 'w-full flex flex-col gap-1 flex-1 px-2.5 [&>article]:last-of-type:min-h-(--last-message-height)',
indicator: 'h-6 flex items-center gap-1 py-3 *:size-2 *:rounded-full *:bg-elevated [&>*:nth-child(1)]:animate-[bounce_1s_infinite] [&>*:nth-child(2)]:animate-[bounce_1s_0.15s_infinite] [&>*:nth-child(3)]:animate-[bounce_1s_0.3s_infinite]',
viewport: 'absolute inset-x-0 top-[86%] data-[state=open]:animate-[fade-in_200ms_ease-out] data-[state=closed]:animate-[fade-out_200ms_ease-in]',
autoScroll: 'rounded-full absolute right-1/2 translate-x-1/2 bottom-0'
},
variants: {
compact: {
true: '',
false: ''
}
}
}
}
})
```
# ChatPalette
## Usage
The ChatPalette component is a structured layout wrapper that organizes [ChatMessages](https://ui.nuxt.com/components/chat-messages) in a scrollable content area and [ChatPrompt](https://ui.nuxt.com/components/chat-prompt) in a fixed bottom section, creating cohesive chatbot interfaces for modals, slideovers, or drawers.
```vue {2,8}
```
## Examples
::note{target="_blank" to="https://sdk.vercel.ai/docs/getting-started/nuxt"}
These chat components are designed to be used with the `useChat` composable from **Vercel AI SDK**.
::
### Within a Modal
You can use the ChatPalette component inside a [Modal](https://ui.nuxt.com/components/modal)'s content.
```vue [ChatPaletteModalExample.vue]
```
### Within ContentSearch
You can use the ChatPalette component conditionally inside [ContentSearch](https://ui.nuxt.com/components/content-search)'s content to display a chatbot interface when a user selects an item.
```vue [ChatPaletteContentSearchExample.vue]
```
## API
### Props
```ts
/**
* Props for the ChatPalette component
*/
interface ChatPaletteProps {
/**
* The element or component this component should render as.
*/
as?: any;
ui?: { root?: ClassNameValue; prompt?: ClassNameValue; close?: ClassNameValue; content?: ClassNameValue; } | undefined;
}
```
### Slots
```ts
/**
* Slots for the ChatPalette component
*/
interface ChatPaletteSlots {
default(): any;
prompt(): any;
}
```
## Theme
```ts [app.config.ts]
export default defineAppConfig({
uiPro: {
chatPalette: {
slots: {
root: 'relative flex-1 flex flex-col min-h-0 min-w-0',
prompt: 'px-0 rounded-t-none border-t border-default',
close: '',
content: 'overflow-y-auto flex-1 flex flex-col py-3'
}
}
}
})
```
# ChatPrompt
## Usage
The ChatPrompt component renders a `