Skip to content

UIFilterDropdown

UIFilterDropdown

A powerful filter dropdown component that enables users to apply and manage multiple filters with support for different filter types, operators, and values.

<template>
<UIFilterDropdown
v-model="filters"
:options="filterOptions"
:operators="operators"
:presets="filterPresets"
placeholder="Add filters"
@change="handleFilterChange"
>
<template #trigger="{ activeFilters }">
<UIButton type="default">
<FilterIcon class="w-5 h-5 mr-2" />
{{ activeFilters.length ? `${activeFilters.length} filters` : 'Filter' }}
</UIButton>
</template>
</UIFilterDropdown>
</template>
<script setup lang="ts">
interface FilterOption {
id: string
label: string
type: 'text' | 'number' | 'select' | 'date' | 'boolean'
operators?: string[]
options?: { label: string; value: any }[]
}
const filters = ref([])
const filterOptions: FilterOption[] = [
{
id: 'name',
label: 'Name',
type: 'text',
operators: ['contains', 'equals', 'starts_with', 'ends_with']
},
{
id: 'status',
label: 'Status',
type: 'select',
options: [
{ label: 'Active', value: 'active' },
{ label: 'Inactive', value: 'inactive' },
{ label: 'Pending', value: 'pending' }
]
},
{
id: 'created_at',
label: 'Created Date',
type: 'date',
operators: ['equals', 'before', 'after', 'between']
}
]
const operators = {
text: [
{ label: 'Contains', value: 'contains' },
{ label: 'Equals', value: 'equals' },
{ label: 'Starts with', value: 'starts_with' },
{ label: 'Ends with', value: 'ends_with' }
],
number: [
{ label: 'Equals', value: 'equals' },
{ label: 'Greater than', value: 'gt' },
{ label: 'Less than', value: 'lt' },
{ label: 'Between', value: 'between' }
]
}
const filterPresets = [
{
id: 'active_recent',
label: 'Active & Recent',
filters: [
{ field: 'status', operator: 'equals', value: 'active' },
{ field: 'created_at', operator: 'after', value: '2024-01-01' }
]
}
]
</script>

Props

  • modelValue (array): Active filters
  • options (array): Filter field options
  • operators (object): Available operators by type
  • presets (array): Predefined filter combinations
  • placeholder (string): Input placeholder
  • disabled (boolean): Disable component
  • loading (boolean): Loading state
  • maxFilters (number): Maximum number of filters
  • searchable (boolean): Enable field search
  • clearable (boolean): Allow clearing filters
  • placement (string): Dropdown placement
  • size (‘sm’ | ‘md’ | ‘lg’): Component size
  • width (number | string): Dropdown width

Events

  • update:modelValue: Filters changed
  • change: Filters changed
  • preset: Preset applied
  • clear: Filters cleared
  • add: Filter added
  • remove: Filter removed
  • search: Field search

Slots

  • trigger: Custom trigger content
  • empty: Empty state content
  • field: Custom field render
  • operator: Custom operator render
  • value: Custom value render
  • preset: Custom preset render

Usage Examples

  1. Basic Filter Dropdown:
<template>
<UIFilterDropdown
v-model="filters"
:options="[
{
id: 'status',
label: 'Status',
type: 'select',
options: [
{ label: 'Active', value: 'active' },
{ label: 'Inactive', value: 'inactive' }
]
},
{
id: 'type',
label: 'Type',
type: 'select',
options: [
{ label: 'User', value: 'user' },
{ label: 'Admin', value: 'admin' }
]
}
]"
/>
</template>
<script setup>
const filters = ref([])
</script>
  1. Advanced Data Table Filters:
<template>
<div class="space-y-4">
<div class="flex items-center justify-between">
<UIFilterDropdown
v-model="tableFilters"
:options="filterOptions"
:operators="operators"
:loading="loading"
class="w-full max-w-xl"
>
<template #trigger="{ activeFilters }">
<UIButton
type="default"
class="w-full justify-between"
>
<div class="flex items-center">
<FilterIcon class="w-5 h-5 mr-2" />
{{ activeFilters.length ? `${activeFilters.length} filters applied` : 'Add filters' }}
</div>
<ChevronDownIcon class="w-5 h-5" />
</UIButton>
</template>
<template #preset="{ preset, active, apply }">
<div
class="flex items-center justify-between p-2 hover:bg-gray-50 cursor-pointer"
:class="{ 'bg-primary-50': active }"
@click="apply"
>
<div>
<div class="font-medium">
{{ preset.label }}
</div>
<div class="text-sm text-gray-500">
{{ preset.description }}
</div>
</div>
<CheckIcon
v-if="active"
class="w-5 h-5 text-primary-500"
/>
</div>
</template>
</UIFilterDropdown>
<UIButton
v-if="tableFilters.length"
variant="ghost"
class="ml-2"
@click="clearFilters"
>
Clear
</UIButton>
</div>
<div
v-if="tableFilters.length"
class="flex flex-wrap gap-2"
>
<UITag
v-for="filter in tableFilters"
:key="filter.id"
:closable="true"
@close="removeFilter(filter)"
>
{{ formatFilter(filter) }}
</UITag>
</div>
<UITable
:data="filteredData"
:columns="columns"
:loading="loading"
/>
</div>
</template>
<script setup>
const formatFilter = (filter) => {
const field = filterOptions.find(f => f.id === filter.field)
const operator = operators[field.type].find(o => o.value === filter.operator)
return `${field.label} ${operator.label} ${filter.value}`
}
</script>
  1. Custom Filter Types:
