---
title: CommandPalette
description: Searchable command list with groups, keyboard navigation, and customizable rendering.
sidebar:
  order: 5
search:
  tags: [command menu, search, keyboard, actions]
---

# CommandPalette

> Searchable command list with groups, keyboard navigation, and customizable rendering.

## Import

```tsx
import { CommandPalette, Dialog } from 'moraine'
```

## Slot Structure

CommandPalette renders a searchable grouped list with keyboard navigation, active-descendant semantics, and optional footer content. Compose it with `Dialog` when it should appear in an overlay; the caller owns the trigger, open state, positioning, and closing behavior.

### Input area

```text
root
└── inputWrapper
    ├── search (IconButton, optional)
    ├── input
    └── close (IconButton, optional)
```

### Results

```text
root
├── listbox
│   ├── item (×n)
│   │   ├── itemLeading (optional)
│   │   ├── itemWrapper
│   │   │   ├── itemLabel
│   │   │   └── itemDescription (optional, bottom or trailing inside itemLabel)
│   │   └── itemTrailing (optional custom metadata)
│   └── group (×n, optional)
│       └── label (optional)
├── empty (optional, no matches)
└── footer (optional)
```

## Examples

### Basic

Compose `Dialog` around CommandPalette and put the trigger in Dialog's children. Handle the selected item with CommandPalette's `onSelect` callback; enabled selections request closing by default through `onClose`.

```tsx
function Usage() {
  const [open, setOpen] = createSignal(false)

  return (
    <Dialog
      open={open()}
      onOpenChange={setOpen}
      close={false}
      classes={{ body: 'p-0 mb-0' }}
      body={
        <CommandPalette
          groups={GROUPS}
          showClose
          onClose={() => setOpen(false)}
          footerRender={() => (
            <div class="flex gap-4 items-center justify-between">
              <div class="flex flex-wrap gap-3 items-center">
                <div class="flex gap-2 items-center">
                  <KbdGroup items={['↑', '↓']} />
                  <span class="text-xs">Navigate</span>
                </div>
                <div class="flex gap-2 items-center">
                  <Kbd value="↵" />
                  <span class="text-xs">Open</span>
                </div>
              </div>
              <div class="flex gap-2 items-center">
                <Kbd value="Esc" />
                <span class="text-xs">Close</span>
              </div>
            </div>
          )}
        />
      }
    >
      <Button variant="outline" trailing={<KbdGroup items={['⌘', 'K']} />}>
        Search...
      </Button>
    </Dialog>
  )
}
```

### Real-World Example

Drive the palette from the same shortcut map you use across the app, and render those bindings with `item.trailingRender` plus `Kbd` so the UI and keyboard behavior stay aligned.

