---
title: "FilterChips"
description: "A single-select row of filter chips with dots and counts."
category: Data
status: beta
since: 0.3.9
tags: [filter, chips, table, data]
syncStatus: reviewed
verified: true
---

## Usage

### Basic
`items` gives each chip its `label`, `dot`, and `count`.
:::TuffDemoWrapper{demo="FilterChipsFilterChipsDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { computed, ref } from 'vue'

  const rows = [
    { task: 'Restock mango sorbet', status: 'todo' },
    { task: 'Churn black sesame', status: 'progress' },
    { task: 'Order waffle cones', status: 'done' },
  ]

  const filter = ref('all')

  const items = computed(() => [
    { value: 'all', label: 'All', count: rows.length },
    { value: 'todo', label: 'To do', dot: '#f09a2f', count: rows.filter(r => r.status === 'todo').length },
    { value: 'progress', label: 'In Progress', dot: '#16a6c7', count: rows.filter(r => r.status === 'progress').length },
    { value: 'done', label: 'Completed', dot: '#25a878', count: rows.filter(r => r.status === 'done').length },
  ])
  </script>

  <template>
    <TxFilterChips v-model="filter" :items="items" aria-label="Task status filter" />
  </template>
---
:::

### With a Data Table
The chips only report the selection; the host filters the rows, so counts and table share one data source.
:::TuffDemoWrapper{demo="FilterChipsFilterTableDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { computed, ref } from 'vue'

  const filter = ref('all')

  const visibleRows = computed(() =>
    filter.value === 'all' ? rows : rows.filter(row => row.status === filter.value),
  )

  const columns = [
    { key: 'task', title: 'Task name', minWidth: 180 },
    // Sorting reads the timestamp; the column only formats it for display.
    { key: 'dueAt', title: 'Date', width: 110, sortable: true, sorter: (a, b) => a.dueAt - b.dueAt },
    { key: 'status', title: 'Status', width: 140 },
  ]
  </script>

  <template>
    <TxFilterChips v-model="filter" :items="items" />
    <TxDataTable :columns="columns" :data="visibleRows" row-key="task" />
  </template>
---
:::

### Best Practices

- Compute counts from the row data with `computed`; never hardcode them.
- Filter in the host: remove filtered-out rows from the data instead of hiding them with CSS, which leaves them in the accessibility tree and tab order.
- Keep the default `role="toolbar"` for list filtering; use `tablist` only when mutually exclusive panels exist.
- Give every chip an `iconClass` once there are more than two or three; use `iconOnly` only for glyphs recognizable on their own.
- Turn `indicator` off only when the host's own animation moves the chips; reduced motion is already handled by the system setting.

## API Reference

### Props

| Prop | Type | Default | Description |
|------|------|---------|------|
| `modelValue` | `string \| number` | - | Currently selected chip. |
| `items` | `FilterChipItem[]` | `[]` | The chips. |
| `disabled` | `boolean` | `false` | Disables the whole row. |
| `role` | `'toolbar' \| 'tablist'` | `'toolbar'` | Whether chips act as toggle buttons or as tabs. |
| `indicator` | `boolean` | `true` | Slides one active fill between chips; off, each chip paints its own. |
| `iconOnly` | `boolean` | `false` | Draws only `iconClass` and moves `label` to `aria-label` and `title`; icon-less chips keep their text. |
| `ariaLabel` | `string` | `'Filters'` | Accessible name of the chip row. |

### Events

| Event | Payload | Description |
|------|------|------|
| `update:modelValue` | `(value)` | Selection changed. |
| `change` | `(value)` | Fires together with `update:modelValue`. |

### Slots

| Name | Description |
|------|------|
| `chip` | Replaces a chip's inner content; receives `{ item, active }`. The button shell and keyboard behavior stay. |

### FilterChipItem

| Field | Type | Description |
|------|------|------|
| `value` | `string \| number` | Chip identity. |
| `label` | `string` | Chip text. |
| `iconClass` | `string` | Leading icon class, drawn before the dot and the label. |
| `dot` | `string` | Leading dot color. |
| `count` | `number` | Trailing count badge; derive it from your data. |
| `disabled` | `boolean` | Disables this chip alone. |

## Overview

- The row is one tab stop: focus lands on the selected chip (or the first enabled one); arrow keys wrap and skip disabled chips, and `Home` / `End` jump to the ends.
- With `toolbar`, chips are `aria-pressed` toggles, and moving focus doesn't change the filter.
- With `tablist`, chips are `role="tab"` with `aria-selected` and selection follows focus; the host renders the matching `role="tabpanel"`.
- Clicking the selected chip emits nothing.
- The slider lands without travel on first paint, a rebuilt chip list, a resize, and under reduced motion; with nothing selected it isn't drawn.

## Technologies

- The slider is an absolutely positioned child of the scrolling row, placed by each chip's `offsetLeft` / `offsetTop`, so it scrolls with the chips.
- Source: `packages/tuffex/packages/components/src/filter-chips/`.
- Adapted from [Beautiful UI](https://www.beautifului.dev) (© 2026 Shane Levine, MIT); upstream has only `aria-pressed`, and this port adds keyboard navigation and the tablist shape.

<TuffDocSourceLink />