<template>
<UIFilterDropdown
v-model="filters"
:options="customFilters"
>
<template #value="{ field, value, onChange }">
<template v-if="field.type === 'price'">
<div class="flex items-center space-x-2">
<span class="text-gray-500">$</span>
<UIInput
v-model="value.min"
type="number"
placeholder="Min"
class="w-24"
@update:modelValue="v => onChange({ ...value, min: v })"
/>
<span class="text-gray-500">to</span>
<UIInput
v-model="value.max"
type="number"
placeholder="Max"
class="w-24"
@update:modelValue="v => onChange({ ...value, max: v })"
/>
</div>
</template>
<template v-else-if="field.type === 'rating'">
<UIRating
v-model="value"
:max="5"
@update:modelValue="onChange"
/>
</template>
</template>
</UIFilterDropdown>
</template>
<script setup>
const customFilters = [
{
id: 'price',
label: 'Price Range',
type: 'price',
defaultValue: { min: null, max: null }
},
{
id: 'rating',
label: 'Rating',
type: 'rating'
}
]
</script>

Best Practices

  1. User Experience:

    • Clear labels
    • Intuitive operators
    • Visual feedback
    • Keyboard support
  2. Performance:

    • Debounced updates
    • Efficient rendering
    • Lazy loading
    • Memory management
  3. Accessibility:

    • ARIA labels
    • Focus management
    • Screen reader support
    • Keyboard navigation
  4. Mobile:

    • Touch targets
    • Responsive layout
    • Clear feedback
    • Simplified interaction

Common Use Cases

  1. Data Table Filters:
<template>
<div class="flex items-center space-x-4">
<UISearch
v-model="search"
placeholder="Search..."
class="w-64"
/>
<UIFilterDropdown
v-model="filters"
:options="filterOptions"
:operators="operators"
class="flex-1"
/>
<UIButton
type="primary"
:loading="loading"
@click="applyFilters"
>
Apply
</UIButton>
</div>
</template>
  1. Analytics Dashboard:
<template>
<div class="space-y-4">
<div class="flex items-center justify-between">
<h2 class="text-lg font-medium">
Analytics
</h2>
<div class="flex items-center space-x-2">
<UIFilterDropdown
v-model="dateFilter"
:options="[
{
id: 'date_range',
label: 'Date Range',
type: 'daterange'
}
]"
>
<template #trigger>
<UIButton type="default">
<CalendarIcon class="w-5 h-5 mr-2" />
{{ formatDateRange(dateFilter[0]?.value) }}
</UIButton>
</template>
</UIFilterDropdown>
<UIFilterDropdown
v-model="metricFilters"
:options="metricOptions"
:presets="metricPresets"
/>
</div>
</div>
<AnalyticsChart
:data="filteredData"
:filters="metricFilters"
/>
</div>
</template>
  1. E-commerce Filters:
<template>
<div class="flex items-start space-x-4">
<div class="w-64 space-y-4">
<UIFilterDropdown
v-model="productFilters"
:options="[
{
id: 'category',
label: 'Category',
type: 'tree',
options: categories
},
{
id: 'price',
label: 'Price',
type: 'range',
min: 0,
max: 1000,
step: 10
},
{
id: 'rating',
label: 'Rating',
type: 'rating'
}
]"
placement="bottom-start"
class="w-full"
>
<template #value="{ field, value, onChange }">
<template v-if="field.type === 'range'">
<UISlider
v-model="value"
:min="field.min"
:max="field.max"
:step="field.step"
range
@update:modelValue="onChange"
/>
</template>
</template>
</UIFilterDropdown>
<div
v-if="productFilters.length"
class="space-y-2"
>
<div class="text-sm font-medium">
Active Filters
</div>
<div class="space-y-1">
<UITag
v-for="filter in productFilters"
:key="filter.id"
:closable="true"
size="sm"
class="w-full"
@close="removeFilter(filter)"
>
{{ formatProductFilter(filter) }}
</UITag>
</div>
</div>
</div>
<div class="flex-1">
<ProductGrid
:products="filteredProducts"
:loading="loading"
/>
</div>
</div>
</template>

Component Composition

The UIFilterDropdown component works well with:

  • UIButton for triggers
  • UIInput for text/number filters
  • UISelect for dropdown filters
  • UIDatePicker for date filters
  • UISlider for range filters
  • UITag for active filters
  • UITooltip for help text
  • UIPopover for complex filters