Filter row

Use a filter row when a page lets readers narrow a dataset without leaving the page. It belongs immediately before the table, list, or grouped dataset it controls, after any introductory prose, sources, or changelog.

ColourFoundations
TypographyFoundations
BreadcrumbsComponents
CardsComponents

When to use#

  • The page lets readers narrow a dataset without leaving the page; place the row immediately before the table, list, or grouped dataset it controls, after any introductory prose, sources, or changelog
  • Filter rows are interactive controls, not metadata — do not use metadata tag classes for them; filter chips change visible rows, metadata tags only describe content
  • Always use the shared partial so the spacing, button classes, count styling, and optional horizontal overflow wrapper stay consistent
  • Use a reset button when only one filtered table is shown and the page has an explicit all-state; omit it when filters are additive chips for cards, grouped lists, or sections where the unfiltered state is simply no chip selected
  • Not for jumping to sections — an anchor index moves the reader, a filter row changes what is visible
  • Current users: NANP telephony, talks archive, publications, recommendations, and recognition

Implementation#

{{ partial "data-page/filter-row.html" (dict
  "wrapClass" "overflow-x-auto pb-1"
  "class" "filter-chip-list flex-nowrap"
  "ariaLabel" "Filter by topic"
  "reset" (dict "label" (i18n "all") "count" $total "attrs" "data-table-filter-reset" "countAttrs" "data-table-visible-count" "active" true)
  "items" $filterItems
) }}

Each item provides label, count, and the data attributes required by the page behaviour:

dict "label" "Security" "count" 12 "attrs" "data-table-filter-group=\"tag\" data-table-filter-value=\"security\""
<nav class="filter-chip-list" aria-label="Filter by topic">
  <button class="filter-chip filter-chip-button tag-pill-active" type="button" data-table-filter-reset aria-pressed="true">All <span class="filter-chip-count" data-table-visible-count aria-live="polite" aria-atomic="true">34</span></button>
  <button class="filter-chip filter-chip-button" type="button" data-table-filter-group="tag" data-table-filter-value="security" aria-pressed="false">Security <span class="filter-chip-count">12</span></button>
</nav>

The partial renders markup only; behaviour comes from whichever page script recognizes the data attributes passed through attrs and navAttrs. Three behaviours exist in assets/js/site.js:

  • initTableLists (pages with a data-table-list container): chips use data-table-filter-group and data-table-filter-value, the reset uses data-table-filter-reset, the visible count uses data-table-visible-count
  • initTalksArchive (talks archive): chips use data-talks-filter-group and data-talks-filter-value, the reset uses data-filter-reset, the count uses data-talks-visible-count
  • The recommendation filter block (navAttrs set to data-recommendation-filters): chips use data-filter-group and data-filter-value — recommendations, recognition, and NANP telephony

The Hugo partial is layouts/partials/data-page/filter-row.html. It renders the <nav> and its chips — buttons with aria-pressed, or links when an item carries href — and nothing else; wiring the chips to rows is the host page’s job. The chip styling is the filter-chip-* classes in assets/css/site.css.

Options#

OptionPurpose
itemsList of chip dicts: label, count, attrs, countAttrs, and href to render a link instead of a button
resetDict for the leading all-state chip: label, count, attrs, countAttrs, active, href
ariaLabel / ariaLabelKeyAccessible label for the <nav>, literal or resolved through i18n
classClasses on the <nav> (default filter-chip-list)
navAttrsExtra attributes on the <nav>
wrapClassWhen set, wraps the <nav> in a div with these classes, usually for horizontal overflow
buttonClassChip classes (default filter-chip filter-chip-button)

Interface manifest

Kind
Component
Category
Data
Status
Implemented
Implementation
Partial:layouts/partials/data-page/filter-row.htmlCSS:assets/css/site.cssJavaScript:assets/js/site.js

Accessibility#

  • The row is a <nav> named by ariaLabel or ariaLabelKey; always pass one, because the partial renders the attribute verbatim and an omitted label ships an unnamed landmark with aria-label=""
  • Chips are native <button type="button"> elements carrying aria-pressed; the page behaviour toggles it together with the tag-pill-active class, and the reset chip stays aria-pressed="true" while no filter is selected
  • Tab moves between chips and Enter or Space toggles one — native button behaviour, with no custom key handling
  • The count in the reset chip carries aria-live="polite" and aria-atomic="true", so screen readers hear the new visible count after a filter changes it, not just see the number change; a page-specific empty-state message uses the same pattern
  • Avoid a link chip (href) for a filter that toggles in place — the partial renders links without aria-pressed, and a link announces navigation