Segmented Controls for View Density

Permalink to "Segmented Controls for View Density"

Data grids often offer density settings: compact rows for experts who want to see more data, comfortable or spacious rows for everyone else. The control is usually a “segmented control” — three joined buttons with icons. Accessible density control matters twice: the control itself must be operable and understandable, and the densities it produces must not break accessibility (compact rows that shrink targets below 24 pixels, or that clip text when users increase spacing).

This guide belongs to tabs & view switchers for data views.

Spec reference

Permalink to "Spec reference"
  • Radio group semantics — a set of mutually exclusive options is a radio group: native input type="radio" in a fieldset with legend, or role="radiogroup" with role="radio" and aria-checked. Arrow keys move and select; only the checked radio is a tab stop.
  • SC 1.3.1 (A) and SC 4.1.2 (A) — group label and selected state are programmatic.
  • SC 1.4.1 Use of Color (A) and SC 1.4.11 Non-text Contrast (AA) — the selected segment needs a non-colour indicator with 3:1 contrast.
  • SC 2.5.8 Target Size (Minimum) (AA) — compact density must keep interactive targets at 24×24 CSS pixels or with sufficient spacing.
  • SC 1.4.12 Text Spacing (AA) — rows must not clip text when users increase line height or letter spacing.
Icon-only toggle buttons versus a labelled radio group Comparison: icon-only buttons are unnamed and do not express mutual exclusivity; a radio group with text labels names each option and its selection. Icon-only toggle buttons versus a labelled radio group✗ Icon-only toggle buttons"Button" ×3, no namesThree tab stopsSelected shown by fill colourGroup purpose unclear✓ Labelled radio group"Compact, radio button, 1 of 3"One tab stop; arrows chooseSelected shown by fill, border and checkLegend: "Row density"

When to use — and when not to

Permalink to "When to use — and when not to"

Offer density control when the grid is used both by power users scanning many rows and by occasional users, or when users with low vision need more space. Keep the choices to two or three.

Do not make compact density the default if it pushes targets below 24 pixels or text below a comfortable size. Do not implement density by scaling with transform or zoom — that shrinks text and targets together and can break reflow.

The misapplication: a compact mode that reduces row height to 24 pixels while keeping checkbox targets at 16 pixels with no spacing, and that clips descenders when users apply a text-spacing bookmarklet.

Row height and target size by density Bar chart of row heights in pixels for spacious 52, comfortable 40, compact 32 and a too-compact 24 with 16-pixel checkboxes that fails target size. Row height and target size by densitySpacioustargets 24 px or moreComfortabletargets 24 pxCompacttargets 24 px; tight but passingToo compact16 px checkboxes, no spacing — fails 2.5.8
Compact can still pass if targets keep 24 pixels or spacing.

Annotated code example

Permalink to "Annotated code example"
<fieldset class="density">
  <legend>Row density</legend>                                       <!-- group label (SC 1.3.1) -->
  <label><input type="radio" name="density" value="compact"> <svg viewBox="0 0 20 20" aria-hidden="true">…</svg> Compact</label>
  <label><input type="radio" name="density" value="comfortable" checked> <svg viewBox="0 0 20 20" aria-hidden="true">…</svg> Comfortable</label>
  <label><input type="radio" name="density" value="spacious"> <svg viewBox="0 0 20 20" aria-hidden="true">…</svg> Spacious</label>
</fieldset>
/* Segmented look; native radios underneath (keyboard and semantics for free) */
.density input { position: absolute; opacity: 0; }
.density label { border: 1px solid var(--rule); padding: .35rem .75rem; }
.density input:checked + svg, .density label:has(input:checked) {
  background: var(--brand-weak); border-color: var(--brand); font-weight: 600;   /* fill + border + weight */
}
.density label:has(input:focus-visible) { outline: 3px solid var(--focus); outline-offset: 2px; }

