Saved Views Menus for Data Grids

Permalink to "Saved Views Menus for Data Grids"

Saved views let users name a combination of filters, sort, visible columns and density — “My overdue invoices”, “Q3 by region” — and return to it in one step. For keyboard and screen reader users they are especially valuable, because rebuilding a complex filter by hand is slow. But the typical implementation — a dropdown of names, a tiny “modified” dot, and a save icon — hides most of what users need to know: which view is active, whether it has unsaved changes, and what switching views actually did to the grid.

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

Spec reference

Permalink to "Spec reference"
  • Menu button pattern — a button with aria-haspopup opening a menu (for actions) or a listbox (for choosing a value). Saved views are usually a choice, so a listbox (or a menu with menuitemradio) fits.
  • menuitemradio with aria-checked — marks the active view inside a menu.
  • SC 4.1.2 Name, Role, Value (A) — the button’s name includes the current view; the active item is marked checked.
  • SC 4.1.3 Status Messages (AA) — switching views changes the grid substantially; announce the outcome.
  • SC 1.4.1 Use of Color (A) — an unsaved-changes dot must have a text equivalent.
  • SC 3.3.2 Labels or Instructions (A) — the save dialog’s fields are labelled.
Structure of a saved views menu Hierarchy: the view button showing the current view and its modified state; a menu with personal views, shared views and actions — Save, Save as, Manage views. Structure of a saved views menuView: My overdue invoices (modified)menu buttonMy viewsgroupMy overdue invoicesmenuitemradio, checkedLarge accountsmenuitemradioShared viewsgroupFinance — month endmenuitemradioActionsseparator then itemsSave changesmenuitemSave as new view…opens dialogManage views…opens dialog
Choices and actions are grouped separately.

When to use — and when not to

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

Offer saved views when users repeatedly configure the same grid state — triage queues, reports, admin lists. Combine them with URL state so a view can also be bookmarked or shared.

Do not auto-save every change into the current view; users lose the original. Show “modified” and let them choose Save or Save as. Do not hide the current view’s name behind an icon.

The misapplication: switching views silently. The grid’s rows, columns and sort all change, and a screen reader user hears only the menu closing.

Silent switch versus announced switch Comparison: a silent view switch changes columns and rows without feedback; an announced switch states the view, the row count and the key changes. Silent switch versus announced switch✗ Silent switchMenu closes; nothing saidColumns change unnoticedModified state shown by a dotSave icon only✓ Announced switch"View: Large accounts. 42 rows. Sorted byAmount, descending.""3 columns hidden""(modified)" in the button name"Save changes to My overdue invoices"

Annotated code example

Permalink to "Annotated code example"
<!-- Current view and modified state in the name (SC 4.1.2, 1.4.1) -->
<button type="button" id="views-btn" aria-haspopup="menu" aria-expanded="false" aria-controls="views-menu">
  View: My overdue invoices <span class="modified">(modified)</span>
</button>
<div role="menu" id="views-menu" aria-labelledby="views-btn" hidden>
  <div role="group" aria-label="My views">
    <div role="menuitemradio" aria-checked="true" tabindex="-1">My overdue invoices</div>
    <div role="menuitemradio" aria-checked="false" tabindex="-1">Large accounts</div>
  </div>
  <div role="group" aria-label="Shared views">
    <div role="menuitemradio" aria-checked="false" tabindex="-1">Finance — month end</div>
  </div>
  <div role="separator"></div>
  <div role="menuitem" tabindex="-1">Save changes to My overdue invoices</div>
  <div role="menuitem" tabindex="-1" aria-haspopup="dialog">Save as new view…</div>
  <div role="menuitem" tabindex="-1" aria-haspopup="dialog">Manage views…</div>
</div>
<p id="grid-status" role="status" class="visually-hidden"></p>
async function switchView(view) {
  const before = gridState();
  await grid.applyState(view.state);                      // filters, sort, columns, density
  const after = gridState();
  closeMenu(); viewsBtn.focus();                          // stay on the button (SC 2.4.3)
  status.textContent = [                                   // what changed (SC 4.1.3)
    `View: ${view.name}.`, `${after.rowCount} rows.`,
    after.sort !== before.sort ? `Sorted by ${describeSort(after.sort)}.` : '',
    columnDelta(before, after),                           // e.g. "3 columns hidden."
  ].filter(Boolean).join(' ');
}

