X-02 · Composites · App components
All elements · Search on the Composites page
Last checked 2026-10-22 · Awaiting approval · Accessibility tests
Approve Search
Approving as
The render-props search host, and the parent of every result view. It owns the toolbar (query, filters,
view switch, saved views), the optional filter rail, the results region and the footer with pagination;
data-view picks which of its five child views renders inside __results — list,
table, gallery, kanban or two-pane. The chrome never changes with the view. DataGrid (X-03), Kanban
(X-04) and Inbox (X-12) are the same idioms used standalone, and are documented separately.
Every result resolves to an object. In list, table, gallery and kanban a result carries
data-ds-open-window and opens that object as a window (the non-modal
dialog.qf-overlay[data-window] of X-01, bounded by the shell's
.qf-content[data-window-area]); the row's primary cell, and each card's title, is a
focusable __open control so the keyboard reaches it. In two-pane a result does not open a
window: it takes aria-current="true" and the object swaps into the right pane.
The full-screen page is search.html,
inside the app shell. The toolbar follows the app's: the query (Clear, then Search on Enter), an
icon-only Add filter that takes the input fields' fill while filters apply
([data-filtered]), a sort menu, then the layout switch; there is no filter rail, no result
count and no saved views (they live in the navigation). Each filter is a .qf-filter-row in
.qf-search__filter-rows under the toolbar, in the same bordered band: field · value · Clear
· Remove, and the last row ends with Add filter. The table view has it so far; the other views still
show the rail. The specimens below are copied from search.html by
bun scripts/design-pages/build-composite-pages.mjs — edit that file, not this one;
--check fails when they drift.
| Axis | Values |
|---|---|
.qf-search[data-view] |
list · table · gallery · kanban · two-pane (the five child views; mirrors the segmented view
switcher and the __more menu)
|
.qf-search[data-filters] |
open · closed (rail visibility; closed hides __filters) |
__toolbar · __query · __filters ·
__results · __footer · __active-filters ·
__count
|
regions |
__gallery · __list · __amount |
gallery is a responsive card grid, list is full-width stacked cards with columns aligned across
rows (owner hides below 40rem), __amount is the right-aligned tabular figure
|
.qf-kanban inside __results |
the kanban child view: the X-04 board, grouped by status, with the search toolbar, filter rail and footer unchanged around it |
__pane-list · __row · __row-icon ·
__row-title · __row-subtitle · __row-amount ·
__row-status · __detail · __detail-body ·
__heading
|
the two-pane child view: results left, the selected object right.
__row[aria-current='true'] is the selection (tinted, primary rule, link-coloured
title); __detail is a .qf-object with its own scrolling body
|
__open · [data-ds-open-window] |
every result returns an object: __open is the focusable control in a row's primary
cell or a card's title, and the result surface carries the window opener
|
.qf-saved-views |
scrollable chip row of saved views |
__more · __filters-label |
narrow toolbar (container query): count and Filters label hide below 54rem, saved views move
into the __more menu below 46rem, the five-button view switcher below 30rem — the
__more menu lists all five views, so none is ever unreachable
|
| Select | Actions | |||||
|---|---|---|---|---|---|---|
| 2h ago | jdJane Doe | Approved | £4,250.00 | |||
| Yesterday | akAlex Kim | In review | £980.00 | |||
| 3 days ago | mrMaya Rossi | Open | £12,080.10 | |||
| Last week | tsTom Sato | Rejected | £2,100.00 | |||
| Last week | jdJane Doe | Approved | £640.00 | |||
| 2 weeks ago | akAlex Kim | Open | £7,315.40 | |||
| 3 weeks ago | mrMaya Rossi | In review | £1,560.00 | |||
| Last month | tsTom Sato | Approved | £3,020.75 |
Guidance
When to use
- Use it for any screen where users query a collection of objects, filter it and act on the results.
- Use it when one result set must be viewable as a list, table, gallery, kanban board or two-pane reader without the surrounding controls changing.
- Use the filter rail when users narrow results by several properties at once.
- Use saved views when users return to the same query and filters often.
When to consider something else
- Use the Inbox when every result set is a canned task search and there is no filter rail. See Inbox.
- Use a DataGrid on its own for a fixed table that needs column picking but no query or view switch. See DataGrid.
- Use a Kanban board on its own when the screen is only a status board. See Kanban.
- Use the app bar search in the Shell for a global jump-to query rather than a full results screen. See App shell.
Usability
-
Keep the toolbar, filter rail and footer the same in every
data-view; only__resultschanges. -
Show the result count in
__countand keep active filters visible as removable chips in__active-filters. -
Give every result a focusable
__opencontrol (the row's primary cell or the card title) that opens its object. -
In two-pane, select with
aria-current="true"and show the object in the right pane instead of opening a window. -
Rely on the
__moremenu at narrow widths; it lists the saved views and all five layouts so none becomes unreachable. - Give every search screen one toolbar in one order: [select all] · query · save-search bookmark · Filters · sort · scope · spacer · export · refresh · auto-refresh · column settings · list width (two-pane only) · layout and More. Leave out what a screen cannot offer; keep the rest in order.
-
Make the bookmark open a window (Save search, or Edit saved search: Delete… · Cancel · Save as new
· Update), not a menu; mark it
data-selectedwith thebookmarkglyph while a saved search is on screen. - Keep the Column settings gear in the toolbar, left of the layout switch, in every layout; disable it, titled "No settings for this layout", where the layout has none.
-
Do not make a whole-row click the only way to open a result; keep the
__openbutton. - Do not change the filters or toolbar when the user switches views.
- Do not mix window and pane behavior in one view.
- Do not show filters only as color or icon; each active filter chip names its property and value.
Accessibility
Keyboard
| Keys | Action |
|---|---|
| Tab | Moves from the query field through Filters, saved views, the view switcher and More, then into the filter rail, the results and pagination. |
| Enter or Space |
On a result's __open control, opens the object in a window; on a two-pane row,
selects it and shows it in the right pane.
|
| Enter or Space |
On a view button, switches the view; the pressed button reports
aria-pressed="true".
|
| Arrow Down / Arrow Up | In the More menu, moves between saved views and layouts; Escape closes it and returns focus to the button. |
| Escape | In an opened object window, closes it and returns focus to the result's open control. |
Roles and names
-
The toolbar is
role="toolbar"; the binding must give it anaria-labelnaming the search, as the Inbox does. -
The query input is labeled
Search query; the Filters toggle and view buttons usearia-pressed; icon-only view buttons havearia-labelandtitle. -
The filter rail is an
<aside>; the binding must label it, for examplearia-label="Filters". -
Table view headers carry
aria-sorton the sorted<th>with a.qf-table__sortbutton; row checkboxes are labeledSelect <name>. -
Active-filter remove buttons must name what they remove (
Remove Type: Invoice), not justRemove. -
Pagination is
<nav class="qf-pagination" aria-label="Pagination">witharia-current="page"on the current page. -
The two-pane split is a
.qf-search__resizer(a.qf-resizable__handle,role="separator",aria-controlsthe list,aria-valuenow/min/maxin pixels); the app sets--qf-search-list-widthanddata-list-widthon.qf-search__two-pane. The toolbar's Narrower · Wider · Reset group is the single-pointer alternative (2.5.7).
Focus
- After a view switch, focus stays on the view control; the results region must not take focus.
- In two-pane, selecting a row keeps focus on the row; users Tab into the detail pane.
- After a filter change, keep focus on the filter control and update the count in place.
Other requirements
- The binding must announce result-count changes after a query or filter change through a polite live region (4.1.3).
- Status chips carry text, so status is not shown by color alone (1.4.1).
- The toolbar collapses into the More menu by container width; the screen must still reflow at 320 CSS pixels (1.4.10).
- Icon-only view and chip-remove buttons must meet 24 by 24 CSS pixels (2.5.8).
Test your implementation too. This guidance covers the element as specified here. How a screen composes it (labels, order, announcements) still needs its own keyboard and screen reader check.
Accessibility tests
Passed the gate (run 358b316 on 2026-10-22): no finding in any theme. Awaiting a person’s approval of the checklist below.
| Area | Automated | A person checks |
|---|---|---|
| General | Passed | Color is never the only cue, text alternatives say the same thing as the graphic, and the element reads the same in the light and the dark theme. |
| Zoom | Passed | At 200% zoom, at 320 CSS pixels wide and with the WCAG text-spacing overrides, nothing is lost, clipped or overlapping. |
| Keyboard | Passed | Every row of the Keyboard table works, focus is always visible and never trapped, and the tab order follows the reading order. (5 rows) |
| Screen reader | Passed | With NVDA or JAWS on Windows and VoiceOver on macOS, names, roles and states are announced as Roles and names says, and changes are announced once. (7 items) |
Approving. When the checklist passes on the checked run, record it with
bun run a11y:approve -- search --by "Your name" and commit
a11y/approvals.json with the rebuilt pages.
All test status
Package
Consumers link one stylesheet, css/classic/index.css, which includes every element. The
files below are where this element is defined.
| Source |
css/classic/composites/data.csscss/classic/composites/inbox-detail.csscss/classic/composites/inbox-list.csscss/classic/composites/inbox.css
|
|---|---|
| Specimens also use | Avatar · Box · Button · ButtonGroup · Card · Chip · Control · Divider · EmptyState · Field · Icon · Kanban · Menu · ObjectDisplay · Overlay · Pagination · Select · Table · Tabs · Text · Toggle · Toolbar |
| Sprites | sprites/icons.svg |
| React | Not yet available: the React binding has not started. |
Latest updates
| Date | Version | Breaking | Description |
|---|---|---|---|
| 2026-10-20 | 0.1 | Yes |
Classic is the only skin: css/modern/ and js/skin.js are removed, so consumers link
css/classic/index.css directly (no data-skin-sheet swap) and keep
data-skin="classic" on <html> for scripts. The skin switch is gone from the
site's app bar, the shell's identity menu and the Office review strip; theme stays. The per-skin
composite pages are single pages: composites/shell.html, inbox.html, overlay-system.html and
search.html replace the *.classic.html and *.modern.html pairs
|
| 2026-10-20 | 0.1 | No | Search: one toolbar on every screen, in one order: query · save-search bookmark (bookmark-plus, or bookmark with data-selected while a saved search is on screen; it opens a window) · Filters (the C-01 pressed disclosure, with its badge) · sort · spacer · Column settings (the gear, left of the layout switch, disabled where a layout has none) · list width (Narrower · Wider · Reset, two-pane only) · layout and More (layouts only). The toolbar's saved-view chips, result count and More ▸ Saved views are gone from the composite. The two-pane split is resizable: .qf-search__resizer, a C-28 Resizable handle in a gap widened to --qf-space-6, with --qf-search-list-width and data-list-width on .qf-search__two-pane and data-resizing while dragged; stacked below 44rem the handle goes (composites/search-split.css), classic skin |