<script setup>
import { Toolbar } from "@dicehub/kappa/components/toolbar";
import { InputGroup } from "@dicehub/kappa/components/input-group";
import { ListFilter, Search, Settings2 } from "@lucide/vue";
</script>
<template>
<Toolbar aria-label="Record tools" class="record-toolbar">
<Toolbar.InputGroup aria-label="Search records" class="record-toolbar__search">
<InputGroup.Addon><Search aria-hidden="true" /></InputGroup.Addon>
<InputGroup.Input placeholder="Search records" />
</Toolbar.InputGroup>
<Toolbar.Button :icon="ListFilter" aria-label="Filter" />
<Toolbar.Button :icon="Settings2" aria-label="Settings" />
</Toolbar>
</template>
<style scoped>
.record-toolbar { width: min(100%, 28rem); }
.record-toolbar__search { flex: 1 1 auto; }
</style>Installation
Toolbar is part of the main Kappa package and has no runtime dependency beyond Vue.
Barrel
import { Toolbar } from "@dicehub/kappa";Granular
import { Toolbar } from "@dicehub/kappa/components/toolbar";Usage
Use Toolbar when several controls must read as one compact action or filter group. Give the root a short accessible name. Use Toolbar.Button, Toolbar.Link,Toolbar.Input, and Toolbar.InputGroup for grouped presentation and shared keyboard movement.
<script setup>
import { Toolbar } from "@dicehub/kappa/components/toolbar";
import { InputGroup } from "@dicehub/kappa/components/input-group";
import { ListFilter, Search } from "@lucide/vue";
</script>
<template>
<Toolbar aria-label="Record tools">
<Toolbar.InputGroup aria-label="Search records">
<InputGroup.Addon><Search aria-hidden="true" /></InputGroup.Addon>
<InputGroup.Input placeholder="Search records" />
</Toolbar.InputGroup>
<Toolbar.Button :icon="ListFilter" aria-label="Filter" />
</Toolbar>
</template>Composition
Toolbar.Root
├── Toolbar.Button
├── Toolbar.Separator
├── Toolbar.Link
├── Toolbar.Input
└── Toolbar.InputGroupThe root renders a native div with role="toolbar". Supported parts inherit the root size and mark one focusable control as the tab stop. A native button, link, input, or select may also be composed directly and is discovered for keyboard movement.
Toolbar.Buttonuses quiet button styling and defaults totype="button".Toolbar.Linkpreserves link semantics and uses KappaLinkButtonbehavior.- Each focusable control inside
Toolbar.InputGroupparticipates in toolbar focus movement. - Wrap
Toolbar.ButtonwithSelect.Trigger as-childto add a Select without changing its popup behavior. - Wrap
Toolbar.InputwithCombobox.TriggerInput as-childto add editable Combobox behavior. Toolbar.Separatoris decorative and defaults to the toolbar cross-axis.- Set
orientation="vertical"for a stacked toolbar. SetloopFocus="false"to stop at either edge.
Examples
Select
Wrap a Toolbar.Button with Select.Trigger and as-child to keep Select behavior in the grouped control.
<script setup>
import { Toolbar } from "@dicehub/kappa/components/toolbar";
import { Select } from "@dicehub/kappa/components/select";
import { ListFilter, Settings2 } from "@lucide/vue";
const sortItems = ["Name", "Created date", "Status"];
</script>
<template>
<Toolbar aria-label="Filter and sort records">
<Toolbar.Button :icon="ListFilter">Filter</Toolbar.Button>
<Select
id="toolbar-sort-records"
aria-label="Sort records"
:default-value="['Name']"
:items="sortItems"
:positioning="{ sameWidth: false }"
>
<Select.Trigger as-child>
<Toolbar.Button><Select.ValueText /></Toolbar.Button>
</Select.Trigger>
<Select.Positioner>
<Select.Content class="sort-menu">
<Select.List>
<template #default="{ item }">
<Select.Item :item="item">{{ item }}</Select.Item>
</template>
</Select.List>
</Select.Content>
</Select.Positioner>
</Select>
<Toolbar.Button :icon="Settings2" aria-label="View settings" />
</Toolbar>
</template>
<style scoped>
.sort-menu { min-width: 10rem; }
</style>Combobox
Use Combobox.TriggerInput with as-child to make a Toolbar.Input an editable popup trigger.
<script setup>
import { Toolbar } from "@dicehub/kappa/components/toolbar";
import { Combobox } from "@dicehub/kappa/components/combobox";
import { ListFilter, Settings2 } from "@lucide/vue";
const statusItems = ["All records", "Active", "Paused", "Failed"];
</script>
<template>
<Toolbar aria-label="Filter record status" class="status-toolbar">
<Toolbar.Button :icon="ListFilter">Status</Toolbar.Button>
<Combobox
id="toolbar-filter-status"
:default-value="['All records']"
:items="statusItems"
>
<Combobox.TriggerInput as-child>
<Toolbar.Input
aria-label="Filter status"
class="status-toolbar__input"
placeholder="Filter status…"
/>
</Combobox.TriggerInput>
<Combobox.Content>
<Combobox.Empty>No matching status.</Combobox.Empty>
<Combobox.List>
<template #default="{ item }">
<Combobox.Item :item="item">{{ item }}</Combobox.Item>
</template>
</Combobox.List>
</Combobox.Content>
</Combobox>
<Toolbar.Button :icon="Settings2" aria-label="Status settings" />
</Toolbar>
</template>
<style scoped>
.status-toolbar { width: min(100%, 28rem); }
.status-toolbar__input { flex: 1 1 auto; }
</style>Input shorthand
Use Toolbar.Input for simple text input that does not need an addon or suffix.
<script setup>
import { Toolbar } from "@dicehub/kappa/components/toolbar";
import { ListFilter, Settings2 } from "@lucide/vue";
</script>
<template>
<Toolbar aria-label="Search records" class="search-toolbar">
<Toolbar.Input
aria-label="Search records"
class="search-toolbar__input"
placeholder="Search records"
/>
<Toolbar.Button :icon="ListFilter" aria-label="Filter" />
<Toolbar.Button :icon="Settings2" aria-label="Settings" />
</Toolbar>
</template>
<style scoped>
.search-toolbar { width: min(100%, 28rem); }
.search-toolbar__input { flex: 1 1 auto; }
</style>Input group
Use Toolbar.InputGroup when one toolbar item needs an inline addon or suffix.
<script setup>
import { Toolbar } from "@dicehub/kappa/components/toolbar";
import { InputGroup } from "@dicehub/kappa/components/input-group";
</script>
<template>
<Toolbar aria-label="Open a site" class="site-toolbar">
<Toolbar.InputGroup aria-label="Site address" class="site-toolbar__input">
<InputGroup.Input aria-label="Site subdomain" placeholder="docs" />
<InputGroup.Addon align="inline-end">
<InputGroup.Text>.example.com</InputGroup.Text>
</InputGroup.Addon>
</Toolbar.InputGroup>
<Toolbar.Button>Open</Toolbar.Button>
</Toolbar>
</template>
<style scoped>
.site-toolbar { width: min(100%, 32rem); }
.site-toolbar__input { flex: 1 1 auto; }
</style>Sizes
Set size on the root to give every supported toolbar item the same density.
<script setup>
import { Toolbar, type ToolbarSize } from "@dicehub/kappa/components/toolbar";
const sizes: ToolbarSize[] = ["xs", "sm", "base", "lg"];
</script>
<template>
<div class="toolbar-sizes">
<div v-for="size in sizes" :key="size" class="toolbar-size-row">
<span class="toolbar-size-label">{{ size }}</span>
<Toolbar :aria-label="`${size} toolbar`" :size="size">
<Toolbar.Input :aria-label="`${size} search`" placeholder="Search…" />
<Toolbar.Button>Apply</Toolbar.Button>
</Toolbar>
</div>
</div>
</template>
<style scoped>
.toolbar-sizes { display: grid; gap: 0.75rem; }
.toolbar-size-row { display: flex; align-items: center; gap: 0.75rem; }
.toolbar-size-label { width: 2.5rem; }
</style>Button actions
Use quiet toolbar buttons for related actions that have the same priority.
<script setup>
import { Toolbar } from "@dicehub/kappa/components/toolbar";
import { Download, Upload } from "@lucide/vue";
</script>
<template>
<Toolbar aria-label="File actions">
<Toolbar.Button :icon="Upload">Upload</Toolbar.Button>
<Toolbar.Button :icon="Download">Download</Toolbar.Button>
</Toolbar>
</template>Links
Use Toolbar.Link for navigation. It keeps link semantics and joins arrow-key navigation.
<script setup>
import { Toolbar } from "@dicehub/kappa/components/toolbar";
import { BookOpen, Download } from "@lucide/vue";
</script>
<template>
<Toolbar aria-label="Documentation actions">
<Toolbar.Link href="/docs/components/button" :icon="BookOpen">Button docs</Toolbar.Link>
<Toolbar.Button :icon="Download">Download</Toolbar.Button>
</Toolbar>
</template>Accessible labels
Use aria-label for compact controls that do not have visible text.
<script setup>
import { Toolbar } from "@dicehub/kappa/components/toolbar";
import { Search } from "@lucide/vue";
</script>
<template>
<Toolbar aria-label="Search records">
<Toolbar.Input aria-label="Search records" placeholder="Search" />
<Toolbar.Button :icon="Search" aria-label="Search" />
</Toolbar>
</template>Accessibility
- Name the toolbar with a visible label,
aria-label, oraria-labelledby. - Use native buttons for actions and links for navigation. Add an accessible name to every icon-only button and input.
- Disabled controls use native disabled semantics and are skipped by Tab and arrow-key movement.
- Use
aria-pressedfor formatting toggles; Toolbar does not invent action state. - Focus indicators remain visible in light, dark, and forced-colors modes.
Keyboard support
| Key | Behavior |
|---|---|
Tab | Enter the toolbar at its first enabled item; leave the toolbar normally. |
Arrow Left / Right | Move between items in a horizontal toolbar. Direction follows RTL layout. |
Arrow Up / Down | Move between items in a vertical toolbar. |
Home / End | Move to the first or last enabled item when the focused control has no native movement. |
Enter | Activate the focused native button or link. |
Space | Activate the focused native button; inputs retain Space for editing. |
API Reference
Toolbar.Root
| Prop / Slot | Type | Default | Description |
|---|---|---|---|
orientation | "horizontal" | "vertical" | "horizontal" | Sets the layout axis and the arrow-key direction. |
size | "xs" | "sm" | "base" | "lg" | "base" | Sets the shared density for Toolbar.Button, Toolbar.Input, and Toolbar.InputGroup. |
disabled | boolean | false | Disables the toolbar and propagates native disabled behavior to its compound controls. |
loopFocus | boolean | true | Wraps arrow-key focus from the first enabled item to the last and back. |
default slot | slot | - | Toolbar parts or native controls. Native controls are discovered for roving focus. |
Parts
| Part | Type | Description |
|---|---|---|
Toolbar.Button | ToolbarButtonProps | Button action with quiet toolbar styling and inherited size. |
Toolbar.Link | ToolbarLinkProps | LinkButton-based navigation control with inherited size. |
Toolbar.Input | ToolbarInputProps | Text input with inherited size and native input events. |
Toolbar.InputGroup | ToolbarInputGroupProps | Each focusable InputGroup control participates in toolbar focus movement. |
Toolbar.Separator | ToolbarSeparatorProps | Decorative divider; defaults to the cross-axis of the toolbar. |
Keyboard
The root handles navigation for enabled toolbar items and preserves native text editing keys.
| Key | Behavior |
|---|---|
Tab | Enter the toolbar at its first enabled item; leave the toolbar normally. |
Arrow Left / Right | Move between items in a horizontal toolbar. Direction follows RTL layout. |
Arrow Up / Down | Move between items in a vertical toolbar. |
Home / End | Move to the first or last enabled item when the focused control has no native movement. |
Enter | Activate the focused native button or link. |
Space | Activate the focused native button; inputs retain Space for editing. |
Data attributes
| Attribute | Value | Description |
|---|---|---|
data-slot | "toolbar" | Identifies the toolbar root. |
data-orientation | "horizontal" | "vertical" | Resolved toolbar orientation. |
data-size | "xs" | "sm" | "base" | "lg" | Resolved toolbar density. |
data-disabled | present | Appears when the root is disabled. |
Exports
| Export | Description |
|---|---|
Toolbar | Compound toolbar root with Button, Link, Input, InputGroup, and Separator parts. |
ToolbarRoot / ToolbarButton / ToolbarLink | Named Vue component exports for granular composition. |
ToolbarInput / ToolbarInputGroup / ToolbarSeparator | Named input and divider component exports. |
ToolbarProps and part prop types | Public prop and slot contracts. |
ToolbarOrientation / ToolbarSize | Supported orientation and density values. |
resolveToolbarOrientation / resolveToolbarSize | Safe resolvers with Kappa defaults. |