---
title: Toolbar
description: Horizontal bar with search, filters, and action buttons
visibility: guest
draft: false
---

# Toolbar

A horizontal action bar with named slots for search, filters, and action buttons. Supports sticky positioning, density variants, and flexible alignment.

## Basic Usage

:::preview{title="Simple toolbar"}
<x-wirekit::toolbar aria-label="Page actions">
    <x-wirekit::button size="sm">Export</x-wirekit::button>
    <x-wirekit::button size="sm" intent="primary">Create</x-wirekit::button>
</x-wirekit::toolbar>
:::

## Three-Zone Layout

Use named slots for structured toolbar regions:

:::preview{title="Leading + filters + trailing"}
<x-wirekit::toolbar aria-label="Search and filter">
    <x-slot:leading>
        <x-wirekit::input aria-label="Search" type="search" placeholder="Search..." />
    </x-slot:leading>
    <x-slot:filters>
        <x-wirekit::badge>Active</x-wirekit::badge>
        <x-wirekit::badge intent="neutral">Archived</x-wirekit::badge>
    </x-slot:filters>
    <x-slot:trailing>
        <x-wirekit::button size="sm" intent="primary">New Item</x-wirekit::button>
    </x-slot:trailing>
</x-wirekit::toolbar>
:::

## Sticky

Pin the toolbar to the top of a scrollable container. The preview wraps the toolbar plus a tall body in an overflow-scrolling box so the sticky behavior is observable without scrolling the outer page — scroll inside the box to watch the toolbar stay anchored at the top.

:::preview{title="Sticky toolbar pinned at top of scrollable container"}
<div tabindex="0" role="region" aria-label="Sticky toolbar demo scroll panel" style="height: 16rem; overflow-y: auto; border: 1px solid var(--color-wk-border); border-radius: var(--radius-wk-md);">
    <x-wirekit::toolbar :sticky="true" aria-label="Sticky actions">
        <x-wirekit::button size="sm">Save</x-wirekit::button>
        <x-wirekit::button size="sm" intent="danger">Discard</x-wirekit::button>
    </x-wirekit::toolbar>
    <div style="padding: 1rem; font-family: var(--font-wk-sans); color: var(--color-wk-text); display: flex; flex-direction: column; gap: 0.75rem;">
        <p>Scroll this panel — the toolbar above stays pinned to the top while the body below scrolls. The sticky-pin uses native CSS <code>position: sticky</code> against the nearest scrolling ancestor (this overflow:auto wrapper), so no JS scroll listener is needed and the behavior holds at any viewport.</p>
        <p>For the sticky to work the toolbar's parent must be either a scrolling container (this wrapper) or the document root. Mounting a sticky toolbar inside a parent with <code>overflow: hidden</code> or a flex layout that constrains height will pin it against the wrong ancestor — opt out by setting <code>:sticky="false"</code> when the toolbar lives inside a non-scrolling card.</p>
        <p>Filler content to demonstrate the scroll. Each paragraph adds vertical real estate that pushes the bottom of the panel beyond the visible viewport. The toolbar tracks the top boundary as the reader scrolls.</p>
        <p>Use sticky toolbars for primary actions in long-form admin panels (Save / Discard for an edit screen, Filter / Sort for a long results list, Compose / Reply for a mail thread). Avoid for read-only contexts where the toolbar's affordances are unrelated to the body content.</p>
        <p>Combine with <code>density="compact"</code> to minimize the height the sticky toolbar reserves at the top of the scrollable region — useful when vertical space is tight.</p>
        <p>The sticky pin respects the container's <code>top</code> offset, so if the scrolling ancestor itself has padding-top, the toolbar pins below the padding rather than against the raw scroll edge.</p>
        <p>More filler. The sticky should now be visibly anchored. Scroll back to the top to see the toolbar return to its document-flow position; the pinning is purely a scroll-position behavior, not a layout shift.</p>
        <p>End of the scrollable body. The toolbar above is the last thing you see at the top regardless of how far down you scroll.</p>
    </div>
</div>
:::

## Density Variants

:::preview{title="Compact vs comfortable density"}
<x-wirekit::stack gap="md">
    <x-wirekit::toolbar density="compact" aria-label="Compact toolbar">
        <x-wirekit::button size="sm">Action A</x-wirekit::button>
        <x-wirekit::button size="sm">Action B</x-wirekit::button>
    </x-wirekit::toolbar>
    <x-wirekit::toolbar density="comfortable" aria-label="Comfortable toolbar">
        <x-wirekit::button size="sm">Action A</x-wirekit::button>
        <x-wirekit::button size="sm">Action B</x-wirekit::button>
    </x-wirekit::toolbar>
</x-wirekit::stack>
:::

## Alignment

:::preview{title="Alignment variants"}
<x-wirekit::stack gap="md">
    <x-wirekit::toolbar align="start" aria-label="Start-aligned">
        <x-wirekit::button size="sm">Left</x-wirekit::button>
    </x-wirekit::toolbar>
    <x-wirekit::toolbar align="end" aria-label="End-aligned">
        <x-wirekit::button size="sm">Right</x-wirekit::button>
    </x-wirekit::toolbar>
    <x-wirekit::toolbar align="between" aria-label="Space-between">
        <x-slot:leading>
            <x-wirekit::button size="sm">Left</x-wirekit::button>
        </x-slot:leading>
        <x-slot:trailing>
            <x-wirekit::button size="sm">Right</x-wirekit::button>
        </x-slot:trailing>
    </x-wirekit::toolbar>
</x-wirekit::stack>
:::

## Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `sticky` | `bool` | `false` | Pin to top of scroll container |
| `density` | `string` | `'comfortable'` | Padding density: `comfortable` or `compact` |
| `align` | `string` | `'between'` | Content alignment: `between`, `start`, `end` |
| `aria-label` | `string` | `null` | Accessible label for the toolbar |

## Slots

| Slot | Description |
|------|-------------|
| `leading` | Left region (search, primary controls) |
| `filters` | Center region (filter chips, badges) |
| `trailing` | Right region (action buttons) |
| default | Falls back to trailing region |

## Accessibility

- `role="toolbar"` on root element
- Requires `aria-label` for screen reader identification
- Responsive: wraps on narrow viewports via `flex-wrap`

## Keyboard Interaction

This component is a layout wrapper. Keyboard interaction is delegated to its children.

## Pitfalls

- **Don't render a toolbar without `aria-label`.** WAI-ARIA Toolbar Pattern requires identification — without it, screen readers announce "toolbar" with no context.

## Design Tokens

The Toolbar is a layout primitive — it composes its inner controls (search inputs, buttons, badges) and inherits their tokens. Only the wrapper itself reaches for tokens directly:

| Token | Used for |
| --- | --- |
| `--font-wk-sans` | Toolbar font family |
| `--color-wk-bg` | Toolbar background |

For control-level tokens see [Input](/components/input#design-tokens), [Button](/components/button#design-tokens), and [Badge](/components/badge#design-tokens).