/* Density via tokens, never transform: scale (SC 1.4.12, 2.5.8) */
.grid[data-density="compact"] { --row-pad: 4px; --cell-min-block: 32px; }
.grid[data-density="compact"] input[type="checkbox"] { inline-size: 24px; block-size: 24px; } /* target kept */
.grid td { padding-block: var(--row-pad); min-block-size: var(--cell-min-block); line-height: 1.5; }
document.querySelector('.density').addEventListener('change', (e) => {
  grid.dataset.density = e.target.value;
  localStorage.setItem('grid-density', e.target.value);   // persisted preference
});

Keyboard & AT behaviour

Permalink to "Keyboard & AT behaviour"
Key Action Expected announcement AT deviations
Tab into group Focus checked radio “Row density, grouping, Comfortable, radio button, checked, 2 of 3” —
Left / Right arrow Select previous / next “Compact, radio button, checked, 1 of 3” VoiceOver uses arrows too
Space Select focused (if not already) — —
Tab Leave the group Next control —
Density options and their accessibility checks Matrix of densities — spacious, comfortable, compact — against target size, text spacing tolerance and suitability for magnifier users. Density options and their accessibility checksTarget sizeText spacingMagnifier useSpaciousGenerousAmple roomRecommendedComfortable24 pxPassesGoodCompact24 px, tightTest carefullyMore panning
Every density must pass; compact just has less margin.

Integration context

Permalink to "Integration context"

Density interacts with designing focus indicators for dense grids — compact rows leave less room for focus rings. Target sizes in dense grids are tested in testing target size 2.5.8 in dense grids. Density tokens belong to the design system — see density tokens and target size in data grids.

Gotchas

Permalink to "Gotchas"

Hidden radios that cannot be focused. display: none on the inputs removes them from keyboard access. Hide them visually with opacity or clipping, not display.

Density resetting on navigation. Users who need spacious rows should not have to re-select it on every page; persist per user.

Charts and density. If density also affects charts or KPI cards, make sure text sizes stay readable at compact settings.

Framework notes

Permalink to "Framework notes"

MUI X Data Grid has density (compact, standard, comfortable) and a built-in toolbar selector; check its row heights against target size in compact. AG Grid sets density through theme parameters (rowHeight, spacing). Angular Material supports density via its theming (mat.theme density scale, down to -5); test negative densities for target size. For the control itself, React Aria RadioGroup or a native fieldset are the simplest accessible options.

// MUI X: control density from your own accessible radio group
<DataGrid density={density} … />

Worked example

Permalink to "Worked example"

A logistics grid offered “Compact” as default to fit 40 rows on screen. Users with low vision and tremor reported mis-clicks on row checkboxes, and an audit measured 16-pixel targets with 2 pixels of spacing. The team made “Comfortable” the default, kept compact as an option with 24-pixel checkbox targets achieved by padding the label, and replaced the icon-only density buttons with a labelled radio group. Power users still choose compact; nobody is stuck with it.

Testing checklist

Permalink to "Testing checklist"

Design system notes

Permalink to "Design system notes"

Define density as a token set (row padding, minimum cell height, control sizes) with every density verified against 2.5.8 and 1.4.12, and expose a shared DensityControl that writes a user preference all grids read.

FAQ

Permalink to "FAQ"
Is a segmented control a tablist or a radio group?

If it switches content panels, a tablist. If it sets a value (density, units, period), a radio group.

Do density icons need text?

The accessible name needs text; showing the text visually is better still, especially for voice control users.

Can compact density fail WCAG?

Yes, if it shrinks targets below 24 pixels without spacing, or clips text under text-spacing overrides.

Should density changes be announced?

The radio’s checked state is announced already. No extra live message is needed.

Should density follow the operating system's settings?

There is no standard density preference, but respecting browser zoom and text size is essential; never fix row heights in pixels that ignore font size.

Should density apply to every grid or per grid?

A global preference with per-grid overrides works best. Users who need spacious rows want it everywhere; a power user may want one dense grid for a specific task.

Permalink to "Related"

← Back to Tabs & View Switchers for Data Views