Skip to content

useFilter

Composable for defining a named filter group whose state can be read and mutated from any component on the page — picker in the header, bar in the sidebar, anywhere.

Mental model:

  • A group has an id and contains one or more filters.
  • Each filter exposes a v-model-compatible bind bagv-bind="filters.<name>" onto any component.
  • Helpers (clear, isActive, value) live under filters.$.<name>.
  • A reactive $active list drives "selected chips with unselect" UIs.
  • { url: true } lazily wires useUrlSync — the dependency is only loaded when the flag is set.

Live Demo

Pickers — placed anywhere

Status

Filter bar — reads $active, calls clear per chip

Nothing selected.

The { url: true } flag is intentionally off here — its dependency on Nuxt's useRoute only matters when set. In a Nuxt app, pass it as the third argument and the URL will update reactively.

Defining a group

ts
const filters = useFilter("appointments", {
  category: { value: null },
  status: { value: [] as string[] },
  dateRange: { value: { from: null, to: null } },
});

Mirror state to the URL

Pass { url: true } as the third argument:

ts
const filters = useFilter("appointments", config, { url: true });

useUrlSync is dynamically imported only when this flag is set — so useFilter works in non-Nuxt environments (tests, Storybook, VitePress demos) as long as url stays off. Filter names become top-level URL keys; avoid colliding names across multiple URL-synced groups on the same page.

Retrieving the same group

Call useFilter(id) with no config — anywhere in the app:

ts
const filters = useFilter("appointments");

If the group has not been defined yet, this throws. Define before consumers mount (e.g. at a parent component or layout level).

Binding to components

Each filter is a v-model-compatible bag — spread it onto any component that supports v-model:

vue
<orio-selector v-bind="filters.category" :options="categories" />
<orio-checkbox-group v-bind="filters.status" :options="statuses" />
<orio-date-range-picker v-bind="filters.dateRange" />

App data (options, labels, etc.) is passed on the component normally — it is not part of the filter config.

Building a filter bar

Iterate $active — a computed list of every filter whose current value differs from its initial:

vue
<template>
  <button v-for="f in filters.$active.value" :key="f.name" @click="f.clear">
    {{ f.name }}: {{ f.value }} &times;
  </button>

  <orio-button v-if="filters.$active.value.length" @click="filters.$clearAll">
    Clear all
  </orio-button>
</template>

API reference

useFilter(id, config?, options?)

ArgumentTypeDescription
idstringStable identifier for the group.
configRecord<string, { value }>Map of filter name → initial value. Required on the first call for a given id.
options{ url?: boolean }url: true lazy-loads useUrlSync and mirrors $state to URL query params. Off by default.

Returns a FilterGroup.

FilterGroup shape

ts
filters.<name>              // { modelValue, 'onUpdate:modelValue' } — spread with v-bind
filters.$.<name>            // { value, initial, isActive, clear }
filters.$active             // ComputedRef<{ name, value, clear }[]>
filters.$clearAll()         // reset every filter to its initial value
filters.$state              // Ref<Record<string, unknown>> — raw reactive state

disposeFilter(id)

Removes a group from the registry. Use in SPAs where filter groups are page-scoped:

ts
import { disposeFilter } from "orio-ui";
onBeforeUnmount(() => disposeFilter("appointments"));

Caveats

  • Define before consume. Calling useFilter(id) without config throws if the id is unknown. Define the group at a level that runs before any consumer.
  • No auto-cleanup. Groups persist for the app lifetime. Call disposeFilter(id) manually when scoping to a route.
  • URL sync requires Nuxt. { url: true } depends on useUrlSync, which uses Nuxt's #imports for SSR-safe pre-population. The dependency is lazy-loaded, so leaving the flag off keeps useFilter usable outside of Nuxt.

See also

  • useUrlSync — underlying URL serialization (composed by { url: true }).