Skip to content
Honor Society®Brand system

v0.6.4

Forms and controls

Filter

The filter panel above a catalog: one row of selects on wide screens, a Filters disclosure on phones, one GET form.

Block
.hs-filter
CSS
components/filter.css (in /brand.css)
JS
components/filter.js: load it only on a page with this component
Status
Decided
Decided in
Directories round
Markdown
/components/filter.md

What it is

A GET form that narrows a catalog by two to four fixed dimensions. The fields sit in a details panel. Below 48rem the panel is a closed "Filters" disclosure with the active count. From 48rem filter.js opens it, CSS hides the summary, and the selects sit in one row above the results. A select submits the form on change; the Apply button stays for the no-JS page.

When to use

  • A catalog with two or more dimensions: the scholarship directory (who it is for, type, offered by), the benefits list (category, membership needed).
  • Any list long enough to need a pager.

When not to use

  • One dimension over a short list: use Filter chips, as the article topics.
  • A keyword alone: use Search.
  • Sibling pages at their own URLs: use Tabs.

Rules

  • Markup: form.hs-filter[method="get"] > details.hs-filter__panel[data-hs-filter] > summary.hs-filter__summary + div.hs-filter__fields.
  • The summary reads Filters, then span.hs-filter__count with the number of active filters. Leave the count out when no filter is set.
  • Each field is div.hs-field.hs-filter__field with label.hs-label and select.hs-control. The first option is the empty value: "Any type", "Anyone".
  • The last item in the fields is div.hs-filter__actions: button.hs-button.hs-button--navy.hs-button--sm "Apply filters", then a.hs-form-link.hs-filter__clear "Clear all" to the clean URL. Print Clear all only when a filter is set. It shows only while the panel is a drawer (under 48rem, or without filter.js): filter.js adds is-inline to the open panel on wide screens and filter.css hides the link there, because the Results bar carries Clear all.
  • Two to four selects. A dimension with more than about 12 values needs a curated map in code, not every tag.
  • Option labels are visitor words ("Graduate", "Study abroad"), never internal tags or school names.
  • The form keeps the keyword (q) as a hidden field, so a filter change does not drop the search.
  • Filtering runs on the server: the URL holds the state and every option is a real link target. A short list (about 40 items) may filter in the page on the same markup.
  • Load filter.js on the page. The sort select in the Results bar joins this form with form="FORM-ID".

Accessibility

  • Each select has a visible label for. The summary is the native disclosure button, 48px tall.
  • The count is part of the summary text, so a screen reader hears "Filters 2".
  • Without filter.js the summary stays visible at every width, so the panel always opens; the Apply button submits.
  • Before an auto-submit, filter.js disables every empty field of the form and of its joined controls, so the URL holds set filters only (?for=undergraduate&sort=name). The no-JS Apply button still submits all fields.
  • Auto-submit on change reloads the page with the new list; the Results bar count, a role="status" line, states the new total.
  • The chevron turns with a 200ms transition; reduced motion removes it.
  • Summary navy 12.4:1, count white on navy 12.4:1, labels ink 13.0:1 on white.

Variants

Filter panel

Narrow the window under 768px to see the closed disclosure. Needs filter.js.

Preview on soft

Filters
<form class="hs-filter" id="catalog-filter" method="get" action="/scholarship-directory">
  <details class="hs-filter__panel" data-hs-filter>
    <summary class="hs-filter__summary">Filters</summary>
    <div class="hs-filter__fields">
      <div class="hs-field hs-filter__field">
        <label class="hs-label" for="f-for">Who it&rsquo;s for</label>
        <select class="hs-control" id="f-for" name="for">
          <option value="">Anyone</option>
          <option value="undergraduate">Undergraduate</option>
          <option value="graduate">Graduate</option>
          <option value="alumni">Alumni</option>
          <option value="international">International</option>
          <option value="multicultural">Multicultural</option>
        </select>
      </div>
      <div class="hs-field hs-filter__field">
        <label class="hs-label" for="f-type">Type</label>
        <select class="hs-control" id="f-type" name="type">
          <option value="">Any type</option>
          <option value="scholarship">Scholarship</option>
          <option value="grant">Grant</option>
          <option value="fellowship">Fellowship</option>
          <option value="internship">Internship</option>
          <option value="study-abroad">Study abroad</option>
        </select>
      </div>
      <div class="hs-field hs-filter__field">
        <label class="hs-label" for="f-by">Offered by</label>
        <select class="hs-control" id="f-by" name="by">
          <option value="">Any provider</option>
          <option value="honor-society">Honor Society&reg;</option>
          <option value="other">Other providers</option>
        </select>
      </div>
      <div class="hs-filter__actions">
        <button class="hs-button hs-button--navy hs-button--sm" type="submit">Apply filters</button>
      </div>
    </div>
  </details>
</form>

With active filters

Two filters set: the summary shows the count, the selects keep their values, and Clear all returns to the clean URL.

Preview on soft

Filters 2
Clear all
<form class="hs-filter" id="catalog-filter-2" method="get" action="/scholarship-directory">
  <input type="hidden" name="q" value="leadership">
  <details class="hs-filter__panel" data-hs-filter>
    <summary class="hs-filter__summary">Filters <span class="hs-filter__count">2</span></summary>
    <div class="hs-filter__fields">
      <div class="hs-field hs-filter__field">
        <label class="hs-label" for="f2-for">Who it&rsquo;s for</label>
        <select class="hs-control" id="f2-for" name="for">
          <option value="">Anyone</option>
          <option value="undergraduate">Undergraduate</option>
          <option value="graduate" selected>Graduate</option>
        </select>
      </div>
      <div class="hs-field hs-filter__field">
        <label class="hs-label" for="f2-type">Type</label>
        <select class="hs-control" id="f2-type" name="type">
          <option value="">Any type</option>
          <option value="scholarship">Scholarship</option>
          <option value="fellowship" selected>Fellowship</option>
        </select>
      </div>
      <div class="hs-field hs-filter__field">
        <label class="hs-label" for="f2-by">Offered by</label>
        <select class="hs-control" id="f2-by" name="by">
          <option value="">Any provider</option>
          <option value="honor-society">Honor Society&reg;</option>
          <option value="other">Other providers</option>
        </select>
      </div>
      <div class="hs-filter__actions">
        <button class="hs-button hs-button--navy hs-button--sm" type="submit">Apply filters</button>
        <a class="hs-form-link hs-filter__clear" href="/scholarship-directory">Clear all</a>
      </div>
    </div>
  </details>
</form>