```tsx
function RealWorldExample() {
  const [open, setOpen] = createSignal(false)
  const [isMac, setIsMac] = createSignal(true)
  const [lastAction, setLastAction] = createSignal(
    'Ready. Open the palette from the trigger below.',
  )

  const modifierLabel = createMemo(() => (isMac() ? '⌘' : 'Ctrl'))

  const groups = createMemo<CommandPaletteT.Group<AppCommand>[]>(() => [
    {
      id: 'jump-to',
      label: 'Jump to',
      items: [
        {
          value: 'open-project-switcher',
          label: 'Open project switcher',
          description: 'Jump between projects, teams, and recent workspaces.',
          leadingRender: () => <Icon name="i-lucide-search-check" />,
          trailingRender: () => (
            <KbdGroup
              items={[modifierLabel(), 'Shift', 'P']}
              size="sm"
              class="text-muted-foreground"
            />
          ),
          binding: { key: 'p', shiftKey: true },
          result: 'Opened the project switcher.',
        },
        {
          value: 'go-issues',
          label: 'Go to issues',
          description: 'Open the active team issue board.',
          leadingRender: () => <Icon name="i-lucide-circle-dot" />,
          trailingRender: () => (
            <KbdGroup items={[modifierLabel(), 'I']} size="sm" class="text-muted-foreground" />
          ),
          binding: { key: 'i' },
          result: 'Navigated to the issue board.',
        },
      ],
    },
    {
      id: 'workspace',
      label: 'Workspace',
      items: [
        {
          value: 'new-issue',
          label: 'Create issue',
          description: 'Capture a bug or task without leaving the current page.',
          leadingRender: () => <Icon name="i-lucide-file-plus-2" />,
          trailingRender: () => (
            <KbdGroup items={[modifierLabel(), 'N']} size="sm" class="text-muted-foreground" />
          ),
          binding: { key: 'n' },
          result: 'Created a new issue draft.',
        },
        {
          value: 'toggle-sidebar',
          label: 'Toggle sidebar',
          description: 'Collapse navigation to focus on the current editor.',
          leadingRender: () => <Icon name="i-lucide-panel-left-close" />,
          trailingRender: () => (
            <KbdGroup items={[modifierLabel(), 'B']} size="sm" class="text-muted-foreground" />
          ),
          binding: { key: 'b' },
          result: 'Toggled the workspace sidebar.',
        },
        {
          value: 'open-billing',
          label: 'Open billing',
          description: 'Restricted to workspace owners.',
          leadingRender: () => <Icon name="i-lucide-credit-card" />,
          disabled: true,
          trailingRender: () => <span class="text-xs text-muted-foreground">Owner only</span>,
        },
      ],
    },
  ])

  const commands = createMemo(() => groups().flatMap((group) => group.items ?? []))

  const onSelect = (item: AppCommand) => {
    setLastAction(item.result ?? `Selected ${item.label ?? item.value}.`)
    setOpen(false)
  }

  onMount(() => {
    setIsMac(/Mac|iPhone|iPad/.test(window.navigator.platform))

    const handler = (event: KeyboardEvent) => {
      if (isTypingTarget(event.target)) {
        return
      }

      const matchedCommand = commands().find(
        (item) => item.binding && !item.disabled && matchesBinding(event, item.binding),
      )

      if (!matchedCommand) {
        return
      }

      event.preventDefault()
      onSelect(matchedCommand)
    }

    window.addEventListener('keydown', handler)
    onCleanup(() => window.removeEventListener('keydown', handler))
  })

  return (
    <div class="flex flex-col gap-3 max-w-full w-xl">
      <div class="p-3 border border-border rounded-lg bg-muted/20">
        <p class="text-sm font-medium">Global shortcuts and palette commands stay in sync.</p>
        <p class="text-sm text-muted-foreground mt-1">{lastAction()}</p>
      </div>

      <Dialog
        open={open()}
        onOpenChange={setOpen}
        close={false}
        classes={{ body: 'p-0 mb-0' }}
        body={
          <CommandPalette<AppCommand>
            groups={groups()}
            showClose
            onSelect={onSelect}
            onClose={() => setOpen(false)}
            footerRender={() => (
              <div class="flex flex-wrap gap-3 items-center justify-between">
                <div class="flex flex-wrap gap-3 items-center">
                  <span class="flex gap-2 items-center">
                    <KbdGroup items={['↑', '↓']} size="sm" />
                    <span class="text-xs">Navigate</span>
                  </span>
                  <span class="flex gap-2 items-center">
                    <Kbd value="↵" size="sm" />
                    <span class="text-xs">Run command</span>
                  </span>
                </div>
              </div>
            )}
          />
        }
      >
        <Button variant="outline">Search projects, issues, and actions</Button>
      </Dialog>
    </div>
  )
}
```

### Custom Item Render

Use `itemRender` when the row layout needs richer metadata than the default label, description, leadingRender, and trailingRender item render hooks.

```tsx
function CustomItemRender() {
  const [open, setOpen] = createSignal(false)

  return (
    <div class="max-w-full w-lg">
      <Dialog
        open={open()}
        onOpenChange={setOpen}
        close={false}
        classes={{ body: 'p-0 mb-0' }}
        body={
          <CommandPalette<TeamCommand>
            groups={GROUPS}
            onClose={() => setOpen(false)}
            itemRender={(ctx) => (
              <div class="flex flex-1 gap-3 min-w-0 items-center">
                <Icon name="i-lucide-folder-kanban text-muted-foreground shrink-0" />
                <span class="flex flex-1 flex-col min-w-0">
                  <span class="text-sm font-medium truncate">{ctx.item.label}</span>
                  <span class="text-xs text-muted-foreground truncate">
                    {ctx.item.owner} · {ctx.item.description}
                  </span>
                </span>
                <Badge variant={ctx.item.status === 'ready' ? 'default' : 'outline'}>
                  {ctx.item.status}
                </Badge>
              </div>
            )}
          />
        }
      >
        <Button variant="outline">Open project switcher</Button>
      </Dialog>
    </div>
  )
}
```

