---
name: sidebar
title: Sidebar
url: https://ui.frontic.com/components/ui/sidebar.md
tier: ui
install: npx @frontic/ui add sidebar
files:
  - components/ui/sidebar/Sidebar.vue
  - components/ui/sidebar/SidebarContent.vue
  - components/ui/sidebar/SidebarFooter.vue
  - components/ui/sidebar/SidebarGroup.vue
  - components/ui/sidebar/SidebarGroupAction.vue
  - components/ui/sidebar/SidebarGroupContent.vue
  - components/ui/sidebar/SidebarGroupLabel.vue
  - components/ui/sidebar/SidebarHeader.vue
  - components/ui/sidebar/SidebarInput.vue
  - components/ui/sidebar/SidebarInset.vue
  - components/ui/sidebar/SidebarMenu.vue
  - components/ui/sidebar/SidebarMenuAction.vue
  - components/ui/sidebar/SidebarMenuBadge.vue
  - components/ui/sidebar/SidebarMenuButton.vue
  - components/ui/sidebar/SidebarMenuButtonChild.vue
  - components/ui/sidebar/SidebarMenuItem.vue
  - components/ui/sidebar/SidebarMenuSkeleton.vue
  - components/ui/sidebar/SidebarMenuSub.vue
  - components/ui/sidebar/SidebarMenuSubButton.vue
  - components/ui/sidebar/SidebarMenuSubItem.vue
  - components/ui/sidebar/SidebarProvider.vue
  - components/ui/sidebar/SidebarRail.vue
  - components/ui/sidebar/SidebarSeparator.vue
  - components/ui/sidebar/SidebarTrigger.vue
  - components/ui/sidebar/index.ts
  - components/ui/sidebar/utils.ts
registryDependencies:
  - button
  - input
  - separator
  - sheet
  - skeleton
  - tooltip
dependencies:
  - "@lucide/vue"
  - "@vueuse/core"
  - class-variance-authority
  - reka-ui
---

# Sidebar

Side panel on the start or end of the page, with groups, menus, badges and actions, that collapses off-screen or to icons and opens as a sheet on phones.

## Install

```sh
npx @frontic/ui add sidebar
```

