- Accordion
- Alert
- Alert Dialog
- Aspect Ratio
- Avatar
- Badge
- Breadcrumb
- Button
- Button Group
- Calendar
- Card
- Carousel
- Checkbox
- Collapsible
- Combobox
- Command
- Context Menu
- Data Table
- Date Picker
- Dialog
- Drawer
- Dropdown Menu
- Empty
- Field
- Filter
- Form
- Hover Card
- Image
- Input
- Input Group
- Input OTP
- Item
- Kbd
- Label
- Mega Menu
- Menubar
- Native Select
- Navigation Menu
- Number Field
- Pagination
- Pin Input
- Popover
- Price
- Progress
- Radio Group
- Radio Stack
- Range Calendar
- Rating
- Resizable
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Spinner
- Stepper
- Swatch
- Swatch Group
- Switch
- Table
- Tabs
- Tags Input
- Textarea
- Toast
- Toggle
- Toggle Group
- Tooltip
- Typography
This is a Frontic UI custom component.
Only available items
<script setup lang="ts">
import { ref } from 'vue'
import {
Filter,
FilterGroup,
FilterList,
FilterPrice,
FilterRange,
FilterRating,
FilterReset,
FilterSlider,
FilterSwatch,
FilterSwitch,
} from '@/components/ui/filter'
const brands = ref([
{ option: 'Nike', value: 'nike', count: 342, selected: false },
{ option: 'Adidas', value: 'adidas', count: 287, selected: false, disabled: true },
{ option: 'Puma', value: 'puma', count: 164, selected: false },
])
const colors = ref([
{ value: 'red', hex: '#ef4444', label: 'Red', count: 42, selected: false },
{ value: 'blue', hex: '#3b82f6', label: 'Blue', count: 24, selected: false },
{ value: 'green', hex: '#22c55e', label: 'Green', count: 18, selected: false, disabled: true },
{ value: 'amber', hex: '#f59e0b', label: 'Amber', count: 15, selected: false },
])
const rating = ref(0)
const size = ref<{ from?: number, to?: number }>({ from: 38, to: 44 })
const priceAmount = ref<{ from?: number, to?: number }>({ to: 50000 })
const weight = ref<{ from?: number, to?: number }>({})
const inStock = ref(false)
function handleBrandToggle(value: string) {
const opt = brands.value.find(o => o.value === value)
if (opt) opt.selected = !opt.selected
}
function handleColorToggle(value: string) {
const opt = colors.value.find(o => o.value === value)
if (opt) opt.selected = !opt.selected
}
function resetAll() {
brands.value.forEach(o => (o.selected = false))
colors.value.forEach(o => (o.selected = false))
rating.value = 0
size.value = { from: 38, to: 44 }
priceAmount.value = { to: 50000 }
weight.value = {}
inStock.value = false
}
</script>
<template>
<Filter :reset="resetAll" class="flex w-64 flex-col gap-2">
<FilterGroup label="Brand">
<FilterList :options="brands" @toggle="handleBrandToggle" />
</FilterGroup>
<FilterGroup label="Color">
<FilterSwatch pill size="sm" orientation="vertical" :options="colors" @toggle="handleColorToggle" />
</FilterGroup>
<FilterGroup label="Rating">
<FilterRating
:value="rating"
@change="rating = $event"
/>
</FilterGroup>
<FilterGroup label="Size">
<FilterSlider
:min="34"
:max="50"
:step="1"
:value="size"
@change="size = $event"
/>
</FilterGroup>
<FilterGroup label="Price">
<FilterPrice
currency="EUR"
:precision="2"
:min="0"
:max="100000"
:step="1000"
:value="priceAmount"
@change="priceAmount = $event"
/>
</FilterGroup>
<FilterGroup label="Weight">
<FilterRange
unit="kg"
unit-position="end"
:min="0"
:max="100"
:step="1"
:value="weight"
placeholder-from="Min"
placeholder-to="Max"
@change="weight = $event"
/>
</FilterGroup>
<FilterSwitch
:value="inStock"
label="In Stock"
description="Only available items"
@change="inStock = $event"
/>
<FilterReset />
</Filter>
</template>Installation
pnpm dlx @frontic/ui add filter
Overview
The Filter component ships as a set of single-purpose primitives. Each one handles exactly one filter type, accepts simple props, and emits events when the user interacts.
| Component | Purpose |
|---|---|
Filter | Context provider (holds reset action) |
FilterList | Multi-select list with counts, search, and virtualization |
FilterSwatch | Color / visual selection with SwatchGroup |
FilterRating | Star rating selector |
FilterSlider | Range or max-value slider |
FilterPrice | Price slider with currency formatting |
FilterRange | Dual input fields with unit pre/append |
FilterSwitch | Labeled on/off toggle |
FilterGroup | Collapsible section or popover-style wrapper |
FilterTrigger | Shared trigger button for FilterGroup (label + badge + chevron) |
FilterFieldLabel | Accessible <label> for filter inputs via for/id |
FilterReset | Clear all active filters |
Key concepts:
Filteras context provider — wraps your filter panel, similar to<Form>. Provides aresetaction thatFilterResetpicks up automatically via inject. No manual event wiring needed.- Events over v-model — filter primitives don't own state. Components emit events (
@toggle,@change), and the parent updates state accordingly. FilterGroupas layout wrapper — wraps any filter primitive in either a collapsible section (default, for sidebars) or a popover dropdown (for toolbars / mobile). Both modes share the sameFilterTriggerbutton with label, active count badge, and animated chevron.FilterListandFilterSwatchboth accept an options array — the difference is visual. UseFilterSwatchfor color-type filters (withhexfield) andFilterListfor everything else.
Examples
FilterList
Filterable, virtualizable selection list with checkboxes, labels, and counts. Add searchable for a built-in search input. Wrap in Filter with FilterReset for a complete list filter.
FilterSwatch
Color or visual option selector. Accepts an options array with hex field and renders using SwatchGroup internally. Each option can have a label and count.
<script setup lang="ts">
import { FilterSwatch } from '@/components/ui/filter'
const options = ref([
{ value: 'red', hex: '#ef4444', label: 'Red', count: 42, selected: false },
{ value: 'blue', hex: '#3b82f6', label: 'Blue', count: 24, selected: false },
{ value: 'green', hex: '#22c55e', label: 'Green', count: 18, selected: false },
{ value: 'amber', hex: '#f59e0b', label: 'Amber', count: 15, selected: false },
])
function handleToggle(value: string) {
const opt = options.value.find(o => o.value === value)
if (opt) opt.selected = !opt.selected
}
</script>
<template>
<FilterSwatch :options="options" @toggle="handleToggle" />
</template>FilterRating
Star rating selector for "X stars & up" filtering. Click a star to select the minimum rating, click the same star again to deselect.
<script setup lang="ts">
import { FilterRating } from '@/components/ui/filter'
</script>
<template>
<FilterRating :value="4" label="Minimum Rating" @change="applyRatingFilter" />
</template>FilterSlider
Generic slider for range or max-value filtering. Pass { to: 25 } for a single-thumb "up to" slider, or { from: 38, to: 44 } for a dual-thumb range slider. Use formatValue to customize the display. For price-specific filtering, prefer FilterPrice.
<script setup lang="ts">
import { FilterSlider } from '@/components/ui/filter'
const size = ref({ from: 38, to: 44 })
</script>
<template>
<FilterSlider
label="Size"
:min="34"
:max="50"
:step="1"
:value="size"
@change="size = $event"
/>
</template>FilterPrice
Like FilterSlider but designed for price filtering with Frontic's PriceType conventions. Accepts currency and precision props and formats the display values using formatPrice (e.g. amount 50000 with precision 2 displays as €500.00).
<script setup lang="ts">
import { FilterPrice } from '@/components/ui/filter'
const price = ref({ from: 10000, to: 80000 })
</script>
<template>
<FilterPrice
label="Price Range"
currency="EUR"
:precision="2"
:min="0"
:max="100000"
:step="1000"
:value="price"
@change="price = $event"
/>
</template>FilterRange
Dual input fields with configurable unit prefix or suffix for from/to range filtering. Useful when users want to type exact values instead of dragging a slider.
<script setup lang="ts">
import { FilterRange } from '@/components/ui/filter'
const price = ref<{ from?: number, to?: number }>({})
</script>
<template>
<FilterRange
label="Price"
unit="€"
unit-position="start"
:min="0"
:max="1000"
placeholder-from="Min"
placeholder-to="Max"
:value="price"
@change="price = $event"
/>
</template>FilterSwitch
Labeled toggle switch for boolean filters like "In Stock" or "On Sale". Includes an optional description below the label.
Only available items
Discounted items only
<script setup lang="ts">
import { FilterSwitch } from '@/components/ui/filter'
</script>
<template>
<div class="space-y-1">
<FilterSwitch :value="true" label="In Stock" description="Only available items" @change="toggleInStock" />
<FilterSwitch :value="false" label="On Sale" description="Discounted items only" @change="toggleOnSale" />
</div>
</template>FilterGroup
Layout wrapper for filter primitives. Supports two display modes via the type prop:
| Type | Behavior | Use case |
|---|---|---|
| collapsible (default) | Expandable section with FilterTrigger + content below | Sidebar filter panels |
| popover | FilterTrigger opens a floating popover dropdown | Toolbars, mobile layouts |
Both modes render a FilterTrigger button with label, optional active count badge, and an animated chevron that rotates on open. Built on reka-ui Collapsible and Popover.
Collapsible (sidebar)
<script setup lang="ts">
import { FilterGroup, FilterSwatch } from '@/components/ui/filter'
</script>
<template>
<FilterGroup type="collapsible" label="Color" :active-count="1">
<FilterSwatch pill orientation="vertical" :options="colorOptions" @toggle="handleToggle" />
</FilterGroup>
</template>Popover (toolbar / mobile)
<template>
<div class="flex gap-2">
<FilterGroup label="Brand" :active-count="2" type="popover">
<FilterList :options="brandOptions" searchable @toggle="handleToggle" />
</FilterGroup>
<FilterGroup label="Color" :active-count="1" type="popover">
<FilterSwatch
:options="colorOptions.map(o => ({ ...o, hex: COLOR_MAP[o.value] ?? '#94A3B8', label: o.option }))"
@toggle="handleColorToggle"
/>
</FilterGroup>
</div>
</template>API Reference
Filter
Context provider that wraps your filter panel. Provides a reset function to FilterReset via inject.
| Prop | Type | Default |
|---|---|---|
reset | () => void | — |
FilterList
Filterable, virtualizable multi-select list. Built on reka-ui Listbox with Virtualizer.
| Prop | Type | Default |
|---|---|---|
options | FilterOption[] | — |
searchable | boolean | false |
disabled | boolean | false |
| Event | Payload |
|---|---|
toggle | string |
interface FilterOption {
option: string
value: string
count: number
selected?: boolean
disabled?: boolean
}FilterSwatch
Color option group. Accepts an options array and renders using SwatchGroup internally. Supports pill, size, and orientation props that are passed through to SwatchGroup. When count is provided on an option, it is rendered next to the label with matching FilterList styling.
| Prop | Type | Default |
|---|---|---|
options | FilterSwatchOption[] | — |
type | "single" | "multiple" | "multiple" |
disabled | boolean | false |
pill | boolean | false |
size | "sm" | "md" | "lg" | "md" |
orientation | "horizontal" | "vertical" | "horizontal" |
labelClass | string | — |
| Event | Payload |
|---|---|
toggle | string |
interface FilterSwatchOption {
value: string
hex: string
label?: string
count?: number
selected?: boolean
disabled?: boolean
}FilterRating
Star rating selector for "X stars & up" filtering.
| Prop | Type | Default |
|---|---|---|
value | number | 0 |
label | string | — |
max | number | 5 |
disabled | boolean | false |
| Event | Payload |
|---|---|
change | number |
FilterSlider
Generic range or max-value slider for numeric filters (size, distance, quantity, etc.). Pass { to } for a single thumb or { from, to } for a range. For price filtering, prefer FilterPrice.
| Prop | Type | Default |
|---|---|---|
value | { from?: number, to?: number } | — |
label | string | — |
description | string | — |
min | number | 0 |
max | number | 1000 |
step | number | 1 |
formatValue | (value: number) => string | — |
disabled | boolean | false |
| Event | Payload |
|---|---|
change | { from?: number, to?: number } |
FilterPrice
Price slider with automatic currency formatting. Values are raw amounts (e.g. 50000 for €500.00 at precision 2), matching Frontic's PriceType convention.
| Prop | Type | Default |
|---|---|---|
value | { from?: number, to?: number } | — |
label | string | — |
description | string | — |
currency | string | "EUR" |
precision | number | 2 |
locale | string | — |
min | number | 0 |
max | number | 100000 |
step | number | 100 |
disabled | boolean | false |
| Event | Payload |
|---|---|
change | { from?: number, to?: number } |
FilterRange
Dual input group with configurable unit prefix/suffix for from/to range filtering.
| Prop | Type | Default |
|---|---|---|
value | { from?: number, to?: number } | — |
label | string | — |
description | string | — |
unit | string | — |
unitPosition | "start" | "end" | "start" |
min | number | — |
max | number | — |
step | number | — |
placeholderFrom | string | "From" |
placeholderTo | string | "To" |
disabled | boolean | false |
| Event | Payload |
|---|---|
change | { from?: number, to?: number } |
FilterSwitch
Labeled toggle switch for boolean filters.
| Prop | Type | Default |
|---|---|---|
value | boolean | false |
label | string | — |
description | string | — |
disabled | boolean | false |
| Event | Payload |
|---|---|
change | boolean |
FilterGroup
Layout wrapper that renders either a collapsible section or a popover dropdown. Uses FilterTrigger internally for both modes.
| Prop | Type | Default |
|---|---|---|
label | string | — |
type | "popover" | "collapsible" | "popover" |
activeCount | number | — |
FilterTrigger
Shared trigger button used internally by FilterGroup. Renders a styled button with label text, an optional active count badge, and an animated chevron icon. Receives data-state from the parent Popover/Collapsible trigger via as-child for open/close chevron rotation.
| Prop | Type | Default |
|---|---|---|
label | string | — |
activeCount | number | — |
FilterFieldLabel
Accessible label element that associates with a filter input via for/id. Renders a native <label> with filter field styling. Used internally by FilterSwitch and available for custom filter compositions.
| Prop | Type | Default |
|---|---|---|
for | string | — |
class | string | — |
FilterReset
Reset button that automatically picks up the reset function from the Filter context via inject.
| Slot | Description |
|---|---|
default | Custom button text (default: "Reset") |