Qure UI
Components

Filter Bar

What is currently filtering a list, and a way to undo it.

Filtered by
Chest CTUnread messagesLast 7 days
'use client'import { useState } from 'react'import { FilterBar, FilterChip } from '@/registry/qure/ui/filter-bar'const FILTERS = ['Chest CT', 'Unread messages', 'Last 7 days']export default function FilterBarDemo() {  const [filters, setFilters] = useState(FILTERS)  return (    <div className="w-full max-w-sm">      <FilterBar onClear={() => setFilters([])}>        {filters.map((f) => (          <FilterChip key={f}>{f}</FilterChip>        ))}      </FilterBar>      {filters.length === 0 && (        <button          type="button"          className="text-body-small text-muted-foreground underline"          onClick={() => setFilters(FILTERS)}        >          Reapply filters        </button>      )}    </div>  )}

Installation

npx shadcn@latest add https://qure-ui.qure.ai/r/filter-bar.json

Usage

import { FilterBar, FilterChip } from '@/components/ui/filter-bar'
<FilterBar onClear={() => setFilters([])}>
  {filters.map((f) => (
    <FilterChip key={f.key}>{f.label}</FilterChip>
  ))}
</FilterBar>

This is one of the components with no shadcn equivalent. It comes from worklist-filter-bar.tsx in qtrack-lc, where it sits under the search field in the patient panel. Nothing about it is a worklist, so it ships on its own.

Why bother

A filtered list and an empty one look identical. Three chips explaining why there are four results is the difference between "no patients match this" and "no patients, something is broken" — and the second reading is the one people act on, usually by reloading and asking someone.

It matters more the longer a filter survives. A saved filter applied three days ago is invisible by Wednesday; the bar is what stops that becoming a support ticket.

Overflow

Filtered by
Chest CTUnread messagesLast 7 days
'use client'import { FilterBar, FilterChip } from '@/registry/qure/ui/filter-bar'const FILTERS = [  'Chest CT',  'Unread messages',  'Last 7 days',  'Bengaluru — Whitefield site',  'Reported by Dr. Nair',  'Priority: urgent',]export default function FilterBarOverflow() {  return (    <FilterBar onClear={() => {}} className="w-full max-w-sm">      {FILTERS.map((f) => (        <FilterChip key={f}>{f}</FilterChip>      ))}    </FilterBar>  )}

Past max chips, the rest collapse into "+N more", which opens a Popover listing all of them. max defaults to 3, which is what the app uses.

The chips in the bar truncate to keep the row one line; the ones in the popover wrap, because reading the ones that did not fit is the entire point of opening it. Every chip also carries its own full text as a title.

Nothing applied

FilterBar renders null when it has no chips. That is deliberate, and it means you can leave it mounted rather than guarding it at the call site.

A bar reading "Filtered by" over an empty row is a claim that something is being hidden, which sends people looking for a filter that is not there.

Clearing

onClear is optional; omit it and the button is not rendered. Include it wherever you can — a filter someone cannot see how to remove is one they will remove by reloading the page, losing whatever else they had set up.

Clearing everything at once is the common case and the only one here. Removing chips one at a time means each chip is a button, and then the bar is a control rather than a summary; if that is what you want, put a Badge row together yourself.

API Reference

FilterBar

PropTypeDefaultDescription
labelReactNode"Filtered by"The lead-in, shown in the bar and above the overflow list.
onClear() => voidOmit and no clear button is rendered.
clearLabelReactNode"Clear"Text for that button.
maxnumber3Chips shown before the rest move into "+N more".

FilterChip

Renders a <span>. title defaults to the chip's own text when that text is a string, so a truncated chip still says what it is on hover.

On this page