### Description Position

Set `descriptionPosition="trailing"` to keep descriptions inline near the label while reserving `item.trailingRender` for badges, shortcuts, or other metadata.

```tsx
function DescriptionPosition() {
  const [open, setOpen] = createSignal(false)

  return (
    <div class="max-w-full w-lg">
      <Dialog
        open={open()}
        onOpenChange={setOpen}
        close={false}
        classes={{ body: 'p-0 mb-0' }}
        body={
          <CommandPalette
            groups={GROUPS}
            descriptionPosition="trailing"
            onClose={() => setOpen(false)}
          />
        }
      >
        <Button variant="outline">Open navigation</Button>
      </Dialog>
    </div>
  )
}
```

### Custom Empty State

Override the default 'No results.' message.

```tsx
function CustomEmptyState() {
  const [open, setOpen] = createSignal(false)

  return (
    <div class="max-w-full w-lg">
      <Dialog
        open={open()}
        onOpenChange={setOpen}
        close={false}
        classes={{ body: 'p-0 mb-0' }}
        body={
          <CommandPalette
            groups={[]}
            emptyRender={() => (
              <span class="flex flex-col gap-2 items-center">
                <Icon name="i-lucide-search-x" class="text-muted-foreground size-6" />
                <span class="text-foreground font-medium">No commands found</span>
                <span class="text-xs">Try a different keyword or clear the search.</span>
              </span>
            )}
          />
        }
      >
        <Button variant="outline">Open palette</Button>
      </Dialog>
    </div>
  )
}
```

### Loading

Search icon becomes a spinner while loading.

```tsx
function Loading() {
  const [open, setOpen] = createSignal(false)
  const BASIC_GROUPS: CommandPaletteT.Group[] = [
    {
      id: 'workspace',
      label: 'Workspace',
      items: [
        {
          value: 'new-issue',
          label: 'New Issue',
          leadingRender: () => <Icon name="i-lucide-circle-plus" />,
          trailingRender: () => <KbdGroup items={['⌘', 'N']} />,
        },
        {
          value: 'open-inbox',
          label: 'Open Inbox',
          leadingRender: () => <Icon name="i-lucide-inbox" />,
          trailingRender: () => <KbdGroup items={['⌘', 'I']} />,
        },
        {
          value: 'sync-roadmap',
          label: 'Sync Roadmap',
          leadingRender: () => <Icon name="i-lucide-refresh-cw" />,
          description: 'Pull the latest planning updates',
        },
      ],
    },
  ]

  return (
    <div class="max-w-full w-lg">
      <Dialog
        open={open()}
        onOpenChange={setOpen}
        close={false}
        classes={{ body: 'p-0 mb-0' }}
        body={<CommandPalette groups={BASIC_GROUPS} loading onClose={() => setOpen(false)} />}
      >
        <Button variant="outline">Open palette</Button>
      </Dialog>
    </div>
  )
}
```

### With Close Button

A close button in the input trailing slot.

```tsx
function WithCloseButton() {
  const [open, setOpen] = createSignal(false)
  const BASIC_GROUPS: CommandPaletteT.Group[] = [
    {
      id: 'workspace',
      label: 'Workspace',
      items: [
        {
          value: 'new-issue',
          label: 'New Issue',
          leadingRender: () => <Icon name="i-lucide-circle-plus" />,
          trailingRender: () => <span class="text-xs text-muted-foreground">⌘N</span>,
        },
        {
          value: 'open-inbox',
          label: 'Open Inbox',
          leadingRender: () => <Icon name="i-lucide-inbox" />,
          trailingRender: () => <span class="text-xs text-muted-foreground">GI</span>,
        },
        {
          value: 'sync-roadmap',
          label: 'Sync Roadmap',
          leadingRender: () => <Icon name="i-lucide-refresh-cw" />,
          description: 'Pull the latest planning updates',
        },
      ],
    },
    {
      id: 'navigation',
      label: 'Navigation',
      items: [
        {
          value: 'go-dashboard',
          label: 'Dashboard',
          leadingRender: () => <Icon name="i-lucide-layout-dashboard" />,
        },
        {
          value: 'go-projects',
          label: 'Projects',
          leadingRender: () => <Icon name="i-lucide-folder-kanban" />,
        },
        {
          value: 'go-settings',
          label: 'Settings',
          leadingRender: () => <Icon name="i-lucide-settings" />,
          description: 'Preferences',
        },
        {
          value: 'go-billing',
          label: 'Billing',
          leadingRender: () => <Icon name="i-lucide-credit-card" />,
          disabled: true,
        },
      ],
    },
  ]

  return (
    <div class="max-w-full w-lg">
      <Dialog
        open={open()}
        onOpenChange={setOpen}
        close={false}
        classes={{ body: 'p-0 mb-0' }}
        body={<CommandPalette groups={BASIC_GROUPS} showClose onClose={() => setOpen(false)} />}
      >
        <Button variant="outline">Open palette</Button>
      </Dialog>
    </div>
  )
}
```