Keyboard & AT behaviour

Permalink to "Keyboard & AT behaviour"
Key Action Expected announcement AT deviations
Enter on view button Open menu “View: My overdue invoices (modified), menu, My overdue invoices, checked” —
Arrows Move through views and actions “Large accounts, radio menu item, not checked” Group labels read on entering a group in NVDA
Enter on a view Switch; menu closes “View: Large accounts. 42 rows. Sorted by Amount, descending.” —
Enter on Save as Dialog with “View name” field Dialog name and field —
Escape Close menu Button name —
From choosing a view to hearing the result Chain: the user picks a view; the grid applies filters, sort, columns and density; the app compares before and after; one status message summarises the change; focus stays on the view button. From choosing a view to hearing the resultPick view"Large accounts"Grid updatesfilters, sort,columns, densityComparerows 240 to 42; sortchanged; 3 columnshiddenStatus"View: Largeaccounts. 42 rows.Sorted by Amount…"apply statediffannounce
The summary is computed from the actual change, not from the view's name alone.

Integration context

Permalink to "Integration context"

The menu follows row action menus with the menu button pattern. Filter state inside a view uses the same descriptions as applied filter chips and removal. Column visibility changes are those from column visibility menus and column choosers. View announcements in general are covered in announcing view changes in data apps.

Gotchas

Permalink to "Gotchas"

Long view names. Truncated names in the button lose the distinguishing part (“Finance — month end — EU…”). Keep the full name in the accessible name and show a tooltip on focus for the visual.

Shared views that change. If a colleague edits a shared view, the user’s current grid may no longer match. Indicate “updated by Maria at 10:42” and offer reload rather than switching silently.

Default view. Mark which view loads by default (“default”) in text, and let users change it from Manage views.

Framework notes

Permalink to "Framework notes"

AG Grid exposes getState() and setState() (v31+) covering filters, sort, columns and more — a natural base for saved views. MUI X provides apiRef.current.exportState() and restoreState(). TanStack Table state is plain objects you can persist. In all cases, compute the announcement from before and after state rather than trusting the view definition, because some state (like columns that no longer exist) may not apply.

// AG Grid
const before = api.getState();
api.setState(view.state);
announce(diffStates(before, api.getState()));

Worked example

Permalink to "Worked example"

A support team’s queue offered twelve shared views in an icon-only dropdown. A screen reader user could not tell which view was active, switching produced no feedback, and “modified” was a blue dot. The rebuild put “View: Urgent — EMEA (modified)” in the button text, split personal and shared views into labelled groups with menuitemradio, announced each switch with the row count and key changes, and moved Save, Save as and Manage into the same menu after a separator. The user now switches between four views a dozen times a day and always knows where she is.

Testing checklist

Permalink to "Testing checklist"

Design system notes

Permalink to "Design system notes"

Make saved views a grid capability with a serialisable state contract, a standard menu, a save dialog and a diff-based announcer. Teams supply storage; the component guarantees names, states and messages.

FAQ

Permalink to "FAQ"
Menu or listbox for saved views?

A menu with menuitemradio items works well when actions (Save, Manage) live in the same popup. A listbox suits a pure choice. Either is fine if roles and checked state are correct.

Should switching views move focus to the grid?

Usually not; users often switch several times to compare. Keep focus on the button and announce the change.

How should unsaved changes be shown?

In text in the button’s name — “(modified)” — and, optionally, a visual dot. Never the dot alone.

Should views be in the URL?

Yes, at least the view ID and any unsaved modifications, so Back and shared links work.

What if a view references a column that was removed?

Apply what you can and say what you could not: “View: Q3 by region. 1 column in this view is no longer available.”

Permalink to "Related"

← Back to Tabs & View Switchers for Data Views