Domphy

Faceting & Filter UI

Faceting extracts unique values or min/max ranges from column data — the inputs you need to build filter controls like dropdowns, multi-select checkboxes, and range sliders.

Column faceting — unique values

Enable getFacetedUniqueValues() for a column to get a Map<value, count>:

import { createDomphyTable } from "@domphy/table/domphy"

const table = createDomphyTable({
  data: () => products,
  columns: [
    columnHelper.accessor("category", {
      header: "Category",
      filterFn: "arrIncludesSome",   // matches multi-select filter
      meta: { facet: true },
    }),
    columnHelper.accessor("brand",    { header: "Brand" }),
    columnHelper.accessor("price",    { header: "Price" }),
  ],
  getFacetedRowModel: getFacetedRowModel(),
  getFacetedUniqueValues: getFacetedUniqueValues(),
  getFacetedMinMaxValues: getFacetedMinMaxValues(),
})

Building a checkbox filter from unique values

import { toState } from "@domphy/core"

const selectedCategories = toState<string[]>([])

const CategoryFilter = {
  div: [
    { h3: "Category" },
    {
      div: (l) => {
        const col = table.table.getColumn("category")!
        const facetMap = col.getFacetedUniqueValues()
        return [...facetMap.entries()].map(([value, count]) => ({
          _key: String(value),
          label: [
            {
              input: null,
              type: "checkbox",
              checked: (l) => selectedCategories.get(l).includes(String(value)),
              onChange: (e: Event) => {
                const checked = (e.target as HTMLInputElement).checked
                const cat = String(value)
                selectedCategories.set((prev) =>
                  checked ? [...prev, cat] : prev.filter((c) => c !== cat)
                )
                // Apply filter to table
                col.setFilterValue(selectedCategories.get() || undefined)
              },
            },
            { span: `${value} (${count})` },
          ],
        }))
      },
    },
  ],
}

Min/max range filter (price slider)

import { toState } from "@domphy/core"

const priceRange = toState<[number, number]>([0, 10000])

// Initialize from facet data after mount
function initPriceFilter() {
  const col = table.table.getColumn("price")!
  const [min, max] = col.getFacetedMinMaxValues() ?? [0, 10000]
  priceRange.set([min, max])
}

const PriceRangeFilter = {
  div: [
    { h3: "Price" },
    {
      div: [
        {
          input: null,
          type: "range",
          min: (l) => String(priceRange.get(l)[0]),
          max: (l) => String(priceRange.get(l)[1]),
          value: (l) => String(priceRange.get(l)[0]),
          onInput: (e: Event) => {
            const min = Number((e.target as HTMLInputElement).value)
            priceRange.set(([_, max]) => [min, max])
            table.table.getColumn("price")?.setFilterValue(priceRange.get())
          },
        },
        {
          span: (l) => `$${priceRange.get(l)[0]} — $${priceRange.get(l)[1]}`,
        },
      ],
    },
  ],
  _onMount: initPriceFilter,
}

Global faceting

getFacetedUniqueValues() per column looks at filtered rows by default. To always show ALL values (even when filtered out), use global faceting:

import { getFacetedRowModel, getFacetedUniqueValues } from "@domphy/table"

const table = createDomphyTable({
  data: () => products,
  columns,
  getFacetedRowModel: getFacetedRowModel(),
  getFacetedUniqueValues: getFacetedUniqueValues(),
  // Pass the pre-filter row model for global (unfiltered) facets
  // table.table.getPreFilteredRowModel() in custom facet function
})

Faceting API

MethodReturnsDescription
column.getFacetedRowModel()RowModelRows before this column's filter is applied
column.getFacetedUniqueValues()Map<any, number>Unique values + counts
column.getFacetedMinMaxValues()[min, max] | undefinedMin/max for number columns

Active filter indicator

Show a badge or indicator when a filter is active:

const FilterBadge = (columnId: string) => {
  const col = table.table.getColumn(columnId)!
  return {
    span: "●",
    hidden: (l) => !col.getIsFiltered(),
    style: { color: "var(--accent-5)", marginLeft: "4px" },
    title: (l) => `Filtered: ${JSON.stringify(col.getFilterValue())}`,
  }
}

// Usage in column header
const Header = (col: HeaderCell) => ({
  th: [
    { span: String(col.columnDef.header ?? "") },
    FilterBadge(col.id),
  ],
})

Resetting all filters

const ClearFilters = {
  button: "Clear all filters",
  onClick: () => table.table.resetColumnFilters(),
  hidden: (l) => !table.table.getState().columnFilters.length,
}