### Sub-Navigation

Compose multi-step flows outside the component by swapping the `groups` prop in a wrapper and set `closeOnSelect={false}` while the palette remains open between steps.

```tsx
function SubNavigation() {
  const ROOT_GROUPS: CommandPaletteT.Group[] = [
    {
      id: 'main',
      label: 'Commands',
      items: [
        {
          value: 'create',
          label: 'Create',
          leadingRender: () => <Icon name="i-lucide-plus-circle" />,
          description: 'Create new resources',
        },
        {
          value: 'share',
          label: 'Share',
          leadingRender: () => <Icon name="i-lucide-share-2" />,
          description: 'Share with others',
        },
        {
          value: 'delete',
          label: 'Delete',
          leadingRender: () => <Icon name="i-lucide-trash-2" />,
        },
      ],
    },
  ]
  const CREATE_GROUPS: CommandPaletteT.Group[] = [
    {
      id: 'create',
      label: 'Create',
      items: [
        {
          value: 'create-new-file',
          label: 'New File',
          leadingRender: () => <Icon name="i-lucide-file-plus" />,
        },
        {
          value: 'create-new-folder',
          label: 'New Folder',
          leadingRender: () => <Icon name="i-lucide-folder-plus" />,
        },
        {
          value: 'create-new-project',
          label: 'New Project',
          leadingRender: () => <Icon name="i-lucide-git-branch" />,
        },
      ],
    },
  ]
  const SHARE_GROUPS: CommandPaletteT.Group[] = [
    {
      id: 'share',
      label: 'Share',
      items: [
        {
          value: 'share-copy-link',
          label: 'Copy Link',
          leadingRender: () => <Icon name="i-lucide-link" />,
          trailingRender: () => <span class="text-xs text-muted-foreground">⌘L</span>,
        },
        {
          value: 'share-send-email',
          label: 'Send via Email',
          leadingRender: () => <Icon name="i-lucide-mail" />,
        },
      ],
    },
  ]
  const [open, setOpen] = createSignal(false)
  const [view, setView] = createSignal<'root' | 'create' | 'share'>('root')

  const groups = createMemo(() => {
    switch (view()) {
      case 'create':
        return CREATE_GROUPS
      case 'share':
        return SHARE_GROUPS
      default:
        return ROOT_GROUPS
    }
  })

  const onSelect = (item: CommandPaletteT.Item) => {
    if (item.value === 'create') {
      setView('create')
    } else if (item.value === 'share') {
      setView('share')
    }
  }

  return (
    <div class="flex flex-col gap-3 max-w-full w-lg">
      <div class="flex gap-3 items-center justify-between">
        <p class="text-sm text-muted-foreground">
          Drive multi-step navigation outside the component by swapping the `groups` prop.
        </p>
        <Button
          size="sm"
          variant="outline"
          disabled={view() === 'root'}
          onClick={() => setView('root')}
        >
          Back
        </Button>
      </div>
      <Dialog
        open={open()}
        onOpenChange={setOpen}
        close={false}
        classes={{ body: 'p-0 mb-0' }}
        body={<CommandPalette groups={groups()} closeOnSelect={false} onSelect={onSelect} />}
      >
        <Button variant="outline">Open palette</Button>
      </Dialog>
    </div>
  )
}
```

## Sizes and Virtual Rendering

Use `size="xs" | "sm" | "md" | "lg" | "xl"` to size command rows. For large collections, pass `virtualRender` and `scrollToItem`; CommandPalette exposes type-safe flattened entries and its mounted scroll element, while `scrollToItem` receives the source command plus its flattened entry index.

## API Reference

### Attributes

#### `root`

Command palette container that owns search and option list.

#### `inputWrapper`

Search row that groups input, search icon, and dismiss controls.

#### `input`

