Category Filters

Filter pills used on list pages. Desktop wraps onto multiple lines; mobile uses a horizontal scroll with a right-side gradient fade. Click writes to the `?category=` URL param with scroll preservation.

Blog filters

Active category

Case study filters

Props

PropTypeDefaultDescription
categories*string[]-The filter options. The component prepends the "All" pill automatically.
activeCategorystring | null-Overrides the active category from context. Only for rendering a fixed state in isolation (like the examples on this page) — pages should let the provider drive it.
resultCountnumber-Number of items currently shown — rendered below the filter row. Omit to derive it from counts/totalCount.
allLabelstring"All"Label used for the reset pill.

Import

tsx
import {
  CategoryFilterProvider,
  CategoryFilters,
} from '@/components/ui/CategoryFilters'

Usage

tsx
// Wrap the pills and the list they filter in one provider. It owns
// the active category and keeps ?category= in the URL for shareability.
//
// It deliberately does NOT use useSearchParams(): calling that in a
// statically-rendered route makes Next skip prerendering the subtree,
// which strips every item link out of the static HTML and hides them
// from search crawlers. State starts null so the full list prerenders.
<CategoryFilterProvider basePath="/work">
  <CategoryFilters
    categories={['Web App', 'Website', 'Mobile App']}
    totalCount={caseStudies.length}
    counts={categoryCounts}
  />

  <WorkCarousel caseStudies={caseStudies} />
</CategoryFilterProvider>