Toolbar that folds filter controls behind a Filters button once more than three controls would sit inline
Tags: component layout toolbar filters
FilterToolbar is a Toolbar with a built-in rule for busy list pages: when the left side would hold more than three controls (search counts; count text does not), the filters move behind a Filters button. On desktop the button expands a FilterPanel under the strip; on mobile (below lg) it opens a full-screen Dialog. Filters apply live in every mode.
Pair it with ActiveFilterChips under the toolbar so filters hidden behind the button stay visible and removable.
Import
<script>
import { FilterToolbar, ActiveFilterChips } from '@classic-homes/theme-svelte';
</script>Basic Usage
<script>
import {
FilterToolbar,
ActiveFilterChips,
Input,
Label,
Select,
} from '@classic-homes/theme-svelte';
let search = $state('');
let status = $state('');
let type = $state('');
let supplier = $state('');
const activeCount = $derived([status, type, supplier].filter(Boolean).length);
const clear = () => (status = type = supplier = '');
</script>
<FilterToolbar label="Sign tools" filterCount={3} {activeCount} onClear={clear}>
{#snippet search()}
<Input type="search" aria-label="Search signs" class="w-full sm:w-56" bind:value={search} />
{/snippet}
{#snippet filters({ inline })}
<div class="space-y-1">
<Label for="status" class={inline ? 'sr-only' : ''}>Status</Label>
<Select id="status" options={statusOptions} bind:value={status} />
</div>
<!-- …type, supplier -->
{/snippet}
{#snippet summary()}<span class="text-sm">42 signs</span>{/snippet}
</FilterToolbar>
<ActiveFilterChips
chips={status
? [{ key: 'status', label: `Status: ${status}`, onRemove: () => (status = '') }]
: []}
onClearAll={clear}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
label |
string |
required | Accessible name of the strip (required) |
search |
Snippet |
- | Search field; always inline |
filters |
Snippet<[{ inline: boolean }]> |
required | Filter controls; inline is false inside the panel/dialog |
filterCount |
number |
required | Controls rendered by filters (drives the collapse rule) |
activeCount |
number |
0 |
Active filters, shown as a badge on the button |
summary |
Snippet |
- | Result count after the filters / button |
end |
Snippet |
- | Right-aligned cluster |
onClear |
() => void |
- | Resets every filter ("Clear all") |
maxInline |
number |
3 |
Most left-side controls (search included) allowed inline |
open |
boolean |
false |
Panel/dialog open state (bindable) |
mobileQuery |
string |
'(max-width: 1023px)' |
Below this viewport filters always collapse into a dialog |
class |
string |
- | Additional classes on the wrapper |
Accessibility
- The Filters button carries
aria-expanded, andaria-controlspointing at the panel on desktop; its name includes the active count ("Filters, 2 active"). - The collapsed panel is
inert, so hidden controls are not tabbable or announced. - Render a label for every control; keep it
sr-onlywheninlineis true.
Related Components
- Toolbar - The underlying strip
- ActionHeader - Page header with a
toolbarslot