Search input used to filter commands.

##### ARIA Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| aria-activedescendant | boolean \| string \| undefined | Accessibility attribute forwarded by the rendered component. |
| aria-autocomplete | boolean \| string \| undefined | Accessibility attribute forwarded by the rendered component. |
| aria-controls | boolean \| string \| undefined | References the controlled element while the related content is mounted. |
| aria-expanded | boolean \| string \| undefined | Indicates whether the controlled content is expanded. |
| aria-haspopup | boolean \| string \| undefined | Accessibility attribute forwarded by the rendered component. |
| role | string | Defines the semantic role exposed to assistive technology. |

#### `listbox`

Scrollable command list that owns option and active-descendant semantics.

##### ARIA Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| role | string | Defines the semantic role exposed to assistive technology. |

#### `footer`

Bottom region for keyboard hints or custom footer content.

#### `group`

Section wrapper for a group of command items.

##### ARIA Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| role | string | Defines the semantic role exposed to assistive technology. |

#### `label`

Group heading text.

#### `item`

Command row that can be highlighted, selected, or disabled.

##### Data Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| data-disabled | string \| undefined | Present when the component or item is disabled. |
| data-highlighted | string \| undefined | Present when the item is highlighted by pointer or keyboard navigation. |

##### ARIA Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| aria-disabled | boolean \| string \| undefined | Indicates that the control is disabled. |
| aria-posinset | boolean \| string \| undefined | Accessibility attribute forwarded by the rendered component. |
| aria-selected | boolean \| string \| undefined | Indicates the currently selected option or tab. |
| aria-setsize | boolean \| string \| undefined | Accessibility attribute forwarded by the rendered component. |
| role | string | Defines the semantic role exposed to assistive technology. |

#### `itemLeading`

Leading region for a command row.

#### `itemWrapper`

Text column that groups command label and description.

#### `itemLabel`

Primary text for a command item.

#### `itemDescription`

Supporting text for a command item.

#### `itemTrailing`

Trailing region for shortcuts or custom item metadata.

#### `search`

Search icon or loading indicator displayed in the input row.

##### Data Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| data-loading | string \| undefined | Present when the component is loading. |

##### ARIA Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| aria-busy | boolean \| string \| undefined | Accessibility attribute forwarded by the rendered component. |

#### `close`

Button that dismisses the command palette.

##### ARIA Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| aria-label | boolean \| string \| undefined | Provides an accessible label when visible text is not sufficient. |

#### `empty`

Message shown when no command items match the search.

### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| autofocus | boolean \| undefined | true | Whether to focus the search input automatically on mount. |
| class | ClassValue | — | Class applied to the component root or trigger element. |
| classes | CommandPaletteT.Classes \| undefined | — | — |
| closeIcon | IconT.Name | icon-close | Icon name for the palette close button. |
| closeOnSelect | boolean \| undefined | true | Whether to request closing the palette after an enabled item is selected. |
| descriptionPosition | CommandPaletteT.DescriptionPosition \| undefined | bottom | Where descriptions render by default. |
| disableFilter | boolean \| undefined | false | Disable built-in search filtering and render all provided items. |
| emptyRender | ComponentOrElement<CommandPaletteT.EmptyRenderProps<TItem>> \| undefined | — | Custom empty state renderer. |
| filterItems | ((args: { groups: CommandPaletteT.Group<TItem>[]; searchTerm: string; }) => CommandPaletteT.Group<TItem>[]) \| undefined | — | Custom filter function that fully controls which groups and items are visible. |
| footerRender | ComponentOrElement<CommandPaletteT.FooterRenderProps<TItem>> \| undefined | — | Custom footer renderer. |
| getItemSearchText | ((item: TItem, group: CommandPaletteT.Group<TItem>) => string) \| undefined | — | Custom search text builder for built-in filtering. |
| groups | CommandPaletteT.Group<TItem>[] \| undefined | [] | Command groups to display initially. |
| inputProps | JSX.HTMLAttributes<HTMLInputElement> \| undefined | — | Additional props of input |
| itemProps | ((context: CommandPaletteT.ItemRenderProps<TItem>) => ElementProps<HTMLDivElement> \| undefined) \| undefined | — | Additional attributes for a command row. |
| itemRender | ComponentOrElement<CommandPaletteT.ItemRenderProps<TItem>> \| undefined | — | Custom command row content renderer. |
| leadingIcon | IconT.Name | icon-search | Icon name of input's leading icon. |
| listboxProps | ElementProps<HTMLDivElement> \| undefined | — | Additional attributes for the command listbox. |
| loading | boolean \| undefined | false | Whether the palette is in a loading state. |
| loadingIcon | IconT.Name | icon-loading | Icon name of input's leading icon for the loading state. |
| onClose | (() => void) \| undefined | — | Callback triggered when the close button is clicked or selection requests closing. |
| onSearchTermChange | ((term: string) => void) \| undefined | — | Callback triggered when the search term changes. |
| onSelect | ((item: TItem) => void) \| undefined | — | Callback triggered when an enabled item is selected. |
| placeholder | string \| undefined | Search... | Placeholder text for the search input. |
| ref | JSX.HTMLElementTags["div"] extends { ref?: infer Ref; } ? Ref : never \| undefined | — | — |
| scrollToItem | ((item: TItem, entryIndex: number) => void) \| undefined | — | Scrolls a highlighted command into view using its flattened entry index. |
| searchMaxLength | number \| undefined | — | Maximum allowed length for the search text. |
| searchTerm | string \| undefined | — | Controlled search term. |
| showClose | boolean \| undefined | false | Whether to show a close button in the header. |
| size | "xs" \| "sm" \| "md" \| "lg" \| "xl" \| undefined | — | — |
| style | JSX.CSSProperties \| undefined | — | — |
| styles | CommandPaletteT.Styles \| undefined | — | — |
| virtualRender | Component<CommandPaletteT.VirtualRenderProps<TItem>> \| undefined | — | Renders flattened group labels and commands through a virtualization layer. |