Built from: [Button](https://ui.frontic.com/components/ui/button.md), [Input](https://ui.frontic.com/components/ui/input.md), [Separator](https://ui.frontic.com/components/ui/separator.md), [Sheet](https://ui.frontic.com/components/ui/sheet.md), [Skeleton](https://ui.frontic.com/components/ui/skeleton.md), [Tooltip](https://ui.frontic.com/components/ui/tooltip.md)

npm packages: `@lucide/vue`, `@vueuse/core`, `class-variance-authority`, `reka-ui`

## Examples

```vue
<script setup lang="ts">
import { XIcon } from '@lucide/vue'

import { computed, reactive, ref } from 'vue'
import { Button } from '@/components/ui/button'
import { FilterPrice, FilterSwatch, FilterSwitch } from '@/components/ui/filter'
import { Price } from '@/components/ui/price'
import { TagGroup, TagGroupItem, TagGroupItemDelete, TagGroupItemText } from '@/components/ui/tag-group'
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupAction,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarInset,
  SidebarMenu,
  SidebarMenuBadge,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarMenuSub,
  SidebarMenuSubButton,
  SidebarMenuSubItem,
  SidebarProvider,
  SidebarRail,
  SidebarSeparator,
  SidebarTrigger,
} from '@/components/ui/sidebar'

// The demo shop's men's jackets, as the category grid shows them. Blizzard Defender is one product in three colours.
const JACKETS = [
  { name: 'Summit Series Winter Parka', price: { amount: 13200, currency: 'EUR', ref: 15900 }, colours: [{ colour: 'Charcoal', hex: '#4a4a47' }] },
  { name: 'Glacier Explorer', price: { amount: 9400, currency: 'EUR' }, colours: [{ colour: 'Slate blue', hex: '#5f7680' }], inStock: false },
  { name: 'Winter Cliffs Hiking Jacket', price: { amount: 5200, currency: 'EUR' }, colours: [{ colour: 'Ochre', hex: '#c08a3c' }] },
  { name: 'Frost Cruiser Softshell', price: { amount: 10700, currency: 'EUR' }, colours: [{ colour: 'Steel grey', hex: '#8e98a4' }], inStock: false },
  { name: 'Glacier Hiker Coat', price: { amount: 8400, currency: 'EUR', ref: 10500 }, colours: [{ colour: 'Dark olive', hex: '#55573f' }] },
  { name: 'Summit Strider', price: { amount: 8100, currency: 'EUR' }, colours: [{ colour: 'Saffron', hex: '#e5a63c' }] },
  { name: 'Polar Light Ski Jacket', price: { amount: 7900, currency: 'EUR' }, colours: [{ colour: 'Dusty pink', hex: '#d6aaa6' }] },
  { name: 'Glacier Fever Functional Jacket', price: { amount: 6500, currency: 'EUR' }, colours: [{ colour: 'Mustard', hex: '#cf9e42' }] },
  { name: 'Arctic Fox Winter Jacket', price: { amount: 6100, currency: 'EUR', ref: 7900 }, colours: [{ colour: 'Light grey', hex: '#d0d0ce' }] },
  { name: 'Arctic Mission Jacket', price: { amount: 8700, currency: 'EUR' }, colours: [{ colour: 'Dark brown', hex: '#4d3e34' }] },
  {
    name: 'Blizzard Defender',
    price: { amount: 12600, currency: 'EUR' },
    colours: [
      { colour: 'Aubergine', hex: '#3d3347' },
      { colour: 'Dusty rose', hex: '#b98a87' },
      { colour: 'Salmon', hex: '#dc967b' },
    ],
  },
  { name: 'High Altitude Trekking Jacket', price: { amount: 6200, currency: 'EUR' }, colours: [{ colour: 'Olive', hex: '#a09d5c' }] },
  { name: 'Arctic Fox Hybrid Coat', price: { amount: 6800, currency: 'EUR' }, colours: [{ colour: 'Terracotta', hex: '#c4735f' }], inStock: false },
  { name: 'Summit Stormer Parka', price: { amount: 5700, currency: 'EUR' }, colours: [{ colour: 'Amber', hex: '#e2a240' }] },
  { name: 'Mountaineer Hybrid Jacket', price: { amount: 9100, currency: 'EUR' }, colours: [{ colour: 'Charcoal', hex: '#4a4a47' }] },
  { name: 'Aurora Functional Jacket', price: { amount: 7400, currency: 'EUR' }, colours: [{ colour: 'Rust', hex: '#b0593f' }] },
  { name: 'Frost Escape Parka', price: { amount: 14900, currency: 'EUR' }, colours: [{ colour: 'Sun yellow', hex: '#e5ad40' }] },
  { name: 'Alpine Trekker', price: { amount: 12600, currency: 'EUR' }, colours: [{ colour: 'Stone grey', hex: '#9c9c9a' }], inStock: false },
  { name: 'Arctic Mission Parka', price: { amount: 11980, currency: 'EUR' }, colours: [{ colour: 'Burnt orange', hex: '#c8704f' }] },
]

const HEX: Record<string, string> = Object.fromEntries(JACKETS.flatMap((jacket) => jacket.colours.map(({ colour, hex }) => [colour, hex])).sort(([a], [b]) => a.localeCompare(b)))
const MIN = Math.floor(Math.min(...JACKETS.map((j) => j.price.amount)) / 1000) * 10
const MAX = Math.ceil(Math.max(...JACKETS.map((j) => j.price.amount)) / 1000) * 10

const inStock = ref(false)
const colours = reactive(new Set<string>())
const price = ref<{ from?: number; to?: number }>({ from: MIN, to: MAX })

const priced = computed(() => JACKETS.filter((j) => (!inStock.value || j.inStock !== false) && j.price.amount >= (price.value.from ?? MIN) * 100 && j.price.amount <= (price.value.to ?? MAX) * 100))
const shown = computed(() => priced.value.filter((j) => !colours.size || j.colours.some(({ colour }) => colours.has(colour))))

const colourOptions = computed(() =>
  Object.entries(HEX).map(([colour, hex]) => {
    const count = priced.value.filter((j) => j.colours.some((c) => c.colour === colour)).length
    return { value: colour, hex, label: colour, count, selected: colours.has(colour), disabled: !count && !colours.has(colour) }
  }),
)

// Every active filter as a chip; removing one clears that filter.
const active = computed(() => [
  ...(inStock.value ? [{ key: 'stock', label: 'In stock', clear: () => (inStock.value = false) }] : []),
  ...[...colours].map((colour) => ({ key: `colour-${colour}`, label: colour, clear: () => colours.delete(colour) })),
  ...(price.value.from !== MIN || price.value.to !== MAX
    ? [{ key: 'price', label: `€${price.value.from ?? MIN} – €${price.value.to ?? MAX}`, clear: () => (price.value = { from: MIN, to: MAX }) }]
    : []),
])

function remove(keys: unknown[]) {
  for (const chip of active.value) if (keys.includes(chip.key)) chip.clear()
}

function toggleColour(colour: string) {
  if (colours.has(colour)) colours.delete(colour)
  else colours.add(colour)
}

function clearAll() {
  inStock.value = false
  colours.clear()
  price.value = { from: MIN, to: MAX }
}
</script>

<template>
  <SidebarProvider>
    <Sidebar label="Filters" description="Narrow the men's jackets by stock, colour and price.">
      <SidebarHeader>
        <TagGroup v-if="active.length" aria-label="Active filters" @remove="remove">
          <TagGroupItem v-for="chip in active" :key="chip.key" :value="chip.key">
            <TagGroupItemText>{{ chip.label }}</TagGroupItemText>
            <TagGroupItemDelete />
          </TagGroupItem>
        </TagGroup>
      </SidebarHeader>
      <SidebarContent>
        <SidebarGroup>
          <SidebarGroupLabel>Category</SidebarGroupLabel>
          <SidebarGroupContent>
            <SidebarMenu>
              <SidebarMenuItem>
                <SidebarMenuButton as="a" href="#" is-active>
                  <span>Jackets</span>
                </SidebarMenuButton>
                <SidebarMenuBadge>{{ shown.length }}</SidebarMenuBadge>
                <SidebarMenuSub>
                  <SidebarMenuSubItem>
                    <SidebarMenuSubButton href="#" is-active>Men</SidebarMenuSubButton>
                  </SidebarMenuSubItem>
                  <SidebarMenuSubItem>
                    <SidebarMenuSubButton href="#">Women</SidebarMenuSubButton>
                  </SidebarMenuSubItem>
                </SidebarMenuSub>
              </SidebarMenuItem>
              <SidebarMenuItem>
                <SidebarMenuButton as="a" href="#">
                  <span>Goggles</span>
                </SidebarMenuButton>
              </SidebarMenuItem>
              <SidebarMenuItem>
                <SidebarMenuButton as="a" href="#">
                  <span>Backpacks</span>
                </SidebarMenuButton>
              </SidebarMenuItem>
            </SidebarMenu>
          </SidebarGroupContent>
        </SidebarGroup>
        <SidebarSeparator />
        <SidebarGroup>
          <SidebarGroupContent>
            <FilterSwitch label="In stock" description="Ready to ship today" :value="inStock" @change="inStock = $event" />
          </SidebarGroupContent>
        </SidebarGroup>
        <SidebarGroup>
          <SidebarGroupLabel>Colour</SidebarGroupLabel>
          <SidebarGroupAction v-if="colours.size" @click="colours.clear()">
            <XIcon data-icon />
            <span class="sr-only">Clear colours</span>
          </SidebarGroupAction>
          <SidebarGroupContent>
            <FilterSwatch :options="colourOptions" @toggle="toggleColour" />
          </SidebarGroupContent>
        </SidebarGroup>
        <SidebarGroup>
          <SidebarGroupLabel>Price</SidebarGroupLabel>
          <SidebarGroupContent>
            <FilterPrice aria-label="Price" :min="MIN" :max="MAX" :step="10" :precision="0" :value="price" @change="price = $event" />
          </SidebarGroupContent>
        </SidebarGroup>
      </SidebarContent>
      <SidebarFooter>
        <Button variant="outline" color="neutral" :disabled="!active.length" @click="clearAll">Clear all</Button>
      </SidebarFooter>
      <SidebarRail label="Toggle filters" />
    </Sidebar>
    <SidebarInset>
      <header class="flex items-center gap-2 p-4">
        <SidebarTrigger label="Toggle filters" />
        <h2>Men's jackets</h2>
        <span class="ms-auto">{{ shown.length }} products</span>
      </header>
      <ul class="flex flex-col px-4 pb-4">
        <li v-for="jacket in shown" :key="jacket.name" class="flex items-center justify-between gap-4 border-b border-border py-2">
          <span>{{ jacket.name }}</span>
          <Price :price="jacket.price" />
        </li>
      </ul>
    </SidebarInset>
  </SidebarProvider>
</template>
```

The example also uses `filter`, `price` and `tag-group`, which `add sidebar` leaves out: `npx @frontic/ui add filter price tag-group`.

## Anatomy

```vue
<SidebarProvider>
  <Sidebar>
    <SidebarHeader />
    <SidebarContent>
      <SidebarGroup>
        <SidebarGroupLabel />
        <SidebarGroupContent>
          <SidebarMenu>
            <SidebarMenuItem>
              <SidebarMenuButton />
              <SidebarMenuBadge />
              <SidebarMenuSub>
                <SidebarMenuSubItem>
                  <SidebarMenuSubButton />
                </SidebarMenuSubItem>
              </SidebarMenuSub>
            </SidebarMenuItem>
          </SidebarMenu>
        </SidebarGroupContent>
        <SidebarGroupAction />
      </SidebarGroup>
      <SidebarSeparator />
    </SidebarContent>
    <SidebarFooter />
    <SidebarRail />
  </Sidebar>
  <SidebarInset>
    <SidebarTrigger />
  </SidebarInset>
</SidebarProvider>
```

## API reference

### Sidebar

Holds the side navigation panel, which can collapse to icons or hide off-screen. Renders a `<div>`.

`data-slot="sidebar"` · `Sidebar.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `side` | `SidebarSide`: `'start' \| 'end'` | `'start'` | The edge the sidebar sits on: `start` is the left in a left-to-right page and the right in a right-to-left one. |
| `variant` | `SidebarVariant`: `'inset' \| 'sidebar' \| 'floating'` |  | Defaults to the provider's: `inset` under `<SidebarProvider variant="inset">`, else `sidebar`. |
| `collapsible` | `SidebarCollapsible`: `'icon' \| 'none' \| 'offcanvas'` | `'offcanvas'` | How the sidebar collapses: off the screen, down to icons, or not at all. |
| `label` | `string` | `'Sidebar'` | The sidebar's title for screen readers on mobile, where it opens as a sheet. |
| `description` | `string` | `'Displays the mobile sidebar.'` | The sidebar's description for screen readers on mobile, where it opens as a sheet. |

### SidebarContent

Holds the scrollable groups and menus between the sidebar header and footer. Renders a `<div>`.

`data-slot="sidebar-content"` · `SidebarContent.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'div'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-sidebar` | `'content'` | Names the sidebar element, so other sidebar parts can style against it. |

### SidebarFooter

Holds the content pinned at the bottom of the sidebar. Renders a `<div>`.

`data-slot="sidebar-footer"` · `SidebarFooter.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'div'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-sidebar` | `'footer'` | Names the sidebar element, so other sidebar parts can style against it. |

### SidebarGroup

Groups a label with the related menu items in the sidebar. Renders a `<div>`.

`data-slot="sidebar-group"` · `SidebarGroup.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'div'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-sidebar` | `'group'` | Names the sidebar element, so other sidebar parts can style against it. |

### SidebarGroupAction

Adds a small action button next to a sidebar group's label. Renders a `<button>`.

`data-slot="sidebar-group-action"` · `SidebarGroupAction.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'button'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-sidebar` | `'group-action'` | Names the sidebar element, so other sidebar parts can style against it. |

### SidebarGroupContent

Holds the menu belonging to a sidebar group. Renders a `<div>`.

`data-slot="sidebar-group-content"` · `SidebarGroupContent.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'div'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-sidebar` | `'group-content'` | Names the sidebar element, so other sidebar parts can style against it. |

### SidebarGroupLabel

Labels a group of sidebar items with a heading. Renders a `<div>`.

`data-slot="sidebar-group-label"` · `SidebarGroupLabel.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'div'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-sidebar` | `'group-label'` | Names the sidebar element, so other sidebar parts can style against it. |

### SidebarHeader

Holds the content pinned at the top of the sidebar, such as a search field. Renders a `<div>`.

`data-slot="sidebar-header"` · `SidebarHeader.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'div'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-sidebar` | `'header'` | Names the sidebar element, so other sidebar parts can style against it. |

### SidebarInput

Lets the user search or filter from within the sidebar.

`data-slot="sidebar-input"` · `SidebarInput.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-sidebar` | `'input'` | Names the sidebar element, so other sidebar parts can style against it. |

### SidebarInset

Holds the main page content beside the sidebar. Renders a `<main>`.

`data-slot="sidebar-inset"` · `SidebarInset.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'main'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

### SidebarMenu

Lists the navigation items of a sidebar group. Renders a `<ul>`.

`data-slot="sidebar-menu"` · `SidebarMenu.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'ul'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-sidebar` | `'menu'` | Names the sidebar element, so other sidebar parts can style against it. |

### SidebarMenuAction

Adds a secondary action button to a sidebar menu item. Renders a `<button>`.

`data-slot="sidebar-menu-action"` · `SidebarMenuAction.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'button'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `showOnHover` | `boolean` | `false` | Hides the action until its menu item is hovered or focused; always shown on mobile. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-sidebar` | `'menu-action'` | Names the sidebar element, so other sidebar parts can style against it. |

### SidebarMenuBadge

Shows a count or status beside a sidebar menu item. Renders a `<div>`.

`data-slot="sidebar-menu-badge"` · `SidebarMenuBadge.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'div'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-sidebar` | `'menu-badge'` | Names the sidebar element, so other sidebar parts can style against it. |

### SidebarMenuButton

Lets the user open a sidebar menu item, with a tooltip when collapsed. Renders a `<button>`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `SidebarMenuButtonVariant`: `'default' \| 'outline'` | `'default'` | The visual style: transparent, or outlined on the background colour. |
| `size` | `SidebarMenuButtonSize`: `'md' \| 'sm' \| 'lg'` | `'md'` | The height and text size of the button. |
| `isActive` | `boolean` | `false` | Highlights the button as the current page. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'button'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `tooltip` | `string \| Component`: `string \| ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions>` |  | Shown in a tooltip beside the button while the sidebar is collapsed to icons; text or a component. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-active` |  | Present while the item is the active one. |
| `data-sidebar` | `'menu-button'` | Names the sidebar element, so other sidebar parts can style against it. |
| `data-size` | `'md' \| 'sm' \| 'lg'` | Carries the `size` prop. |
| `data-size-lg` |  | Present when `size` is `lg`. |
| `data-size-md` |  | Present when `size` is `md`. |
| `data-size-sm` |  | Present when `size` is `sm`. |

### SidebarMenuItem

Groups one navigation entry with its button, action and badge. Renders a `<li>`.

`data-slot="sidebar-menu-item"` · `SidebarMenuItem.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'li'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-sidebar` | `'menu-item'` | Names the sidebar element, so other sidebar parts can style against it. |

### SidebarMenuSkeleton

Shows a placeholder for a sidebar menu item while it loads. Renders a `<div>`.

`data-slot="sidebar-menu-skeleton"` · `SidebarMenuSkeleton.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `showIcon` | `boolean` | `false` | Shows an icon placeholder before the text placeholder. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-sidebar` | `'menu-skeleton'` | Names the sidebar element, so other sidebar parts can style against it. |

### SidebarMenuSub

Lists the nested items under a sidebar menu item. Renders a `<ul>`.

`data-slot="sidebar-menu-sub"` · `SidebarMenuSub.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'ul'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-sidebar` | `'menu-sub'` | Names the sidebar element, so other sidebar parts can style against it. |

### SidebarMenuSubButton

Lets the user open a nested sidebar menu item. Renders a `<a>`.

`data-slot="sidebar-menu-sub-button"` · `SidebarMenuSubButton.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'a'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `size` | `'md' \| 'sm'` | `'md'` | The text size of the sub-item. |
| `isActive` | `boolean` | `false` | Highlights the sub-item as the current page. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-active` |  | Present while the item is the active one. |
| `data-sidebar` | `'menu-sub-button'` | Names the sidebar element, so other sidebar parts can style against it. |
| `data-size` | `'md' \| 'sm'` | Carries the `size` prop. |

### SidebarMenuSubItem

Groups one nested entry of a sidebar submenu. Renders a `<li>`.

`data-slot="sidebar-menu-sub-item"` · `SidebarMenuSubItem.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'li'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-sidebar` | `'menu-sub-item'` | Names the sidebar element, so other sidebar parts can style against it. |

### SidebarProvider

Groups all parts of the sidebar and shares its open state with them. Renders a `<div>`.

`data-slot="sidebar-wrapper"` · `SidebarProvider.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultOpen` | `boolean` | `!defaultDocument?.cookie.includes(`${SIDEBAR_COOKIE_NAME}=false`)` | Whether the sidebar starts expanded when `open` is not bound; read from the sidebar cookie when left out. |
| `open` | `boolean` |  | Whether the desktop sidebar is expanded, bound with v-model:open. |
| `variant` | `SidebarProviderVariant`: `'default' \| 'inset'` | `'default'` | `inset` paints the wrapper in the card colour, which `<Sidebar variant="inset">` needs. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Event | Payload | Description |
| --- | --- | --- |
| `update:open` | `open: boolean` | Fires with the new value when `open` changes; `v-model:open` binds it. |

### SidebarRail

Lets the user toggle the sidebar by clicking along its edge. Renders a `<button>`.

`data-slot="sidebar-rail"` · `SidebarRail.vue`

Styling: `sidebar-rail-line`, which extends [`divider`](https://ui.frontic.com/DESIGN.md#divider). Change it there and every part that extends it follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `string` | `'Toggle Sidebar'` | Read by screen readers and shown as the tooltip on the rail that toggles the sidebar. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-sidebar` | `'rail'` | Names the sidebar element, so other sidebar parts can style against it. |

### SidebarSeparator

Separates sections within the sidebar.

`data-slot="sidebar-separator"` · `SidebarSeparator.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-sidebar` | `'separator'` | Names the sidebar element, so other sidebar parts can style against it. |

### SidebarTrigger

Lets the user expand and collapse the sidebar.

`data-slot="sidebar-trigger"` · `SidebarTrigger.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `string` | `'Toggle Sidebar'` | Read by screen readers on the button that toggles the sidebar. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-sidebar` | `'trigger'` | Names the sidebar element, so other sidebar parts can style against it. |

## Exports

```ts
import { type SidebarMenuButtonSize, type SidebarMenuButtonVariant, type SidebarCollapsible, type SidebarProviderVariant, type SidebarSide, type SidebarVariant, useSidebar, type SidebarProps } from '@/components/ui/sidebar'
```

- `SidebarMenuButtonSize`: `'md' \| 'sm' \| 'lg'`
- `SidebarMenuButtonVariant`: `'default' \| 'outline'`
- `SidebarCollapsible`: `'offcanvas' \| 'icon' \| 'none'`
- `SidebarProviderVariant`: `'default' \| 'inset'`
- `SidebarSide`: `'start' \| 'end'`
- `SidebarVariant`: `'sidebar' \| 'floating' \| 'inset'`
- `useSidebar`
- `SidebarProps`: below

```ts
interface SidebarProps {
  /** The edge the sidebar sits on: `start` is the left in a left-to-right page and the right in a right-to-left one. */
  side?: SidebarSide
  /** Defaults to the provider's: `inset` under `<SidebarProvider variant="inset">`, else `sidebar`. */
  variant?: SidebarVariant
  /** How the sidebar collapses: off the screen, down to icons, or not at all. */
  collapsible?: SidebarCollapsible
  /** The sidebar's title for screen readers on mobile, where it opens as a sheet. */
  label?: string
  /** The sidebar's description for screen readers on mobile, where it opens as a sheet. */
  description?: string
  /** Lands on the container. A width class here also stops icon collapse; set the width with `--fui-sidebar-size-width` on SidebarProvider. */
  class?: HTMLAttributes['class']
}
```

## Styling

| Part | Recipe |
| --- | --- |
| `default` | [`sidebar`](https://ui.frontic.com/DESIGN.md#sidebar) |
| `size` | [`sidebar-size`](https://ui.frontic.com/DESIGN.md#sidebar-size) |
| `container` | [`sidebar-container`](https://ui.frontic.com/DESIGN.md#sidebar-container) |
| `wrapper` | [`sidebar-wrapper`](https://ui.frontic.com/DESIGN.md#sidebar-wrapper) |
| `inset` | [`sidebar-inset`](https://ui.frontic.com/DESIGN.md#sidebar-inset) |
| `header` | [`sidebar-header`](https://ui.frontic.com/DESIGN.md#sidebar-header) |
| `footer` | [`sidebar-footer`](https://ui.frontic.com/DESIGN.md#sidebar-footer) |
| `group` | [`sidebar-group`](https://ui.frontic.com/DESIGN.md#sidebar-group) |
| `menu` | [`sidebar-menu`](https://ui.frontic.com/DESIGN.md#sidebar-menu) |
| `separator` | [`sidebar-separator`](https://ui.frontic.com/DESIGN.md#sidebar-separator) |
| `content` | [`sidebar-content`](https://ui.frontic.com/DESIGN.md#sidebar-content) |
| `input` | [`sidebar-input`](https://ui.frontic.com/DESIGN.md#sidebar-input) |
| `group-label` | [`sidebar-group-label`](https://ui.frontic.com/DESIGN.md#sidebar-group-label) |
| `group-content` | [`sidebar-group-content`](https://ui.frontic.com/DESIGN.md#sidebar-group-content) |
| `action` | [`sidebar-action`](https://ui.frontic.com/DESIGN.md#sidebar-action) |
| `menu-badge` | [`sidebar-menu-badge`](https://ui.frontic.com/DESIGN.md#sidebar-menu-badge) |
| `menu-button` | [`sidebar-menu-button`](https://ui.frontic.com/DESIGN.md#sidebar-menu-button) |
| `menu-sub` | [`sidebar-menu-sub`](https://ui.frontic.com/DESIGN.md#sidebar-menu-sub) |
| `menu-sub-button` | [`sidebar-menu-sub-button`](https://ui.frontic.com/DESIGN.md#sidebar-menu-sub-button) |
| `menu-skeleton` | [`sidebar-menu-skeleton`](https://ui.frontic.com/DESIGN.md#sidebar-menu-skeleton) |
| `rail` | [`sidebar-rail`](https://ui.frontic.com/DESIGN.md#sidebar-rail) |
| `rail-line` | [`sidebar-rail-line`](https://ui.frontic.com/DESIGN.md#sidebar-rail-line) |
| `focus-ring` | [`focus-ring`](https://ui.frontic.com/DESIGN.md#focus-ring) |
| `disabled` | [`disabled`](https://ui.frontic.com/DESIGN.md#disabled) |

States with a look of their own: hover, focus and disabled.

## Source

`components/ui/sidebar/Sidebar.vue`

```vue
<script lang="ts">
/** Holds the side navigation panel, which can collapse to icons or hide off-screen. */
export default {}
</script>

<script setup lang="ts">
import type { SidebarProps } from '.'
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from '@library/components/ui/sheet'
import { cva } from 'class-variance-authority'
import { useDirection } from 'reka-ui'
import { computed, toRef } from 'vue'
import { physicalSide, provideSidebarInstance, sidebarLayout, useSidebar } from './utils'

const sidebar = cva('fui-sidebar', {
  variants: {
    variant: {
      sidebar: 'fui-sidebar-sidebar',
      inset: 'fui-sidebar-variant-inset',
      floating: 'fui-sidebar-floating',
    },
  },
  defaultVariants: { variant: 'sidebar' },
})

const sidebarSize = cva('fui-sidebar-size relative transition-all duration-200 ease-linear', {
  variants: {
    width: {
      icon: 'fui-sidebar-size-icon',
      'icon-padded': 'fui-sidebar-size-icon-padded',
      offcanvas: 'fui-sidebar-size-offcanvas',
      mobile: 'fui-sidebar-size-mobile',
    },
  },
})

const sidebarContainer = cva('fui-sidebar-container fixed inset-y-0 z-10 transition-all duration-200 ease-linear', {
  variants: {
    edge: {
      start: 'fui-sidebar-container-start',
      end: 'fui-sidebar-container-end',
      padded: 'fui-sidebar-container-padded',
    },
    width: {
      icon: 'fui-sidebar-container-icon',
      'icon-padded': 'fui-sidebar-container-icon-padded',
    },
  },
  defaultVariants: { edge: 'start' },
})

defineOptions({
  inheritAttrs: false,
})

const props = withDefaults(defineProps<SidebarProps>(), {
  side: 'start',
  collapsible: 'offcanvas',
  label: 'Sidebar',
  description: 'Displays the mobile sidebar.',
})

const { isMobile, state, openMobile, setOpenMobile, variant: layoutVariant } = useSidebar()
const dir = useDirection()

const variant = computed(() => props.variant ?? (layoutVariant.value === 'inset' ? 'inset' : 'sidebar'))
const dev = (import.meta as ImportMeta & { env?: { DEV?: boolean } }).env?.DEV
if (dev && props.variant === 'inset' && layoutVariant.value !== 'inset')
  console.warn('<Sidebar variant="inset"> needs <SidebarProvider variant="inset">, which paints the wrapper and raises SidebarInset.')
provideSidebarInstance({ side: toRef(props, 'side'), collapsible: toRef(props, 'collapsible') })

const layout = computed(() => sidebarLayout(props.side, variant.value, props.collapsible, state.value === 'collapsed'))
</script>

<template>
  <div v-if="collapsible === 'none'" data-slot="sidebar" :class="[sidebar(), 'fui-sidebar-size', props.class]" v-bind="$attrs">
    <slot />
  </div>

  <Sheet v-else-if="isMobile" :open="openMobile" v-bind="$attrs" @update:open="setOpenMobile">
    <SheetContent data-sidebar="sidebar" data-slot="sidebar" data-mobile="true" :side="physicalSide(side, dir)" :show-close-button="false" class="fui-sidebar-size fui-sidebar-size-mobile">
      <SheetHeader class="sr-only">
        <SheetTitle>{{ props.label }}</SheetTitle>
        <SheetDescription>{{ props.description }}</SheetDescription>
      </SheetHeader>
      <div :class="[sidebar(), 'h-full w-full']">
        <slot />
      </div>
    </SheetContent>
  </Sheet>

  <div
    v-else
    class="group peer block"
    data-slot="sidebar"
    :data-state="state"
    :data-collapsible="layout.collapsedTo ?? ''"
    :data-collapsed="state === 'collapsed' ? '' : undefined"
    :data-collapsible-icon="layout.collapsedTo === 'icon' ? '' : undefined"
    :data-collapsible-offcanvas="layout.collapsedTo === 'offcanvas' ? '' : undefined"
    :data-variant="variant"
    :data-side="side"
  >
    <div data-slot="sidebar-gap" :class="[sidebarSize({ width: layout.gap }), side === 'end' && 'rotate-180']" />
    <div data-slot="sidebar-container" :data-side="side" :class="['fui-sidebar-size', sidebarContainer({ edge: layout.edge, width: layout.container }), layout.offset, props.class]" v-bind="$attrs">
      <div data-sidebar="sidebar" data-slot="sidebar-inner" :class="[sidebar({ variant }), 'size-full']">
        <slot />
      </div>
    </div>
  </div>
</template>
```

`components/ui/sidebar/SidebarContent.vue`

```vue
<script lang="ts">
/** Holds the scrollable groups and menus between the sidebar header and footer. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
    }
  >(),
  { as: 'div' },
)
</script>

<template>
  <Primitive
    :as="props.as"
    :as-child="props.asChild"
    data-slot="sidebar-content"
    data-sidebar="content"
    :class="['fui-sidebar-content overflow-auto group-data-collapsible-icon:overflow-hidden', props.class]"
  >
    <slot />
  </Primitive>
</template>
```

`components/ui/sidebar/SidebarFooter.vue`

```vue
<script lang="ts">
/** Holds the content pinned at the bottom of the sidebar. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
    }
  >(),
  { as: 'div' },
)
</script>

<template>
  <Primitive :as="props.as" :as-child="props.asChild" data-slot="sidebar-footer" data-sidebar="footer" :class="['fui-sidebar-footer', props.class]">
    <slot />
  </Primitive>
</template>
```

`components/ui/sidebar/SidebarGroup.vue`

```vue
<script lang="ts">
/** Groups a label with the related menu items in the sidebar. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
    }
  >(),
  { as: 'div' },
)
</script>

<template>
  <Primitive
    :as="props.as"
    :as-child="props.asChild"
    data-slot="sidebar-group"
    data-sidebar="group"
    :class="['fui-sidebar-group relative group-data-collapsible-icon:[&>[data-sidebar=group-label]]:-mt-8', props.class]"
  >
    <slot />
  </Primitive>
</template>
```

`components/ui/sidebar/SidebarGroupAction.vue`

```vue
<script lang="ts">
/** Adds a small action button next to a sidebar group's label. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { Primitive } from 'reka-ui'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
    }
  >(),
  {
    as: 'button',
  },
)
</script>

<template>
  <Primitive
    data-slot="sidebar-group-action"
    data-sidebar="group-action"
    :as="as"
    :as-child="asChild"
    :class="[
      'fui-sidebar-action absolute top-3.5 inset-e-3 aspect-square outline-none transition-transform after:absolute after:-inset-2 group-data-collapsible-icon:hidden focus-visible:fui-focus-ring *:data-icon:shrink-0',
      props.class,
    ]"
  >
    <slot />
  </Primitive>
</template>
```

`components/ui/sidebar/SidebarGroupContent.vue`

```vue
<script lang="ts">
/** Holds the menu belonging to a sidebar group. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
    }
  >(),
  { as: 'div' },
)
</script>

<template>
  <Primitive :as="props.as" :as-child="props.asChild" data-slot="sidebar-group-content" data-sidebar="group-content" :class="['fui-sidebar-group-content', props.class]">
    <slot />
  </Primitive>
</template>
```

`components/ui/sidebar/SidebarGroupLabel.vue`

```vue
<script lang="ts">
/** Labels a group of sidebar items with a heading. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { Primitive } from 'reka-ui'

const props = defineProps<
  PrimitiveProps & {
    class?: HTMLAttributes['class']
  }
>()
</script>

<template>
  <Primitive
    data-slot="sidebar-group-label"
    data-sidebar="group-label"
    :as="as"
    :as-child="asChild"
    :class="[
      'fui-sidebar-group-label outline-none transition-all duration-200 ease-linear group-data-collapsible-icon:pointer-events-none group-data-collapsible-icon:select-none group-data-collapsible-icon:opacity-0 focus-visible:fui-focus-ring *:data-icon:shrink-0',
      props.class,
    ]"
  >
    <slot />
  </Primitive>
</template>
```

`components/ui/sidebar/SidebarHeader.vue`

```vue
<script lang="ts">
/** Holds the content pinned at the top of the sidebar, such as a search field. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
    }
  >(),
  { as: 'div' },
)
</script>

<template>
  <Primitive :as="props.as" :as-child="props.asChild" data-slot="sidebar-header" data-sidebar="header" :class="['fui-sidebar-header', props.class]">
    <slot />
  </Primitive>
</template>
```

`components/ui/sidebar/SidebarInput.vue`

```vue
<script lang="ts">
/** Lets the user search or filter from within the sidebar. */
export default {}
</script>

<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { Input } from '@library/components/ui/input'

const props = defineProps<{
  class?: HTMLAttributes['class']
}>()
</script>

<template>
  <Input data-slot="sidebar-input" data-sidebar="input" :class="['fui-sidebar-input', props.class]">
    <slot />
  </Input>
</template>
```

`components/ui/sidebar/SidebarInset.vue`

```vue
<script lang="ts">
/** Holds the main page content beside the sidebar. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { cva } from 'class-variance-authority'
import { computed } from 'vue'
import { useSidebar } from './utils'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
    }
  >(),
  { as: 'main' },
)

const { isMobile, variant } = useSidebar()
const raised = computed(() => variant.value === 'inset' && !isMobile.value)

const sidebarInset = cva('fui-sidebar-inset relative', {
  variants: {
    variant: {
      flat: 'fui-sidebar-inset-flat',
      raised: 'fui-sidebar-inset-raised',
    },
  },
  defaultVariants: { variant: 'flat' },
})
</script>

<template>
  <Primitive :as="props.as" :as-child="props.asChild" data-slot="sidebar-inset" :class="[sidebarInset({ variant: raised ? 'raised' : 'flat' }), raised && 'peer-data-collapsed:ms-2', props.class]">
    <slot />
  </Primitive>
</template>
```

`components/ui/sidebar/SidebarMenu.vue`

```vue
<script lang="ts">
/** Lists the navigation items of a sidebar group. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
    }
  >(),
  { as: 'ul' },
)
</script>

<template>
  <Primitive :as="props.as" :as-child="props.asChild" data-slot="sidebar-menu" data-sidebar="menu" :class="['fui-sidebar-menu', props.class]">
    <slot />
  </Primitive>
</template>
```

`components/ui/sidebar/SidebarMenuAction.vue`

```vue
<script lang="ts">
/** Adds a secondary action button to a sidebar menu item. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { Primitive } from 'reka-ui'
import { computed } from 'vue'
import { useSidebar } from './utils'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      /** Hides the action until its menu item is hovered or focused; always shown on mobile. */
      showOnHover?: boolean
      class?: HTMLAttributes['class']
    }
  >(),
  {
    as: 'button',
  },
)

const { isMobile } = useSidebar()
const hidden = computed(() => props.showOnHover && !isMobile.value)
</script>

<template>
  <Primitive
    data-slot="sidebar-menu-action"
    data-sidebar="menu-action"
    :class="[
      'fui-sidebar-action absolute top-1.5 inset-e-1 aspect-square outline-none transition-transform after:absolute after:-inset-2 peer-data-size-md/menu-button:top-2 peer-data-size-sm/menu-button:top-1 peer-data-size-lg/menu-button:top-2.5 group-data-collapsible-icon:hidden focus-visible:fui-focus-ring *:data-icon:shrink-0',
      hidden && 'opacity-0 group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 aria-expanded:opacity-100',
      props.class,
    ]"
    :as="as"
    :as-child="asChild"
  >
    <slot />
  </Primitive>
</template>
```

`components/ui/sidebar/SidebarMenuBadge.vue`

```vue
<script lang="ts">
/** Shows a count or status beside a sidebar menu item. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
    }
  >(),
  { as: 'div' },
)
</script>

<template>
  <Primitive
    :as="props.as"
    :as-child="props.asChild"
    data-slot="sidebar-menu-badge"
    data-sidebar="menu-badge"
    :class="[
      'fui-sidebar-menu-badge pointer-events-none absolute inset-e-1 top-1.5 tabular-nums select-none peer-data-size-sm/menu-button:top-1 peer-data-size-lg/menu-button:top-2.5 group-data-collapsible-icon:hidden',
      props.class,
    ]"
  >
    <slot />
  </Primitive>
</template>
```

`components/ui/sidebar/SidebarMenuButton.vue`

```vue
<script lang="ts">
/** Lets the user open a sidebar menu item, with a tooltip when collapsed. */
export default {}
</script>

<script setup lang="ts">
import type { Component } from 'vue'
import type { SidebarMenuButtonProps } from './SidebarMenuButtonChild.vue'
import { reactiveOmit } from '@vueuse/core'
import { useDirection } from 'reka-ui'
import { Tooltip, TooltipContent, TooltipTrigger } from '@library/components/ui/tooltip'
import SidebarMenuButtonChild from './SidebarMenuButtonChild.vue'
import { injectSidebarInstance, physicalSide, useSidebar } from './utils'

defineOptions({
  inheritAttrs: false,
})

const props = withDefaults(
  defineProps<
    SidebarMenuButtonProps & {
      /** Shown in a tooltip beside the button while the sidebar is collapsed to icons; text or a component. */
      tooltip?: string | Component
    }
  >(),
  {
    as: 'button',
    variant: 'default',
    size: 'md',
  },
)

const { isMobile, state } = useSidebar()
const sidebar = injectSidebarInstance(null)
const dir = useDirection()

const delegatedProps = reactiveOmit(props, 'tooltip')
</script>

<template>
  <SidebarMenuButtonChild v-if="!tooltip" v-bind="{ ...delegatedProps, ...$attrs }">
    <slot />
  </SidebarMenuButtonChild>

  <Tooltip v-else>
    <TooltipTrigger as-child>
      <SidebarMenuButtonChild v-bind="{ ...delegatedProps, ...$attrs }">
        <slot />
      </SidebarMenuButtonChild>
    </TooltipTrigger>
    <TooltipContent :side="physicalSide(sidebar?.side.value === 'end' ? 'start' : 'end', dir)" align="center" :hidden="state !== 'collapsed' || isMobile">
      <template v-if="typeof tooltip === 'string'">
        {{ tooltip }}
      </template>
      <component :is="tooltip" v-else />
    </TooltipContent>
  </Tooltip>
</template>
```

`components/ui/sidebar/SidebarMenuButtonChild.vue`

```vue
<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { cva } from 'class-variance-authority'
import { Primitive } from 'reka-ui'
import { computed } from 'vue'
import { injectSidebarInstance, useSidebar } from './utils'

export type SidebarMenuButtonVariant = 'default' | 'outline'
export type SidebarMenuButtonSize = 'md' | 'sm' | 'lg'

export interface SidebarMenuButtonProps extends PrimitiveProps {
  /** The visual style: transparent, or outlined on the background colour. */
  variant?: SidebarMenuButtonVariant
  /** The height and text size of the button. */
  size?: SidebarMenuButtonSize
  /** Highlights the button as the current page. */
  isActive?: boolean
  class?: HTMLAttributes['class']
}

const sidebarMenuButton = cva(
  'fui-sidebar-menu-button peer/menu-button group/menu-button overflow-hidden outline-none transition-all focus-visible:fui-focus-ring disabled:pointer-events-none disabled:fui-disabled aria-disabled:pointer-events-none aria-disabled:fui-disabled [&>span:last-child]:truncate *:data-icon:shrink-0 not-in-data-collapsible-icon:group-has-[[data-sidebar=menu-action]]/menu-item:pe-8',
  {
    variants: {
      variant: {
        default: 'fui-sidebar-menu-button-default',
        outline: 'fui-sidebar-menu-button-outline',
      },
      size: {
        md: 'fui-sidebar-menu-button-md',
        sm: 'fui-sidebar-menu-button-sm',
        lg: 'fui-sidebar-menu-button-lg',
      },
      mode: {
        expanded: 'fui-sidebar-menu-button-expanded',
        collapsed: 'fui-sidebar-menu-button-collapsed',
      },
    },
    defaultVariants: {
      variant: 'default',
      size: 'md',
      mode: 'expanded',
    },
  },
)

const props = withDefaults(defineProps<SidebarMenuButtonProps>(), {
  as: 'button',
  variant: 'default',
  size: 'md',
})

const { isMobile, state } = useSidebar()
const sidebar = injectSidebarInstance(null)
const iconOnly = computed(() => !isMobile.value && state.value === 'collapsed' && sidebar?.collapsible.value === 'icon')
</script>

<template>
  <Primitive
    data-slot="sidebar-menu-button"
    data-sidebar="menu-button"
    :data-size="size"
    :data-size-md="size === 'md' ? '' : undefined"
    :data-size-sm="size === 'sm' ? '' : undefined"
    :data-size-lg="size === 'lg' ? '' : undefined"
    :data-active="isActive || undefined"
    :class="[sidebarMenuButton({ variant, size, mode: iconOnly ? 'collapsed' : 'expanded' }), props.class]"
    :as="as"
    :as-child="asChild"
    v-bind="$attrs"
  >
    <slot />
  </Primitive>
</template>
```

`components/ui/sidebar/SidebarMenuItem.vue`

```vue
<script lang="ts">
/** Groups one navigation entry with its button, action and badge. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
    }
  >(),
  { as: 'li' },
)
</script>

<template>
  <Primitive :as="props.as" :as-child="props.asChild" data-slot="sidebar-menu-item" data-sidebar="menu-item" :class="['group/menu-item relative', props.class]">
    <slot />
  </Primitive>
</template>
```

`components/ui/sidebar/SidebarMenuSkeleton.vue`

```vue
<script lang="ts">
/** Shows a placeholder for a sidebar menu item while it loads. */
export default {}
</script>

<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { Skeleton } from '@library/components/ui/skeleton'
import { computed } from 'vue'

const props = defineProps<{
  /** Shows an icon placeholder before the text placeholder. */
  showIcon?: boolean
  class?: HTMLAttributes['class']
}>()

const width = computed(() => `${Math.floor(Math.random() * 40) + 50}%`)
</script>

<template>
  <div data-slot="sidebar-menu-skeleton" data-sidebar="menu-skeleton" :class="['fui-sidebar-menu-skeleton', props.class]">
    <Skeleton v-if="showIcon" class="size-3.5 rounded-none" data-sidebar="menu-skeleton-icon" />
    <Skeleton class="h-4 flex-1" data-sidebar="menu-skeleton-text" :style="{ maxWidth: width }" />
  </div>
</template>
```

`components/ui/sidebar/SidebarMenuSub.vue`

```vue
<script lang="ts">
/** Lists the nested items under a sidebar menu item. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
    }
  >(),
  { as: 'ul' },
)
</script>

<template>
  <Primitive
    :as="props.as"
    :as-child="props.asChild"
    data-slot="sidebar-menu-sub"
    data-sidebar="menu-sub"
    :class="['fui-sidebar-menu-sub translate-x-px group-data-collapsible-icon:hidden', props.class]"
  >
    <slot />
  </Primitive>
</template>
```

`components/ui/sidebar/SidebarMenuSubButton.vue`

```vue
<script lang="ts">
/** Lets the user open a nested sidebar menu item. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { cva } from 'class-variance-authority'
import { Primitive } from 'reka-ui'

const sidebarMenuSubButton = cva(
  'fui-sidebar-menu-sub-button -translate-x-px overflow-hidden outline-none group-data-collapsible-icon:hidden focus-visible:fui-focus-ring disabled:pointer-events-none disabled:fui-disabled aria-disabled:pointer-events-none aria-disabled:fui-disabled [&>span:last-child]:truncate *:data-icon:shrink-0',
  {
    variants: {
      size: {
        sm: 'fui-sidebar-menu-sub-button-sm',
        md: 'fui-sidebar-menu-sub-button-md',
      },
    },
    defaultVariants: { size: 'md' },
  },
)

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      /** The text size of the sub-item. */
      size?: 'sm' | 'md'
      /** Highlights the sub-item as the current page. */
      isActive?: boolean
      class?: HTMLAttributes['class']
    }
  >(),
  {
    as: 'a',
    size: 'md',
  },
)
</script>

<template>
  <Primitive
    data-slot="sidebar-menu-sub-button"
    data-sidebar="menu-sub-button"
    :as="as"
    :as-child="asChild"
    :data-size="size"
    :data-active="isActive || undefined"
    :class="[sidebarMenuSubButton({ size }), props.class]"
  >
    <slot />
  </Primitive>
</template>
```

`components/ui/sidebar/SidebarMenuSubItem.vue`

```vue
<script lang="ts">
/** Groups one nested entry of a sidebar submenu. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
    }
  >(),
  { as: 'li' },
)
</script>

<template>
  <Primitive :as="props.as" :as-child="props.asChild" data-slot="sidebar-menu-sub-item" data-sidebar="menu-sub-item" :class="['group/menu-sub-item relative', props.class]">
    <slot />
  </Primitive>
</template>
```

`components/ui/sidebar/SidebarProvider.vue`

```vue
<script lang="ts">
/** Groups all parts of the sidebar and shares its open state with them. */
export default {}
</script>

<script setup lang="ts">
import type { HTMLAttributes, Ref } from 'vue'
import type { SidebarProviderVariant } from './utils'
import { defaultDocument, useEventListener, useMediaQuery, useVModel } from '@vueuse/core'
import { cva } from 'class-variance-authority'
import { TooltipProvider } from 'reka-ui'
import { computed, ref, toRef } from 'vue'
import { provideSidebarContext, SIDEBAR_COOKIE_MAX_AGE, SIDEBAR_COOKIE_NAME, SIDEBAR_KEYBOARD_SHORTCUT } from './utils'

const props = withDefaults(
  defineProps<{
    /** Whether the sidebar starts expanded when `open` is not bound; read from the sidebar cookie when left out. */
    defaultOpen?: boolean
    /** Whether the desktop sidebar is expanded, bound with v-model:open. */
    open?: boolean
    /** `inset` paints the wrapper in the card colour, which `<Sidebar variant="inset">` needs. */
    variant?: SidebarProviderVariant
    class?: HTMLAttributes['class']
  }>(),
  {
    variant: 'default',
    defaultOpen: !defaultDocument?.cookie.includes(`${SIDEBAR_COOKIE_NAME}=false`),
    open: undefined,
  },
)

const emits = defineEmits<{
  'update:open': [open: boolean]
}>()

const isMobile = useMediaQuery('(max-width: 768px)')
const openMobile = ref(false)

const open = useVModel(props, 'open', emits, {
  defaultValue: props.defaultOpen ?? false,
  passive: (props.open === undefined) as false,
}) as Ref<boolean>

function setOpen(value: boolean) {
  open.value = value
  document.cookie = `${SIDEBAR_COOKIE_NAME}=${open.value}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
}

function setOpenMobile(value: boolean) {
  openMobile.value = value
}

function toggleSidebar() {
  return isMobile.value ? setOpenMobile(!openMobile.value) : setOpen(!open.value)
}

useEventListener('keydown', (event: KeyboardEvent) => {
  if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
    event.preventDefault()
    toggleSidebar()
  }
})

const state = computed(() => (open.value ? 'expanded' : 'collapsed'))

provideSidebarContext({
  state,
  open,
  setOpen,
  isMobile,
  openMobile,
  setOpenMobile,
  toggleSidebar,
  variant: toRef(props, 'variant'),
})

const sidebarWrapper = cva('fui-sidebar-wrapper group/sidebar-wrapper', {
  variants: {
    variant: {
      default: 'fui-sidebar-wrapper-default',
      inset: 'fui-sidebar-wrapper-inset',
    },
  },
  defaultVariants: { variant: 'default' },
})
</script>

<template>
  <TooltipProvider :delay-duration="0">
    <div data-slot="sidebar-wrapper" :class="[sidebarWrapper({ variant: props.variant }), props.class]" v-bind="$attrs">
      <slot />
    </div>
  </TooltipProvider>
</template>
```

`components/ui/sidebar/SidebarRail.vue`

```vue
<script lang="ts">
/** Lets the user toggle the sidebar by clicking along its edge. */
export default {}
</script>

<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cva } from 'class-variance-authority'
import { computed } from 'vue'
import { injectSidebarInstance, useSidebar } from './utils'

const props = withDefaults(
  defineProps<{
    /** Read by screen readers and shown as the tooltip on the rail that toggles the sidebar. */
    label?: string
    class?: HTMLAttributes['class']
  }>(),
  {
    label: 'Toggle Sidebar',
  },
)

const { state, toggleSidebar } = useSidebar()
const { collapsible, side } = injectSidebarInstance()
const offcanvas = computed(() => state.value === 'collapsed' && collapsible.value === 'offcanvas')
const collapsed = computed(() => state.value === 'collapsed')

const sidebarRail = cva('fui-sidebar-rail group/rail absolute inset-y-0 z-20 transition-all ease-linear', {
  variants: {
    mode: {
      default: 'fui-sidebar-rail-default',
      offcanvas: 'fui-sidebar-rail-offcanvas',
    },
  },
  defaultVariants: { mode: 'default' },
})
</script>

<template>
  <button
    data-sidebar="rail"
    data-slot="sidebar-rail"
    :aria-label="props.label"
    :tabindex="-1"
    :title="props.label"
    :class="[
      sidebarRail({ mode: offcanvas ? 'offcanvas' : 'default' }),
      (side === 'start') === collapsed ? 'cursor-e-resize rtl:cursor-w-resize' : 'cursor-w-resize rtl:cursor-e-resize',
      offcanvas ? (side === 'start' ? '-inset-e-2' : '-inset-s-2') : side === 'start' ? '-inset-e-4 -translate-x-1/2 rtl:translate-x-1/2' : 'inset-s-0 -translate-x-1/2 rtl:translate-x-1/2',
      props.class,
    ]"
    @click="toggleSidebar"
  >
    <span aria-hidden="true" :class="['absolute inset-y-0 opacity-0 group-hover/rail:opacity-100', offcanvas ? 'inset-s-full' : 'inset-s-1/2']">
      <span data-slot="sidebar-rail-line" class="fui-sidebar-rail-line block h-full" />
    </span>
    <slot />
  </button>
</template>
```

`components/ui/sidebar/SidebarSeparator.vue`

```vue
<script lang="ts">
/** Separates sections within the sidebar. */
export default {}
</script>

<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { Separator } from '@library/components/ui/separator'

const props = defineProps<{
  class?: HTMLAttributes['class']
}>()
</script>

<template>
  <Separator data-slot="sidebar-separator" data-sidebar="separator" :class="['fui-sidebar-separator', props.class]">
    <slot />
  </Separator>
</template>
```

`components/ui/sidebar/SidebarTrigger.vue`

```vue
<script lang="ts">
/** Lets the user expand and collapse the sidebar. */
export default {}
</script>

<script setup lang="ts">
import { PanelLeftIcon } from '@lucide/vue'

import type { HTMLAttributes } from 'vue'
import { Button } from '@library/components/ui/button'
import { useSidebar } from './utils'

const props = withDefaults(
  defineProps<{
    /** Read by screen readers on the button that toggles the sidebar. */
    label?: string
    class?: HTMLAttributes['class']
  }>(),
  {
    label: 'Toggle Sidebar',
  },
)

const { toggleSidebar } = useSidebar()
</script>

<template>
  <Button data-sidebar="trigger" data-slot="sidebar-trigger" variant="ghost" color="neutral" size="icon-sm" :class="props.class" @click="toggleSidebar">
    <PanelLeftIcon class="rtl:-scale-x-100" data-icon />
    <span class="sr-only">{{ props.label }}</span>
  </Button>
</template>
```

`components/ui/sidebar/index.ts`

```ts
import type { HTMLAttributes } from 'vue'
import type { SidebarCollapsible, SidebarSide, SidebarVariant } from './utils'

export interface SidebarProps {
  /** The edge the sidebar sits on: `start` is the left in a left-to-right page and the right in a right-to-left one. */
  side?: SidebarSide
  /** Defaults to the provider's: `inset` under `<SidebarProvider variant="inset">`, else `sidebar`. */
  variant?: SidebarVariant
  /** How the sidebar collapses: off the screen, down to icons, or not at all. */
  collapsible?: SidebarCollapsible
  /** The sidebar's title for screen readers on mobile, where it opens as a sheet. */
  label?: string
  /** The sidebar's description for screen readers on mobile, where it opens as a sheet. */
  description?: string
  /** Lands on the container. A width class here also stops icon collapse; set the width with `--fui-sidebar-size-width` on SidebarProvider. */
  class?: HTMLAttributes['class']
}

export { default as Sidebar } from './Sidebar.vue'
export { default as SidebarContent } from './SidebarContent.vue'
export { default as SidebarFooter } from './SidebarFooter.vue'
export { default as SidebarGroup } from './SidebarGroup.vue'
export { default as SidebarGroupAction } from './SidebarGroupAction.vue'
export { default as SidebarGroupContent } from './SidebarGroupContent.vue'
export { default as SidebarGroupLabel } from './SidebarGroupLabel.vue'
export { default as SidebarHeader } from './SidebarHeader.vue'
export { default as SidebarInput } from './SidebarInput.vue'
export { default as SidebarInset } from './SidebarInset.vue'
export { default as SidebarMenu } from './SidebarMenu.vue'
export { default as SidebarMenuAction } from './SidebarMenuAction.vue'
export { default as SidebarMenuBadge } from './SidebarMenuBadge.vue'
export { default as SidebarMenuButton } from './SidebarMenuButton.vue'
export type { SidebarMenuButtonSize, SidebarMenuButtonVariant } from './SidebarMenuButtonChild.vue'
export { default as SidebarMenuItem } from './SidebarMenuItem.vue'
export { default as SidebarMenuSkeleton } from './SidebarMenuSkeleton.vue'
export { default as SidebarMenuSub } from './SidebarMenuSub.vue'
export { default as SidebarMenuSubButton } from './SidebarMenuSubButton.vue'
export { default as SidebarMenuSubItem } from './SidebarMenuSubItem.vue'
export { default as SidebarProvider } from './SidebarProvider.vue'
export { default as SidebarRail } from './SidebarRail.vue'
export { default as SidebarSeparator } from './SidebarSeparator.vue'
export { default as SidebarTrigger } from './SidebarTrigger.vue'
export type { SidebarCollapsible, SidebarProviderVariant, SidebarSide, SidebarVariant } from './utils'
export { useSidebar } from './utils'
```

`components/ui/sidebar/utils.ts`

```ts
import type { ComputedRef, Ref } from 'vue'
import { createContext, type Direction } from 'reka-ui'

export const SIDEBAR_COOKIE_NAME = 'sidebar_state'
export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
export const SIDEBAR_KEYBOARD_SHORTCUT = 'b'

export type SidebarSide = 'start' | 'end'
export type SidebarVariant = 'sidebar' | 'floating' | 'inset'
export type SidebarCollapsible = 'offcanvas' | 'icon' | 'none'
export type SidebarProviderVariant = 'default' | 'inset'

export const [useSidebar, provideSidebarContext] = createContext<{
  state: ComputedRef<'expanded' | 'collapsed'>
  open: Ref<boolean>
  setOpen: (value: boolean) => void
  isMobile: Ref<boolean>
  openMobile: Ref<boolean>
  setOpenMobile: (value: boolean) => void
  toggleSidebar: () => void
  /** The layout's variant: `inset` paints the wrapper, raises SidebarInset and is its Sidebars' default. */
  variant: Ref<SidebarProviderVariant>
}>('SidebarProvider')

/** One Sidebar's own props, for the rail and menu buttons inside it. */
export const [injectSidebarInstance, provideSidebarInstance] = createContext<{
  side: Ref<SidebarSide>
  collapsible: Ref<SidebarCollapsible>
}>('Sidebar')

/** The screen edge a side lands on: start is the left in a left-to-right direction and the right in a right-to-left one. */
export function physicalSide(side: SidebarSide, dir: Direction): 'left' | 'right' {
  return (side === 'start') !== (dir === 'rtl') ? 'left' : 'right'
}

/** The values a desktop sidebar's gap and container take; uncollapsed, both take sidebar-size's width. */
export function sidebarLayout(side: SidebarSide, variant: SidebarVariant, collapsible: SidebarCollapsible, collapsed: boolean) {
  const padded = variant === 'floating' || variant === 'inset'
  const to = collapsed ? collapsible : undefined
  const icon = padded ? ('icon-padded' as const) : ('icon' as const)
  const offcanvas = to === 'offcanvas'
  return {
    collapsedTo: to,
    gap: offcanvas ? ('offcanvas' as const) : to === 'icon' ? icon : undefined,
    container: to === 'icon' ? icon : undefined,
    edge: padded ? ('padded' as const) : side,
    offset: side === 'start' ? (offcanvas ? 'inset-s-0 -translate-x-full rtl:translate-x-full' : 'inset-s-0') : offcanvas ? 'inset-e-0 translate-x-full rtl:-translate-x-full' : 'inset-e-0',
  }
}
```

## API of the items the example uses

### Button

[Full docs](https://ui.frontic.com/components/ui/button.md) · `npx @frontic/ui add button`

#### Button

Lets the user trigger an action, such as adding a product to the cart. Renders a `<button>`.

`data-slot="button"` · `Button.vue`

Styling: `button`, which extends [`action`](https://ui.frontic.com/DESIGN.md#action). Change it there and every part that extends it follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'button'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `variant` | `'solid' \| 'outline' \| 'soft' \| 'ghost' \| 'link'` | `'solid'` | The visual style: a filled, outlined, tinted, background-free or text-link button. |
| `color` | `'neutral' \| 'primary' \| 'destructive' \| 'buy' \| 'checkout' \| 'inverse'` | `'primary'` | The colour role, e.g. primary for the brand colour, buy for add-to-cart, checkout for the checkout step, destructive for removing. |
| `size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | `'md'` | The button's height and padding; the `icon-*` sizes make a square, icon-only button. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-color` | `'neutral' \| 'primary' \| 'destructive' \| 'buy' \| 'checkout' \| 'inverse'` | Carries the `color` prop. |
| `data-size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | Carries the `size` prop. |
| `data-variant` | `'solid' \| 'outline' \| 'soft' \| 'ghost' \| 'link'` | Carries the `variant` prop. |

### Filter

[Full docs](https://ui.frontic.com/components/ui/filter.md) · `npx @frontic/ui add filter`

#### Filter

Groups all parts of the filter and lets the user reset them together. Renders a `<div>`.

`data-slot="filter"` · `Filter.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'div'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `reset` | `(() => void)`: `(): void` |  | Called when the shopper presses a `FilterReset` inside; clear every active filter here. |

#### FilterField

Groups the title, control and description of one filter option. Renders a `<div>`.

`data-slot="filter-field"` · `FilterField.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'div'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `orientation` | `'vertical' \| 'horizontal'` |  | Whether the control sits below or beside its title, exposed as `data-orientation` for styling. |
| `disabled` | `boolean` | `false` | Marks the option as unavailable with `data-disabled`, e.g. when no products match it. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-disabled` |  | Present while the part is disabled. |
| `data-orientation` | `'vertical' \| 'horizontal'` | Carries the `orientation` prop. |

#### FilterFieldContent

Stacks a filter option's label and description beside its control. Renders a `<div>`.

`data-slot="filter-field-content"` · `FilterFieldContent.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'div'` | The element or component this component should render as. Can be overwritten by `asChild`. |

#### FilterFieldDescription

Explains a filter option below its label. Renders a `<p>`.

`data-slot="filter-field-description"` · `FilterFieldDescription.vue`

Styling: `filter-field-description`, which extends [`hint`](https://ui.frontic.com/DESIGN.md#hint). Change it there and every part that extends it follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'p'` | The element or component this component should render as. Can be overwritten by `asChild`. |

#### FilterFieldLabel

Labels the control of a filter option. Renders a `<label>`.

`data-slot="filter-field-label"` · `FilterFieldLabel.vue`

Styling: `filter-field-label`, which extends [`label`](https://ui.frontic.com/DESIGN.md#label). Change it there and every part that extends it follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'label'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `for` | `string` |  | The `id` of the control the label belongs to, so clicking the label toggles it. |

#### FilterFieldTitle

Names a group of filter options. Renders a `<div>`.

`data-slot="filter-field-title"` · `FilterFieldTitle.vue`

Styling: `filter-field-title`, which extends [`label`](https://ui.frontic.com/DESIGN.md#label). Change it there and every part that extends it follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'div'` | The element or component this component should render as. Can be overwritten by `asChild`. |

#### FilterGroup

Shows a labelled set of filter options that opens and closes.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `label` (required) | `string` |  | Shown on the trigger that opens the group, e.g. `Brand` or `Size`. |
| `activeCount` | `number` |  | How many options in the group are active; shown on the trigger when above zero. |
| `type` | `'collapsible' \| 'popover'` | `'popover'` | Opens the options in a floating popover, or expands them inline below the trigger as in a sidebar. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-size` |  | The size the part is drawn at. |

#### FilterList

Lists filter options with result counts that the user can toggle and search.

`data-slot="filter-list"` · `FilterList.vue`

Styling: `filter-list-check`, which extends [`choice`](https://ui.frontic.com/DESIGN.md#choice) and [`choice-box`](https://ui.frontic.com/DESIGN.md#choice-box); `filter-list-item`, which extends [`menu-item`](https://ui.frontic.com/DESIGN.md#menu-item); `filter-list-search`, which extends [`control`](https://ui.frontic.com/DESIGN.md#control) and [`control-line`](https://ui.frontic.com/DESIGN.md#control-line). Change them there and every part that extends them follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `options` (required) | `FilterOption[]` |  | The options to list, each with its label, value, result count and selected state. |
| `searchable` | `boolean` | `false` | Shows a search field above the list that narrows the options by label. |
| `disabled` | `boolean` | `false` | Disables every option in the list. |
| `searchPlaceholder` | `string` | `'Search...'` | Shown in the empty search field. |

| Event | Payload | Description |
| --- | --- | --- |
| `toggle` | `value: string` | Fires with an option's value each time the user selects or deselects it. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-disabled` |  | Present while the part is disabled. |
| `data-searchable` |  | Present when the list has a search field. |

#### FilterTrigger

Opens and closes a filter group, showing its label and count of active options.

`data-slot="filter-trigger"` · `FilterTrigger.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `label` (required) | `string` |  | Shown on the trigger, e.g. `Brand` or `Size`. |
| `activeCount` | `number` |  | How many options in the group are active; shown as a count when above zero. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-size` |  | The size the part is drawn at. |

#### FilterPrice

Lets the user limit products to a price range with a slider.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `label` | `string` |  | Shown as the filter's title, e.g. `Price`. |
| `description` | `string` |  | Shown as a hint below the title. |
| `ariaLabel` | `string` |  | Names the slider for screen readers when its title sits outside it, e.g. on an accordion trigger or a popover button. |
| `currency` | `string` | `'EUR'` | ISO 4217 code the amounts are shown in, e.g. `EUR`. |
| `precision` | `number` | `2` | Decimal places of the currency; amounts are in its minor unit, so `2` means cents. |
| `locale` | `string` |  | BCP 47 locale for formatting the amounts; the ConfigProvider's when left out. |
| `min` | `number` | `0` | Lowest price on the slider, in the currency's minor unit, e.g. cents. |
| `max` | `number` | `100000` | Highest price on the slider, in the currency's minor unit, e.g. cents. |
| `step` | `number` | `100` | Amount the slider moves per step, in the currency's minor unit, e.g. cents. |
| `value` | `{ from?: number \| undefined; to?: number \| undefined; }` |  | The selected price range in minor units; with only `to`, a single handle sets the highest price. |
| `disabled` | `boolean` | `false` | Greys out the slider so the shopper cannot change it. |
| `upTo` | `((value: string) => string)`: `(value: string): string` | ``Up to ${value}`` | Builds the text shown for a single-handle maximum from the formatted price. |

| Event | Payload | Description |
| --- | --- | --- |
| `change` | `value: { from?: number \| undefined; to?: number \| undefined; }` | Fires with the selected range in minor units as the user moves the slider; with a single handle, only `to`. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-disabled` |  | Present while the part is disabled. |
| `data-orientation` | `'horizontal' \| 'vertical'` | The direction the part is laid out in. |

#### FilterRange

Lets the user limit a value by typing a lowest and highest number.

`data-slot="filter-range"` · `FilterRange.vue`

Styling: `filter-range-separator`, which extends [`hint`](https://ui.frontic.com/DESIGN.md#hint). Change it there and every part that extends it follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `label` | `string` |  | Shown as the filter's title, e.g. `Width`. |
| `description` | `string` |  | Shown as a hint below the title. |
| `unit` | `string` |  | Shown inside both inputs, e.g. `cm` or `€`. |
| `unitPosition` | `'start' \| 'end'` | `'start'` | Whether the unit sits before or after the number in each input. |
| `min` | `number` |  | Lowest number the inputs accept. |
| `max` | `number` |  | Highest number the inputs accept. |
| `step` | `number` |  | Amount the input arrows change the number by. |
| `value` | `{ from?: number \| undefined; to?: number \| undefined; }` |  | The current lowest and highest values; either may be left open. |
| `disabled` | `boolean` | `false` | Greys out both inputs so the shopper cannot change them. |
| `placeholderFrom` | `string` | `'From'` | Shown in the empty lowest-value input. |
| `placeholderTo` | `string` | `'To'` | Shown in the empty highest-value input. |
| `separator` | `string` | `'–'` | Shown between the two inputs. |

| Event | Payload | Description |
| --- | --- | --- |
| `change` | `value: { from?: number \| undefined; to?: number \| undefined; }` | Fires with the lowest and highest numbers when the user changes either input, leaving out an empty one. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-disabled` |  | Present while the part is disabled. |
| `data-orientation` | `'horizontal' \| 'vertical'` | The direction the part is laid out in. |

#### FilterRating

Lets the user limit products to those with a minimum star rating.

`data-slot="filter-rating"` · `FilterRating.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `value` | `number` | `0` | The selected minimum number of stars; `0` for no rating filter. |
| `label` | `string` |  | Shown as the filter's title, e.g. `Rating`. |
| `max` | `number` | `5` | How many stars to show. |
| `disabled` | `boolean` | `false` | Greys out the stars so the shopper cannot pick one. |
| `starLabel` | `((stars: number) => string)`: `(stars: number): string` | ``${stars} ${stars === 1 ? 'star' : 'stars'} and up`` | Builds each star's screen-reader label from its star count. |
| `suffix` | `string` | `'& up'` | Shown after the stars once a rating is selected. |

| Event | Payload | Description |
| --- | --- | --- |
| `change` | `value: number` | Fires with the minimum star count the user picks, or `0` when they clear it. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-disabled` |  | Present while the part is disabled. |
| `data-orientation` | `'horizontal' \| 'vertical'` | The direction the part is laid out in. |

#### FilterReset

Lets the user clear all filters at once. Renders a `<div>`.

`data-slot="filter-reset"` · `FilterReset.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `label` | `string` | `'Reset'` | Shown on the reset button; the default slot replaces it. |

| Event | Payload | Description |
| --- | --- | --- |
| `reset` |  | Fires when the user clicks the reset button, after the surrounding filter's own reset runs. |

#### FilterSlider

Lets the user limit a numeric value to a range with a slider.

`data-slot="filter-slider"` · `FilterSlider.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `label` | `string` |  | Shown as the filter's title. |
| `description` | `string` |  | Shown as a hint below the title. |
| `ariaLabel` | `string` |  | Names the slider for screen readers when its title sits outside it, e.g. on an accordion trigger or a popover button. |
| `min` | `number` | `0` | Lowest value on the slider. |
| `max` | `number` | `1000` | Highest value on the slider. |
| `step` | `number` | `1` | Amount the slider moves per step. |
| `value` | `{ from?: number \| undefined; to?: number \| undefined; }` |  | The selected range; with only `to`, a single handle sets the highest value. |
| `disabled` | `boolean` | `false` | Greys out the slider so the shopper cannot change it. |
| `formatValue` | `((value: number) => string)`: `(value: number): string` |  | Formats each value in the readout next to the title, e.g. adding a unit; the plain number when left out. |
| `upTo` | `((value: string) => string)`: `(value: string): string` | ``Up to ${value}`` | Builds the text shown for a single-handle maximum from the formatted value. |

| Event | Payload | Description |
| --- | --- | --- |
| `change` | `value: { from?: number \| undefined; to?: number \| undefined; }` | Fires with the selected range as the user moves the slider; with a single handle, only `to`. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-disabled` |  | Present while the part is disabled. |
| `data-orientation` | `'horizontal' \| 'vertical'` | The direction the part is laid out in. |

#### FilterSwatch

Lists colour options as swatches that the user can toggle.

`data-slot="filter-swatch"` · `FilterSwatch.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `options` (required) | `FilterSwatchOption[]` |  | The colours to list, each with its hex, label, result count and selected state. |
| `type` | `'single' \| 'multiple'` | `'multiple'` | Whether the shopper can select one colour or several. |
| `disabled` | `boolean` | `false` | Disables every swatch. |
| `shape` | `SwatchShape`: `'default' \| 'pill'` | `'pill'` | The swatch corners: rounded or fully round. |
| `size` | `SwatchSize`: `'md' \| 'xs' \| 'sm' \| 'lg'` | `'xs'` | The swatch diameter. |
| `orientation` | `SwatchGroupOrientation`: `'vertical' \| 'horizontal'` | `'vertical'` | A scrolling column of swatches with their labels, or a row of swatches. |
| `labelClass` | `ClassValue`: `null \| string \| false \| Record<string, any> \| ClassValue[]` |  | Classes for each swatch's label; unused for options that carry a count. |

| Event | Payload | Description |
| --- | --- | --- |
| `toggle` | `value: string` | Fires with a colour's value each time the user selects or deselects it. |

#### FilterSwitch

Lets the user turn a yes-or-no filter on or off.

`data-slot="filter-switch"` · `FilterSwitch.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `value` | `boolean` | `false` | Whether the filter is on. |
| `label` (required) | `string` |  | Shown next to the switch, e.g. `In stock only`. |
| `description` | `string` |  | Shown as a hint below the label. |
| `disabled` | `boolean` | `false` | Greys out the switch so the shopper cannot change it. |

| Event | Payload | Description |
| --- | --- | --- |
| `change` | `value: boolean` | Fires with the new on or off state when the user flips the switch. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-disabled` |  | Present while the part is disabled. |

### Price

[Full docs](https://ui.frontic.com/components/ui/price.md) · `npx @frontic/ui add price`

#### Price

Shows a price, with the original price and discount percentage when reduced. Renders a `<span>`.

`data-slot="price"` · `Price.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `size` | `PriceSize`: `'md' \| 'sm' \| 'lg'` | `'md'` | The text size of the price. |
| `align` | `PriceAlign`: `'start' \| 'end'` | `'start'` | Which side the badge, the prices and the hint line up on. |
| `price` (required) | `PriceType` |  | The price to show: `amount` and `ref` in minor units such as cents, `currency` as an ISO 4217 code. |
| `locale` | `string` |  | The locale that formats the price, e.g. `de-DE`; the ConfigProvider's when left out. |
| `showRef` | `boolean` | `true` | Shows the reference price struck through when it is higher than the amount. |
| `showDiscount` | `boolean` | `true` | Shows the saving as a percentage next to a discounted price. |
| `showHint` | `boolean` | `true` | Shows the hint below the price when the hint slot holds one. |
| `refLabel` | `string` | `'Was'` | Read out before the original price of a reduced product; hidden on screen. |
| `amountLabel` | `string` | `'Now'` | Read out before the current price of a reduced product; hidden on screen. |

| Slot | Slot props | Description |
| --- | --- | --- |
| `hint` |  | A note below the price, such as tax and shipping. |

#### PriceAmount

Shows the current price of the product. Renders a `<span>`.

`data-slot="price-amount"` · `PriceAmount.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

#### PriceRef

Shows the original price of a discounted product. Renders a `<span>`.

`data-slot="price-ref"` · `PriceRef.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

#### PriceDiscount

Shows the percentage saved against the original price. Renders a `<span>`.

`data-slot="price-discount"` · `PriceDiscount.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Slot | Slot props | Description |
| --- | --- | --- |
| `default` | `{ percent: number; }` | Replaces the discount badge; gets the percentage saved. |

#### PriceHint

Holds a note below the price, such as tax and shipping, which may link on. Renders a `<span>`.

`data-slot="price-hint"` · `PriceHint.vue`

Styling: `price-hint`, which extends [`link-foreground`](https://ui.frontic.com/DESIGN.md#link-foreground). Change it there and every part that extends it follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'span'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

### Tag Group

[Full docs](https://ui.frontic.com/components/ui/tag-group.md) · `npx @frontic/ui add tag-group`

#### TagGroup

Groups tags the user moves through with the arrow keys, and can select or remove. Renders a `<div>`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `modelValue` | `AcceptableValue \| AcceptableValue[]`: `null \| string \| number \| bigint \| Record<string, any> \| AcceptableValue[]` |  | The controlled value of the selected tag(s). Can be binded with `v-model`. An array when `selectionMode` is `multiple`. |
| `defaultValue` | `AcceptableValue \| AcceptableValue[]`: `null \| string \| number \| bigint \| Record<string, any> \| AcceptableValue[]` |  | The value of the selected tag(s) when initially rendered. Use when you do not need to control the selection. |
| `selectionMode` | `TagGroupSelectionMode`: `'single' \| 'multiple' \| 'none'` | `'none'` | The type of selection that is allowed. Tags are not selectable when `none`. |
| `disallowEmptySelection` | `boolean` | `false` | When `true`, the user cannot deselect the last selected tag. |
| `escapeKeyBehavior` | `'none' \| 'clearSelection'` | `'clearSelection'` | Whether pressing <kbd>Escape</kbd> clears the selection. |
| `disabled` | `boolean` | `false` | When `true`, prevents the user from interacting with the tag group and all its tags. |
| `dir` | `Direction`: `'ltr' \| 'rtl'` |  | The reading direction of the tag group when applicable.<br><br>If omitted, inherits globally from `ConfigProvider` or assumes LTR (left-to-right) reading mode. |
| `loop` | `boolean` | `true` | When `true`, keyboard navigation will loop from last tag to first, and vice versa. |
| `by` | `string \| ((a: AcceptableValue, b: AcceptableValue) => boolean)`: `string \| (a: AcceptableValue, b: AcceptableValue): boolean` |  | Use this to compare objects by a particular field, or pass your own comparison function for complete control over how objects are compared. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'div'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Event | Payload | Description |
| --- | --- | --- |
| `update:modelValue` | `value: AcceptableValue \| AcceptableValue[] \| undefined` | Event handler called when the selection changes. |
| `remove` | `values: AcceptableValue[]` | Event handler called when the user removes tags, with the values to remove. Tags are only removable when this event has a listener; remove the values from your own list to remove the tags. |

| Slot | Slot props | Description |
| --- | --- | --- |
| `default` | `{ modelValue: AcceptableValue \| AcceptableValue[] \| undefined; }` | Holds the tags; gets the selected value as `modelValue`. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-disabled` |  | Present while the part is disabled. |
| `data-empty` |  | Present while no option matches the search. |

#### TagGroupItem

One tag: its text and, when removable, a delete button. Renders a `<div>`.

Styling: `tag-group-item`, which extends [`tag`](https://ui.frontic.com/DESIGN.md#tag). Change it there and every part that extends it follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` (required) | `AcceptableValue`: `null \| string \| number \| bigint \| Record<string, any>` |  | The unique value of the tag. |
| `disabled` | `boolean` | `false` | When `true`, prevents the user from interacting with the tag. |
| `textValue` | `string` |  | A string representation of the tag's contents, used for typeahead and as the tag's accessible name. Defaults to the text of `TagGroupItemText`. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'div'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Slot | Slot props | Description |
| --- | --- | --- |
| `default` | `{ selected: boolean; disabled: boolean; }` | Holds the text and delete button; gets whether the tag is `selected` and `disabled`. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-disabled` |  | Present while the part is disabled. |
| `data-state` | `'checked' \| 'unchecked'` | The state the part is in, one value per state. |

#### TagGroupItemDelete

Lets the user remove its tag; the group emits `remove`. Renders a `<button>`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `disabled` | `boolean` | `false` | When `true`, prevents the user from interacting with the delete button. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'button'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-disabled` |  | Present while the part is disabled. |

#### TagGroupItemText

Shows the text of one tag. Renders a `<span>`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'span'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
