# Range filters use accessible input widgets (not freeform text)

- **Pattern:** `ab-001061` (`directory-search-discovery.facets-filters.range-filter-a11y`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001061
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001061)

## Why it matters

Range filters rendered as freeform text inputs (`<input type="text">`) violate WCAG 2.2 SC 1.3.5 (Identify Input Purpose) and SC 4.1.2 (Name, Role, Value) — screen readers cannot convey the expected value type, and users relying on step controls or browser autofill get nothing. Beyond accessibility, freeform text introduces validation surface area: users enter "10-50", "$10 to $50", or "ten dollars" and the backend must parse all variants or return unexpected results. Semantic number or range inputs constrain input to a machine-readable format without custom parsing.

## Severity rationale

Low because the failure degrades usability and assistive technology access without directly exposing data or enabling exploitation.

## Remediation

Replace freeform text range inputs with paired `<input type="number">` elements, each carrying an explicit label and `aria-label`. In `components/PriceFilter.tsx`:

```tsx
<label htmlFor="price-min">Minimum price</label>
<input
  id="price-min"
  type="number"
  value={min}
  onChange={e => setMin(e.target.value)}
  aria-label="Minimum price"
/>
<label htmlFor="price-max">Maximum price</label>
<input
  id="price-max"
  type="number"
  value={max}
  onChange={e => setMax(e.target.value)}
  aria-label="Maximum price"
/>
```

Alternatively, use shadcn/ui's Slider component, which ships with full ARIA support out of the box.

## Detection

- **ID:** `range-filter-a11y`
- **Severity:** `low`
- **What to look for:** Enumerate all relevant files and Identify any range filters (e.g., price range, distance, rating). Check whether they use accessible HTML input widgets: `<input type="range">` (slider), `<input type="number">` (number inputs), or a custom accessible component with ARIA attributes. Avoid freeform text inputs for ranges where possible.
- **Pass criteria:** Range filters use `<input type="range">` (slider), `<input type="number">` (min/max inputs), or an accessible custom component with proper ARIA labels and semantic HTML.
- **Fail criteria:** Range filters are freeform text inputs (e.g., `<input type="text">` with placeholder "e.g., 10-50") or have no accessibility labels/ARIA attributes.
- **Skip (N/A) when:** Directory has fewer than 3 filterable attributes (see Global N/A Rule), or no range filters exist (only categorical filters).
- **Detail on fail:** `"Price range filter uses a freeform text input. Should use <input type='range'> or separate number inputs for min/max."` or `"Range input lacks aria-label. Cannot be identified by screen readers."`
- **Remediation:** Implement range filters with appropriate HTML and ARIA:

  ```tsx
  // components/PriceFilter.tsx
  import { useState } from 'react'

  export function PriceFilter({ onFilterChange }) {
    const [min, setMin] = useState(0)
    const [max, setMax] = useState(100)

    return (
      <div>
        <label htmlFor="price-min">Minimum price</label>
        <input
          id="price-min"
          type="number"
          value={min}
          onChange={e => {
            setMin(e.target.value)
            onFilterChange({ min: e.target.value, max })
          }}
          aria-label="Minimum price"
        />

        <label htmlFor="price-max">Maximum price</label>
        <input
          id="price-max"
          type="number"
          value={max}
          onChange={e => {
            setMax(e.target.value)
            onFilterChange({ min, max: e.target.value })
          }}
          aria-label="Maximum price"
        />
      </div>
    )
  }
  ```

  Or use a slider component from an accessible UI library like shadcn/ui or Radix UI.

## External references

- wcag 1.3.5
- wcag 4.1.2

Taxons: accessibility

HTML version: https://auditbuffet.com/patterns/ab-001061