### Items

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| alwaysShow | boolean \| undefined | — | Whether this item should be excluded from built-in search filtering. |
| description | string \| undefined | — | Secondary description text shown for the item. |
| descriptionPosition | CommandPaletteT.DescriptionPosition \| undefined | — | Where the item description is rendered. Overrides the root setting. |
| disabled | boolean \| undefined | — | Whether the item is disabled and cannot be selected. |
| keywords | string[] \| undefined | — | Additional keywords included in built-in search matching. |
| label | string \| undefined | — | Primary label for the item. |
| leadingRender | ComponentOrElement<CommandPaletteT.ItemRenderProps> \| undefined | — | Custom visual rendered at the start of the item. |
| onSelect | (() => void) \| undefined | — | Callback triggered when the item is selected. |
| trailingRender | ComponentOrElement<CommandPaletteT.ItemRenderProps> \| undefined | — | Custom visual rendered at the end of the item. |
| value* | string | — | Unique value for the item. |

### ARIA

Accessibility attributes and roles emitted by the component markup.

| Attribute | Type | Description |
| --- | --- | --- |
| aria-activedescendant | boolean \| string \| undefined | Accessibility attribute forwarded by the rendered component. |
| aria-autocomplete | boolean \| string \| undefined | Accessibility attribute forwarded by the rendered component. |
| aria-busy | boolean \| string \| undefined | Accessibility attribute forwarded by the rendered component. |
| aria-controls | boolean \| string \| undefined | References the controlled element while the related content is mounted. |
| aria-disabled | boolean \| string \| undefined | Indicates that the control is disabled. |
| aria-expanded | boolean \| string \| undefined | Indicates whether the controlled content is expanded. |
| aria-haspopup | boolean \| string \| undefined | Accessibility attribute forwarded by the rendered component. |
| aria-hidden | boolean \| string \| undefined | Hides decorative content from assistive technology. |
| aria-label | boolean \| string \| undefined | Provides an accessible label when visible text is not sufficient. |
| aria-posinset | boolean \| string \| undefined | Accessibility attribute forwarded by the rendered component. |
| aria-selected | boolean \| string \| undefined | Indicates the currently selected option or tab. |
| aria-setsize | boolean \| string \| undefined | Accessibility attribute forwarded by the rendered component. |
| role | string | Defines the semantic role exposed to assistive technology. |

### Data Attributes

State and slot attributes exposed for styling hooks and selectors.

| Attribute | Type | Description |
| --- | --- | --- |
| data-disabled | string \| undefined | Present when the component or item is disabled. |
| data-highlighted | string \| undefined | Present when the item is highlighted by pointer or keyboard navigation. |
| data-loading | string \| undefined | Present when the component is loading. |
| data-slot | string | Identifies the rendered slot for styling hooks and selectors. |
