--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` when running the tests.
::
### 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;
/**
* 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;
}
```
# Accordion
## Usage
### Items
Use the `items` prop as an array of objects with the following properties:
- `label?: string`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- `icon?: string`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- `trailingIcon?: string`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- `content?: string`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- `value?: string`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- `disabled?: boolean`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- [`slot?: string`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}](https://ui.nuxt.com/#with-custom-slot)
```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 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 }}`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- `#{{ item.slot }}-body`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
```vue [AccordionCustomSlotExample.vue]
{{ item.content }}
```
### 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.
The `useSortable` composable accepts various options, see the [Usage](https://vueuse.org/integrations/useSortable/#usage){rel="nofollow"} for more examples.
```vue [AccordionDragAndDropExample.vue]
```
## 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-(--ui-border) 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-(--ui-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'
}
}
}
}
}
})
```
# 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-(--ui-primary) text-(--ui-bg)'
}
},
{
color: 'secondary',
variant: 'solid',
class: {
root: 'bg-(--ui-secondary) text-(--ui-bg)'
}
},
{
color: 'success',
variant: 'solid',
class: {
root: 'bg-(--ui-success) text-(--ui-bg)'
}
},
{
color: 'info',
variant: 'solid',
class: {
root: 'bg-(--ui-info) text-(--ui-bg)'
}
},
{
color: 'warning',
variant: 'solid',
class: {
root: 'bg-(--ui-warning) text-(--ui-bg)'
}
},
{
color: 'error',
variant: 'solid',
class: {
root: 'bg-(--ui-error) text-(--ui-bg)'
}
},
{
color: 'primary',
variant: 'outline',
class: {
root: 'text-(--ui-primary) ring ring-inset ring-(--ui-primary)/25'
}
},
{
color: 'secondary',
variant: 'outline',
class: {
root: 'text-(--ui-secondary) ring ring-inset ring-(--ui-secondary)/25'
}
},
{
color: 'success',
variant: 'outline',
class: {
root: 'text-(--ui-success) ring ring-inset ring-(--ui-success)/25'
}
},
{
color: 'info',
variant: 'outline',
class: {
root: 'text-(--ui-info) ring ring-inset ring-(--ui-info)/25'
}
},
{
color: 'warning',
variant: 'outline',
class: {
root: 'text-(--ui-warning) ring ring-inset ring-(--ui-warning)/25'
}
},
{
color: 'error',
variant: 'outline',
class: {
root: 'text-(--ui-error) ring ring-inset ring-(--ui-error)/25'
}
},
{
color: 'primary',
variant: 'soft',
class: {
root: 'bg-(--ui-primary)/10 text-(--ui-primary)'
}
},
{
color: 'secondary',
variant: 'soft',
class: {
root: 'bg-(--ui-secondary)/10 text-(--ui-secondary)'
}
},
{
color: 'success',
variant: 'soft',
class: {
root: 'bg-(--ui-success)/10 text-(--ui-success)'
}
},
{
color: 'info',
variant: 'soft',
class: {
root: 'bg-(--ui-info)/10 text-(--ui-info)'
}
},
{
color: 'warning',
variant: 'soft',
class: {
root: 'bg-(--ui-warning)/10 text-(--ui-warning)'
}
},
{
color: 'error',
variant: 'soft',
class: {
root: 'bg-(--ui-error)/10 text-(--ui-error)'
}
},
{
color: 'primary',
variant: 'subtle',
class: {
root: 'bg-(--ui-primary)/10 text-(--ui-primary) ring ring-inset ring-(--ui-primary)/25'
}
},
{
color: 'secondary',
variant: 'subtle',
class: {
root: 'bg-(--ui-secondary)/10 text-(--ui-secondary) ring ring-inset ring-(--ui-secondary)/25'
}
},
{
color: 'success',
variant: 'subtle',
class: {
root: 'bg-(--ui-success)/10 text-(--ui-success) ring ring-inset ring-(--ui-success)/25'
}
},
{
color: 'info',
variant: 'subtle',
class: {
root: 'bg-(--ui-info)/10 text-(--ui-info) ring ring-inset ring-(--ui-info)/25'
}
},
{
color: 'warning',
variant: 'subtle',
class: {
root: 'bg-(--ui-warning)/10 text-(--ui-warning) ring ring-inset ring-(--ui-warning)/25'
}
},
{
color: 'error',
variant: 'subtle',
class: {
root: 'bg-(--ui-error)/10 text-(--ui-error) ring ring-inset ring-(--ui-error)/25'
}
},
{
color: 'neutral',
variant: 'solid',
class: {
root: 'text-(--ui-bg) bg-(--ui-bg-inverted)'
}
},
{
color: 'neutral',
variant: 'outline',
class: {
root: 'text-(--ui-text-highlighted) bg-(--ui-bg) ring ring-inset ring-(--ui-border)'
}
},
{
color: 'neutral',
variant: 'soft',
class: {
root: 'text-(--ui-text-highlighted) bg-(--ui-bg-elevated)/50'
}
},
{
color: 'neutral',
variant: 'subtle',
class: {
root: 'text-(--ui-text-highlighted) bg-(--ui-bg-elevated)/50 ring ring-inset ring-(--ui-border-accented)'
}
}
],
defaultVariants: {
color: 'primary',
variant: 'solid'
}
}
}
})
```
# 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 .
```
## API
### Props
```ts
/**
* Props for the AuthForm component
*/
interface AuthFormProps {
}
```
### Slots
```ts
/**
* Slots for the AuthForm component
*/
interface AuthFormSlots {
header(): any;
leading(): any;
title(): any;
description(): any;
validation(): any;
footer(): any;
}
```
### Emits
```ts
/**
* Emitted events for the AuthForm component
*/
interface AuthFormEmits {
submit: (payload: FormSubmitEvent) => void;
}
```
## 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',
title: 'text-xl text-pretty font-semibold text-(--ui-text-highlighted)',
description: 'mt-1 text-base text-pretty text-(--ui-text-muted)',
body: 'gap-y-6 flex flex-col',
providers: 'space-y-3',
separator: '',
form: 'space-y-5',
footer: 'text-sm text-center text-(--ui-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.
::
## 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]
```
### With chip
You can use a [Chip](https://ui.nuxt.com/components/chip) component to display a chip around the Avatar.
```vue [AvatarChipExample.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;
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 overflow-hidden rounded-full align-middle bg-(--ui-bg-elevated)',
image: 'h-full w-full rounded-[inherit] object-cover',
fallback: 'font-medium leading-none text-(--ui-text-muted) truncate',
icon: 'text-(--ui-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'
}
}
}
})
```
# 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-(--ui-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'
}
}
}
})
```
# 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;
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',
vertical: 'not-only:first:rounded-b-none not-only:last:rounded-t-none not-last:not-first:rounded-none'
},
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'
}
}
},
compoundVariants: [
{
color: 'primary',
variant: 'solid',
class: 'bg-(--ui-primary) text-(--ui-bg)'
},
{
color: 'secondary',
variant: 'solid',
class: 'bg-(--ui-secondary) text-(--ui-bg)'
},
{
color: 'success',
variant: 'solid',
class: 'bg-(--ui-success) text-(--ui-bg)'
},
{
color: 'info',
variant: 'solid',
class: 'bg-(--ui-info) text-(--ui-bg)'
},
{
color: 'warning',
variant: 'solid',
class: 'bg-(--ui-warning) text-(--ui-bg)'
},
{
color: 'error',
variant: 'solid',
class: 'bg-(--ui-error) text-(--ui-bg)'
},
{
color: 'primary',
variant: 'outline',
class: 'text-(--ui-primary) ring ring-inset ring-(--ui-primary)/50'
},
{
color: 'secondary',
variant: 'outline',
class: 'text-(--ui-secondary) ring ring-inset ring-(--ui-secondary)/50'
},
{
color: 'success',
variant: 'outline',
class: 'text-(--ui-success) ring ring-inset ring-(--ui-success)/50'
},
{
color: 'info',
variant: 'outline',
class: 'text-(--ui-info) ring ring-inset ring-(--ui-info)/50'
},
{
color: 'warning',
variant: 'outline',
class: 'text-(--ui-warning) ring ring-inset ring-(--ui-warning)/50'
},
{
color: 'error',
variant: 'outline',
class: 'text-(--ui-error) ring ring-inset ring-(--ui-error)/50'
},
{
color: 'primary',
variant: 'soft',
class: 'bg-(--ui-primary)/10 text-(--ui-primary)'
},
{
color: 'secondary',
variant: 'soft',
class: 'bg-(--ui-secondary)/10 text-(--ui-secondary)'
},
{
color: 'success',
variant: 'soft',
class: 'bg-(--ui-success)/10 text-(--ui-success)'
},
{
color: 'info',
variant: 'soft',
class: 'bg-(--ui-info)/10 text-(--ui-info)'
},
{
color: 'warning',
variant: 'soft',
class: 'bg-(--ui-warning)/10 text-(--ui-warning)'
},
{
color: 'error',
variant: 'soft',
class: 'bg-(--ui-error)/10 text-(--ui-error)'
},
{
color: 'primary',
variant: 'subtle',
class: 'bg-(--ui-primary)/10 text-(--ui-primary) ring ring-inset ring-(--ui-primary)/25'
},
{
color: 'secondary',
variant: 'subtle',
class: 'bg-(--ui-secondary)/10 text-(--ui-secondary) ring ring-inset ring-(--ui-secondary)/25'
},
{
color: 'success',
variant: 'subtle',
class: 'bg-(--ui-success)/10 text-(--ui-success) ring ring-inset ring-(--ui-success)/25'
},
{
color: 'info',
variant: 'subtle',
class: 'bg-(--ui-info)/10 text-(--ui-info) ring ring-inset ring-(--ui-info)/25'
},
{
color: 'warning',
variant: 'subtle',
class: 'bg-(--ui-warning)/10 text-(--ui-warning) ring ring-inset ring-(--ui-warning)/25'
},
{
color: 'error',
variant: 'subtle',
class: 'bg-(--ui-error)/10 text-(--ui-error) ring ring-inset ring-(--ui-error)/25'
},
{
color: 'neutral',
variant: 'solid',
class: 'text-(--ui-bg) bg-(--ui-bg-inverted)'
},
{
color: 'neutral',
variant: 'outline',
class: 'ring ring-inset ring-(--ui-border-accented) text-(--ui-text) bg-(--ui-bg)'
},
{
color: 'neutral',
variant: 'soft',
class: 'text-(--ui-text) bg-(--ui-bg-elevated)'
},
{
color: 'neutral',
variant: 'subtle',
class: 'ring ring-inset ring-(--ui-border-accented) text-(--ui-text) bg-(--ui-bg-elevated)'
}
],
defaultVariants: {
color: 'primary',
variant: 'solid',
size: 'md'
}
}
}
})
```
# 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 {
}
```
### Slots
```ts
/**
* Slots for the Banner component
*/
interface BannerSlots {
}
```
### Emits
No events available for this component.
## 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-(--ui-bg) pointer-events-none',
title: 'text-sm text-(--ui-bg) font-medium truncate',
actions: 'flex gap-1.5 shrink-0 isolate',
close: 'text-(--ui-bg) hover:bg-(--ui-bg)/10 focus-visible:bg-(--ui-bg)/10 -me-1.5 lg:me-0'
},
variants: {
color: {
primary: {
root: 'bg-(--ui-primary)'
},
secondary: {
root: 'bg-(--ui-secondary)'
},
success: {
root: 'bg-(--ui-success)'
},
info: {
root: 'bg-(--ui-info)'
},
warning: {
root: 'bg-(--ui-warning)'
},
error: {
root: 'bg-(--ui-error)'
},
neutral: {
root: 'bg-(--ui-bg-inverted)'
}
},
to: {
true: ''
}
},
compoundVariants: [
{
color: 'primary',
to: true,
class: {
root: 'hover:bg-(--ui-primary)/90'
}
},
{
color: 'secondary',
to: true,
class: {
root: 'hover:bg-(--ui-secondary)/90'
}
},
{
color: 'success',
to: true,
class: {
root: 'hover:bg-(--ui-success)/90'
}
},
{
color: 'info',
to: true,
class: {
root: 'hover:bg-(--ui-info)/90'
}
},
{
color: 'warning',
to: true,
class: {
root: 'hover:bg-(--ui-warning)/90'
}
},
{
color: 'error',
to: true,
class: {
root: 'hover:bg-(--ui-error)/90'
}
},
{
color: 'neutral',
to: true,
class: {
root: 'hover:bg-(--ui-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
className:
- 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`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- `description?: string`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- `avatar?: Omit`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- `chip?: boolean | Omit`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- `size?: UserProps['size']`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- `orientation?: UserProps['orientation']`{className="language-ts-type 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 {
/**
* @default "\"vertical\""
*/
orientation?: "vertical" | "horizontal" | undefined;
/**
* @default "\"article\""
*/
as?: any;
ui?: { root?: ClassNameValue; header?: ClassNameValue; body?: ClassNameValue; footer?: ClassNameValue; image?: ClassNameValue; ... 6 more ...; badge?: ClassNameValue; } | undefined;
target?: "_blank" | "_parent" | "_self" | "_top" | (string & {}) | null | undefined;
to?: string | RouteLocationAsRelativeGeneric | RouteLocationAsPathGeneric | undefined;
title?: string | undefined;
description?: string | 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;
variant?: "outline" | "soft" | "subtle" | "ghost" | "naked" | undefined;
/**
* The image of the blog post. Can be a string or an object.
*/
image?: string | Partial | undefined;
/**
* The date of the blog post. Can be a string or a Date object.
*/
date?: string | Date | undefined;
/**
* The authors of the blog post.
*/
authors?: UserProps[] | 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-[calc(var(--ui-radius)*2)] 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-(--ui-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-(--ui-bg) ring ring-(--ui-border)',
date: 'text-(--ui-text-toned)',
description: 'text-(--ui-text-muted)'
},
soft: {
root: 'bg-(--ui-bg-elevated)/50',
date: 'text-(--ui-text-muted)',
description: 'text-(--ui-text-toned)'
},
subtle: {
root: 'bg-(--ui-bg-elevated)/50 ring ring-(--ui-border)',
date: 'text-(--ui-text-muted)',
description: 'text-(--ui-text-toned)'
},
ghost: {
date: 'text-(--ui-text-toned)',
description: 'text-(--ui-text-muted)',
header: 'shadow-lg rounded-[calc(var(--ui-radius)*2)]'
},
naked: {
root: 'p-0 sm:p-0',
date: 'text-(--ui-text-toned)',
description: 'text-(--ui-text-muted)',
header: 'shadow-lg rounded-[calc(var(--ui-radius)*2)]'
}
},
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-(--ui-bg-elevated)/50'
}
},
{
variant: 'soft',
to: true,
class: {
root: 'hover:bg-(--ui-bg-elevated)'
}
},
{
variant: 'subtle',
to: true,
class: {
root: 'hover:bg-(--ui-bg-elevated) hover:ring-(--ui-border-accented)'
}
},
{
variant: 'ghost',
to: true,
class: {
root: 'hover:bg-(--ui-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 {
/**
* @default "\"horizontal\""
*/
orientation?: "vertical" | "horizontal" | undefined;
/**
* The element or component this component should render as.
*/
as?: any;
posts?: BlogPostProps[] | 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`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- `icon?: string`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- `avatar?: AvatarProps`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- `class?: any`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- [`slot?: string`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}](https://ui.nuxt.com/#with-custom-slot)
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 }}`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- `#{{ item.slot }}-leading`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- `#{{ item.slot }}-label`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- `#{{ item.slot }}-trailing`{className="language-ts-type 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; ... 4 more ...; 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-(--ui-primary)',
linkLeadingIcon: 'shrink-0 size-5',
linkLeadingAvatar: 'shrink-0',
linkLeadingAvatarSize: '2xs',
linkLabel: 'truncate',
separator: 'flex',
separatorIcon: 'shrink-0 size-5 text-(--ui-text-muted)'
},
variants: {
active: {
true: {
link: 'text-(--ui-primary) font-semibold'
},
false: {
link: 'text-(--ui-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-(--ui-text)',
'transition-colors'
]
}
}
]
}
}
})
```
# 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-refresh-cw`.
```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;
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;
/**
* The type of the button when not a link.
*/
type?: "reset" | "submit" | "button" | undefined;
/**
* 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
* @default "\"\""
*/
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?: "true" | "false" | "page" | "step" | "location" | "date" | "time" | undefined;
/**
* Pass the returned promise of `router.push()` to `document.startViewTransition()` if supported.
*/
viewTransition?: boolean | undefined;
disabled?: boolean | undefined;
/**
* Force the link to be active independent of the current route.
* @default "undefined"
*/
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.
* @default "\"\""
*/
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',
vertical: 'not-only:first:rounded-b-none not-only:last:rounded-t-none not-last:not-first:rounded-none'
},
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-(--ui-bg) bg-(--ui-primary) hover:bg-(--ui-primary)/75 disabled:bg-(--ui-primary) aria-disabled:bg-(--ui-primary) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ui-primary)'
},
{
color: 'secondary',
variant: 'solid',
class: 'text-(--ui-bg) bg-(--ui-secondary) hover:bg-(--ui-secondary)/75 disabled:bg-(--ui-secondary) aria-disabled:bg-(--ui-secondary) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ui-secondary)'
},
{
color: 'success',
variant: 'solid',
class: 'text-(--ui-bg) bg-(--ui-success) hover:bg-(--ui-success)/75 disabled:bg-(--ui-success) aria-disabled:bg-(--ui-success) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ui-success)'
},
{
color: 'info',
variant: 'solid',
class: 'text-(--ui-bg) bg-(--ui-info) hover:bg-(--ui-info)/75 disabled:bg-(--ui-info) aria-disabled:bg-(--ui-info) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ui-info)'
},
{
color: 'warning',
variant: 'solid',
class: 'text-(--ui-bg) bg-(--ui-warning) hover:bg-(--ui-warning)/75 disabled:bg-(--ui-warning) aria-disabled:bg-(--ui-warning) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ui-warning)'
},
{
color: 'error',
variant: 'solid',
class: 'text-(--ui-bg) bg-(--ui-error) hover:bg-(--ui-error)/75 disabled:bg-(--ui-error) aria-disabled:bg-(--ui-error) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ui-error)'
},
{
color: 'primary',
variant: 'outline',
class: 'ring ring-inset ring-(--ui-primary)/50 text-(--ui-primary) hover:bg-(--ui-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-(--ui-primary)'
},
{
color: 'secondary',
variant: 'outline',
class: 'ring ring-inset ring-(--ui-secondary)/50 text-(--ui-secondary) hover:bg-(--ui-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-(--ui-secondary)'
},
{
color: 'success',
variant: 'outline',
class: 'ring ring-inset ring-(--ui-success)/50 text-(--ui-success) hover:bg-(--ui-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-(--ui-success)'
},
{
color: 'info',
variant: 'outline',
class: 'ring ring-inset ring-(--ui-info)/50 text-(--ui-info) hover:bg-(--ui-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-(--ui-info)'
},
{
color: 'warning',
variant: 'outline',
class: 'ring ring-inset ring-(--ui-warning)/50 text-(--ui-warning) hover:bg-(--ui-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-(--ui-warning)'
},
{
color: 'error',
variant: 'outline',
class: 'ring ring-inset ring-(--ui-error)/50 text-(--ui-error) hover:bg-(--ui-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-(--ui-error)'
},
{
color: 'primary',
variant: 'soft',
class: 'text-(--ui-primary) bg-(--ui-primary)/10 hover:bg-(--ui-primary)/15 focus:outline-none focus-visible:bg-(--ui-primary)/15 disabled:bg-(--ui-primary)/10 aria-disabled:bg-(--ui-primary)/10'
},
{
color: 'secondary',
variant: 'soft',
class: 'text-(--ui-secondary) bg-(--ui-secondary)/10 hover:bg-(--ui-secondary)/15 focus:outline-none focus-visible:bg-(--ui-secondary)/15 disabled:bg-(--ui-secondary)/10 aria-disabled:bg-(--ui-secondary)/10'
},
{
color: 'success',
variant: 'soft',
class: 'text-(--ui-success) bg-(--ui-success)/10 hover:bg-(--ui-success)/15 focus:outline-none focus-visible:bg-(--ui-success)/15 disabled:bg-(--ui-success)/10 aria-disabled:bg-(--ui-success)/10'
},
{
color: 'info',
variant: 'soft',
class: 'text-(--ui-info) bg-(--ui-info)/10 hover:bg-(--ui-info)/15 focus:outline-none focus-visible:bg-(--ui-info)/15 disabled:bg-(--ui-info)/10 aria-disabled:bg-(--ui-info)/10'
},
{
color: 'warning',
variant: 'soft',
class: 'text-(--ui-warning) bg-(--ui-warning)/10 hover:bg-(--ui-warning)/15 focus:outline-none focus-visible:bg-(--ui-warning)/15 disabled:bg-(--ui-warning)/10 aria-disabled:bg-(--ui-warning)/10'
},
{
color: 'error',
variant: 'soft',
class: 'text-(--ui-error) bg-(--ui-error)/10 hover:bg-(--ui-error)/15 focus:outline-none focus-visible:bg-(--ui-error)/15 disabled:bg-(--ui-error)/10 aria-disabled:bg-(--ui-error)/10'
},
{
color: 'primary',
variant: 'subtle',
class: 'text-(--ui-primary) ring ring-inset ring-(--ui-primary)/25 bg-(--ui-primary)/10 hover:bg-(--ui-primary)/15 disabled:bg-(--ui-primary)/10 aria-disabled:bg-(--ui-primary)/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-(--ui-primary)'
},
{
color: 'secondary',
variant: 'subtle',
class: 'text-(--ui-secondary) ring ring-inset ring-(--ui-secondary)/25 bg-(--ui-secondary)/10 hover:bg-(--ui-secondary)/15 disabled:bg-(--ui-secondary)/10 aria-disabled:bg-(--ui-secondary)/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-(--ui-secondary)'
},
{
color: 'success',
variant: 'subtle',
class: 'text-(--ui-success) ring ring-inset ring-(--ui-success)/25 bg-(--ui-success)/10 hover:bg-(--ui-success)/15 disabled:bg-(--ui-success)/10 aria-disabled:bg-(--ui-success)/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-(--ui-success)'
},
{
color: 'info',
variant: 'subtle',
class: 'text-(--ui-info) ring ring-inset ring-(--ui-info)/25 bg-(--ui-info)/10 hover:bg-(--ui-info)/15 disabled:bg-(--ui-info)/10 aria-disabled:bg-(--ui-info)/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-(--ui-info)'
},
{
color: 'warning',
variant: 'subtle',
class: 'text-(--ui-warning) ring ring-inset ring-(--ui-warning)/25 bg-(--ui-warning)/10 hover:bg-(--ui-warning)/15 disabled:bg-(--ui-warning)/10 aria-disabled:bg-(--ui-warning)/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-(--ui-warning)'
},
{
color: 'error',
variant: 'subtle',
class: 'text-(--ui-error) ring ring-inset ring-(--ui-error)/25 bg-(--ui-error)/10 hover:bg-(--ui-error)/15 disabled:bg-(--ui-error)/10 aria-disabled:bg-(--ui-error)/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-(--ui-error)'
},
{
color: 'primary',
variant: 'ghost',
class: 'text-(--ui-primary) hover:bg-(--ui-primary)/10 focus:outline-none focus-visible:bg-(--ui-primary)/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent'
},
{
color: 'secondary',
variant: 'ghost',
class: 'text-(--ui-secondary) hover:bg-(--ui-secondary)/10 focus:outline-none focus-visible:bg-(--ui-secondary)/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent'
},
{
color: 'success',
variant: 'ghost',
class: 'text-(--ui-success) hover:bg-(--ui-success)/10 focus:outline-none focus-visible:bg-(--ui-success)/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent'
},
{
color: 'info',
variant: 'ghost',
class: 'text-(--ui-info) hover:bg-(--ui-info)/10 focus:outline-none focus-visible:bg-(--ui-info)/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent'
},
{
color: 'warning',
variant: 'ghost',
class: 'text-(--ui-warning) hover:bg-(--ui-warning)/10 focus:outline-none focus-visible:bg-(--ui-warning)/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent'
},
{
color: 'error',
variant: 'ghost',
class: 'text-(--ui-error) hover:bg-(--ui-error)/10 focus:outline-none focus-visible:bg-(--ui-error)/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent'
},
{
color: 'primary',
variant: 'link',
class: 'text-(--ui-primary) hover:text-(--ui-primary)/75 disabled:text-(--ui-primary) aria-disabled:text-(--ui-primary) focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-(--ui-primary)'
},
{
color: 'secondary',
variant: 'link',
class: 'text-(--ui-secondary) hover:text-(--ui-secondary)/75 disabled:text-(--ui-secondary) aria-disabled:text-(--ui-secondary) focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-(--ui-secondary)'
},
{
color: 'success',
variant: 'link',
class: 'text-(--ui-success) hover:text-(--ui-success)/75 disabled:text-(--ui-success) aria-disabled:text-(--ui-success) focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-(--ui-success)'
},
{
color: 'info',
variant: 'link',
class: 'text-(--ui-info) hover:text-(--ui-info)/75 disabled:text-(--ui-info) aria-disabled:text-(--ui-info) focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-(--ui-info)'
},
{
color: 'warning',
variant: 'link',
class: 'text-(--ui-warning) hover:text-(--ui-warning)/75 disabled:text-(--ui-warning) aria-disabled:text-(--ui-warning) focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-(--ui-warning)'
},
{
color: 'error',
variant: 'link',
class: 'text-(--ui-error) hover:text-(--ui-error)/75 disabled:text-(--ui-error) aria-disabled:text-(--ui-error) focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-(--ui-error)'
},
{
color: 'neutral',
variant: 'solid',
class: 'text-(--ui-bg) bg-(--ui-bg-inverted) hover:bg-(--ui-bg-inverted)/90 disabled:bg-(--ui-bg-inverted) aria-disabled:bg-(--ui-bg-inverted) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ui-border-inverted)'
},
{
color: 'neutral',
variant: 'outline',
class: 'ring ring-inset ring-(--ui-border-accented) text-(--ui-text) bg-(--ui-bg) hover:bg-(--ui-bg-elevated) disabled:bg-(--ui-bg) aria-disabled:bg-(--ui-bg) focus:outline-none focus-visible:ring-2 focus-visible:ring-(--ui-border-inverted)'
},
{
color: 'neutral',
variant: 'soft',
class: 'text-(--ui-text) bg-(--ui-bg-elevated) hover:bg-(--ui-bg-accented)/75 focus:outline-none focus-visible:bg-(--ui-bg-accented)/75 disabled:bg-(--ui-bg-elevated) aria-disabled:bg-(--ui-bg-elevated)'
},
{
color: 'neutral',
variant: 'subtle',
class: 'ring ring-inset ring-(--ui-border-accented) text-(--ui-text) bg-(--ui-bg-elevated) hover:bg-(--ui-bg-accented)/75 disabled:bg-(--ui-bg-elevated) aria-disabled:bg-(--ui-bg-elevated) focus:outline-none focus-visible:ring-2 focus-visible:ring-(--ui-border-inverted)'
},
{
color: 'neutral',
variant: 'ghost',
class: 'text-(--ui-text) hover:bg-(--ui-bg-elevated) focus:outline-none focus-visible:bg-(--ui-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-(--ui-text-muted) hover:text-(--ui-text) disabled:text-(--ui-text-muted) aria-disabled:text-(--ui-text-muted) focus:outline-none focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-(--ui-border-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'
}
}
}
})
```
# 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'
}
}
}
}
})
```
# 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.
::
### 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; ... 5 more ...; 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 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 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;
}
```
### 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: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',
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-(--ui-text-muted) data-unavailable:line-through data-unavailable:text-(--ui-text-muted) data-unavailable:pointer-events-none data-[selected]:text-(--ui-bg) data-today:font-semibold data-[outside-view]:text-(--ui-text-muted)',
'transition'
]
},
variants: {
color: {
primary: {
headCell: 'text-(--ui-primary)',
cellTrigger: 'focus-visible:ring-(--ui-primary) data-[selected]:bg-(--ui-primary) data-today:not-data-[selected]:text-(--ui-primary) data-[highlighted]:bg-(--ui-primary)/20 hover:not-data-[selected]:bg-(--ui-primary)/20'
},
secondary: {
headCell: 'text-(--ui-secondary)',
cellTrigger: 'focus-visible:ring-(--ui-secondary) data-[selected]:bg-(--ui-secondary) data-today:not-data-[selected]:text-(--ui-secondary) data-[highlighted]:bg-(--ui-secondary)/20 hover:not-data-[selected]:bg-(--ui-secondary)/20'
},
success: {
headCell: 'text-(--ui-success)',
cellTrigger: 'focus-visible:ring-(--ui-success) data-[selected]:bg-(--ui-success) data-today:not-data-[selected]:text-(--ui-success) data-[highlighted]:bg-(--ui-success)/20 hover:not-data-[selected]:bg-(--ui-success)/20'
},
info: {
headCell: 'text-(--ui-info)',
cellTrigger: 'focus-visible:ring-(--ui-info) data-[selected]:bg-(--ui-info) data-today:not-data-[selected]:text-(--ui-info) data-[highlighted]:bg-(--ui-info)/20 hover:not-data-[selected]:bg-(--ui-info)/20'
},
warning: {
headCell: 'text-(--ui-warning)',
cellTrigger: 'focus-visible:ring-(--ui-warning) data-[selected]:bg-(--ui-warning) data-today:not-data-[selected]:text-(--ui-warning) data-[highlighted]:bg-(--ui-warning)/20 hover:not-data-[selected]:bg-(--ui-warning)/20'
},
error: {
headCell: 'text-(--ui-error)',
cellTrigger: 'focus-visible:ring-(--ui-error) data-[selected]:bg-(--ui-error) data-today:not-data-[selected]:text-(--ui-error) data-[highlighted]:bg-(--ui-error)/20 hover:not-data-[selected]:bg-(--ui-error)/20'
},
neutral: {
headCell: 'text-(--ui-bg-inverted)',
cellTrigger: 'focus-visible:ring-(--ui-border-inverted) data-[selected]:bg-(--ui-bg-inverted) data-today:not-data-[selected]:text-(--ui-bg-inverted) data-[highlighted]:bg-(--ui-bg-inverted)/20 hover:not-data-[selected]:bg-(--ui-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'
}
}
}
})
```
# 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',
header: 'p-4 sm:px-6',
body: 'p-4 sm:p-6',
footer: 'p-4 sm:px-6'
},
variants: {
variant: {
solid: {
root: 'bg-(--ui-bg-inverted) text-(--ui-bg)'
},
outline: {
root: 'bg-(--ui-bg) ring ring-(--ui-border) divide-y divide-(--ui-border)'
},
soft: {
root: 'bg-(--ui-bg-elevated)/50 divide-y divide-(--ui-border)'
},
subtle: {
root: 'bg-(--ui-bg-elevated)/50 ring ring-(--ui-border) divide-y divide-(--ui-border)'
}
}
},
defaultVariants: {
variant: 'outline'
}
}
}
})
```
# 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 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]
```
## 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?: AcceptableValue[] | 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?: any;
ui?: { root?: ClassNameValue; viewport?: ClassNameValue; container?: ClassNameValue; item?: ClassNameValue; controls?: ClassNameValue; ... 4 more ...; 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; ... 13 more ...; watchFocus: FocusHandlerOptionType; }>>, "breakpoints">; } | undefined;
}
```
### Slots
```ts
/**
* Slots for the Carousel component
*/
interface CarouselSlots {
default(): any;
}
```
### 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`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"} | `Ref`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"} |
| `emblaApi`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"} | [`Ref`{className="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-(--ui-border-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: 'bg-(--ui-border-inverted)'
}
}
}
}
}
})
```
# 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 {
/**
* Text content of the message. Use parts when possible.
*/
content: string;
/**
* A unique identifier for the message.
*/
id: string;
/**
* The 'data' role is deprecated.
*/
role: "data" | "system" | "user" | "assistant";
/**
* @default "\"article\""
*/
as?: any;
icon?: string | undefined;
ui?: { root?: ClassNameValue; container?: ClassNameValue; leading?: ClassNameValue; leadingIcon?: ClassNameValue; leadingAvatar?: ClassNameValue; leadingAvatarSize?: ClassNameValue; content?: ClassNameValue; actions?: ClassNameValue; } | undefined;
avatar?: (AvatarProps & { [key: string]: any; }) | undefined;
variant?: "solid" | "outline" | "soft" | "subtle" | "naked" | undefined;
side?: "right" | "left" | undefined;
/**
* For data messages.
*/
data?: JSONValue | 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;
/**
* 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;
/**
* 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',
actions: [
'opacity-0 group-hover/message:opacity-100 absolute bottom-0 flex items-center',
'transition-opacity'
]
},
variants: {
variant: {
solid: {
content: 'bg-(--ui-bg-inverted) text-(--ui-bg)'
},
outline: {
content: 'bg-(--ui-bg) ring ring-(--ui-border)'
},
soft: {
content: 'bg-(--ui-bg-elevated)/50'
},
subtle: {
content: 'bg-(--ui-bg-elevated)/50 ring ring-(--ui-border)'
},
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-[calc(var(--ui-radius)*2)] min-h-12',
leading: 'mt-2'
}
},
{
variant: [
'solid',
'outline',
'soft',
'subtle'
],
compact: true,
class: {
content: 'px-2 py-1 rounded-[calc(var(--ui-radius)*2)] 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'`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- `variant: 'soft'`{className="language-ts-type 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'`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- `variant: 'naked'`{className="language-ts-type 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'`{className="language-ts-type shiki shiki-themes material-theme-lighter material-theme material-theme-palenight" lang="ts-type"}
- `variant: 'outline'`{className="language-ts-type 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-pro/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 {
/**
* @default "true"
*/
autoScroll?: boolean | Partial | undefined;
/**
* @default "false"
*/
shouldAutoScroll?: boolean | undefined;
/**
* @default "true"
*/
shouldScrollToBottom?: boolean | undefined;
/**
* @default "0"
*/
spacingOffset?: number | undefined;
ui?: { root?: ClassNameValue; indicator?: ClassNameValue; viewport?: ClassNameValue; autoScroll?: ClassNameValue; } | undefined;
messages?: Message[] | undefined;
/**
* Render the messages in a compact style.
* This is done automatically when used inside a `UChatPalette`{lang="ts-type"}.
*/
compact?: boolean | 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;
status?: "error" | "submitted" | "streaming" | "ready" | undefined;
/**
* The icon displayed in the auto scroll button.
*/
autoScrollIcon?: string | 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-(--ui-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-(--ui-border)',
close: '',
content: 'overflow-y-auto flex-1 flex flex-col py-3'
}
}
}
})
```
# ChatPrompt
## Usage
The ChatPrompt component renders a `