10.1k

Filter

PreviousNext

Single-purpose filter primitives for e-commerce storefronts. Each component handles one filter type — checkbox, swatch, range, rating, switch — and works standalone or composed into filter panels.

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.

ComponentPurpose
FilterContext provider (holds reset action)
FilterListMulti-select list with counts, search, and virtualization
FilterSwatchColor / visual selection with SwatchGroup
FilterRatingStar rating selector
FilterSliderRange or max-value slider
FilterPricePrice slider with currency formatting
FilterRangeDual input fields with unit pre/append
FilterSwitchLabeled on/off toggle
FilterGroupCollapsible section or popover-style wrapper
FilterTriggerShared trigger button for FilterGroup (label + badge + chevron)
FilterFieldLabelAccessible <label> for filter inputs via for/id
FilterResetClear all active filters

Key concepts:

  • Filter as context provider — wraps your filter panel, similar to <Form>. Provides a reset action that FilterReset picks 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.
  • FilterGroup as 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 same FilterTrigger button with label, active count badge, and animated chevron.
  • FilterList and FilterSwatch both accept an options array — the difference is visual. Use FilterSwatch for color-type filters (with hex field) and FilterList for 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.

Minimum Rating
<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.

Max Distance
Up to 25 km
Size
38 – 44
<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).

Max Price
Up to €550.00
Price Range
€100.00 – €800.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.

Price
Weight
kg
kg
<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:

TypeBehaviorUse case
collapsible (default)Expandable section with FilterTrigger + content belowSidebar filter panels
popoverFilterTrigger opens a floating popover dropdownToolbars, 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.

PropTypeDefault
reset() => void

FilterList

Filterable, virtualizable multi-select list. Built on reka-ui Listbox with Virtualizer.

PropTypeDefault
optionsFilterOption[]
searchablebooleanfalse
disabledbooleanfalse
EventPayload
togglestring
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.

PropTypeDefault
optionsFilterSwatchOption[]
type"single" | "multiple""multiple"
disabledbooleanfalse
pillbooleanfalse
size"sm" | "md" | "lg""md"
orientation"horizontal" | "vertical""horizontal"
labelClassstring
EventPayload
togglestring
interface FilterSwatchOption {
  value: string
  hex: string
  label?: string
  count?: number
  selected?: boolean
  disabled?: boolean
}

FilterRating

Star rating selector for "X stars & up" filtering.

PropTypeDefault
valuenumber0
labelstring
maxnumber5
disabledbooleanfalse
EventPayload
changenumber

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.

PropTypeDefault
value{ from?: number, to?: number }
labelstring
descriptionstring
minnumber0
maxnumber1000
stepnumber1
formatValue(value: number) => string
disabledbooleanfalse
EventPayload
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.

PropTypeDefault
value{ from?: number, to?: number }
labelstring
descriptionstring
currencystring"EUR"
precisionnumber2
localestring
minnumber0
maxnumber100000
stepnumber100
disabledbooleanfalse
EventPayload
change{ from?: number, to?: number }

FilterRange

Dual input group with configurable unit prefix/suffix for from/to range filtering.

PropTypeDefault
value{ from?: number, to?: number }
labelstring
descriptionstring
unitstring
unitPosition"start" | "end""start"
minnumber
maxnumber
stepnumber
placeholderFromstring"From"
placeholderTostring"To"
disabledbooleanfalse
EventPayload
change{ from?: number, to?: number }

FilterSwitch

Labeled toggle switch for boolean filters.

PropTypeDefault
valuebooleanfalse
labelstring
descriptionstring
disabledbooleanfalse
EventPayload
changeboolean

FilterGroup

Layout wrapper that renders either a collapsible section or a popover dropdown. Uses FilterTrigger internally for both modes.

PropTypeDefault
labelstring
type"popover" | "collapsible""popover"
activeCountnumber

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.

PropTypeDefault
labelstring
activeCountnumber

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.

PropTypeDefault
forstring
classstring

FilterReset

Reset button that automatically picks up the reset function from the Filter context via inject.

SlotDescription
defaultCustom button text (default: "Reset")