Table Toolbar
- Stable
- WCAG 2.2 evidence
- RTL
The bar above a data table: a search field and filter chips at the start, table actions at the end.
It is a labelled group of three parts. The search field stands alone. The filters and the actions are each a React Aria
Toolbar (role="toolbar"), so a keyboard user crosses the bar in three Tab stops and moves between the buttons inside a
group with the arrow keys (mirrored in right-to-left layouts); a popup that a button opens returns focus to it on Escape
(WCAG 2.1.1, 2.4.3, 4.1.2). Buttons keep their own focus ring (2.4.7) and 32 px height (2.5.8). The bar adapts to its container, not the viewport: in a narrow container the search field fills the row and the actions wrap
below, so nothing is hidden or clipped (1.4.10). Use it for tables with more than about ten rows. Consumer duties: the names of the toolbar and of every icon button, the wording of filters, and announcing how many rows a
filter or search leaves (announceStatus).
import { TableToolbar } from "@nexera-ui/react";- 5
- examples
- 10
- props
- 0
- live controls
- 1
- platform
- 8
- WCAG criteria
- 2
- blocks use it
Try every prop. Copy the code.
Change the props and the code updates. Check light and dark, LTR and RTL, and phone width.
import { TableToolbar } from "@nexera-ui/react";
<TableToolbar />Examples 4
The same examples as Storybook, rendered live. Open Code to copy one.
Search and action
Only a search field and one action: parts that are not provided are not drawn.
import { Button, Input, TableToolbar } from "@nexera-ui/react";
import { LuSearch, LuUserPlus } from "react-icons/lu";
export function SearchAndAction() {
return (
<TableToolbar
aria-label="Employees table tools"
search={
<Input
type="search"
size="sm"
leadingIcon={<LuSearch />}
aria-label="Search employees"
placeholder="Search"
/>
}
actions={<Button leadingIcon={<LuUserPlus />}>Add employee</Button>}
/>
);
}
Clear filters
"Clear filters" appears while a filter is applied (onClearFilters) and disappears once they are cleared.
import { useState } from "react";
import { FilterChip, Input, MenuItem, TableToolbar } from "@nexera-ui/react";
import { LuSearch } from "react-icons/lu";
const departments = (
<>
<MenuItem id="eng" label="Engineering" />
<MenuItem id="design" label="Design" />
<MenuItem id="people" label="People" />
</>
);
function ClearableFilters() {
const [department, setDepartment] = useState<string | null>("eng");
return (
<TableToolbar
aria-label="Employees table tools"
search={
<Input
type="search"
size="sm"
leadingIcon={<LuSearch />}
aria-label="Search employees"
placeholder="Search"
/>
}
filters={
<FilterChip
label={department === null ? "Department" : `Department: ${department}`}
hasDropdown
selectionMode="single"
selectedKeys={department === null ? [] : [department]}
onSelectionChange={(keys) => {
const next = [...(keys === "all" ? [] : keys)][0];
setDepartment(typeof next === "string" ? next : null);
}}
>
{departments}
</FilterChip>
}
{...(department === null
? {}
: {
onClearFilters: () => {
setDepartment(null);
},
})}
/>
);
}
export function ClearFilters() {
return <ClearableFilters />;
}
Narrow container
At 20 rem the search field fills the row, the chips wrap, and the actions drop below. Nothing is hidden or clipped.
import {
Button,
FilterChip,
IconButton,
Input,
MenuItem,
TableToolbar,
type TableToolbarProps,
} from "@nexera-ui/react";
import { LuColumns3, LuDownload, LuSearch, LuSlidersHorizontal, LuUserPlus } from "react-icons/lu";
const departments = (
<>
<MenuItem id="eng" label="Engineering" />
<MenuItem id="design" label="Design" />
<MenuItem id="people" label="People" />
</>
);
function FigmaToolbar(props: Partial<TableToolbarProps>) {
return (
<TableToolbar
aria-label="Employees table tools"
search={
<Input
type="search"
size="sm"
leadingIcon={<LuSearch />}
aria-label="Search employees"
placeholder="Search"
/>
}
filters={
<>
<FilterChip label="Department: All" hasDropdown selectionMode="single">
{departments}
</FilterChip>
<FilterChip
label="Status: Active"
hasDropdown
isSelected
selectionMode="single"
defaultSelectedKeys={["active"]}
>
<MenuItem id="active" label="Active" />
<MenuItem id="leave" label="On leave" />
</FilterChip>
<FilterChip
label="Location: 2 selected"
hasDropdown
isSelected
selectionMode="multiple"
defaultSelectedKeys={["lhr", "khi"]}
>
<MenuItem id="lhr" label="Lahore" />
<MenuItem id="khi" label="Karachi" />
<MenuItem id="isb" label="Islamabad" />
</FilterChip>
</>
}
onClearFilters={() => undefined}
actions={
<>
<IconButton
variant="secondary"
aria-label="View options"
icon={<LuSlidersHorizontal />}
/>
<IconButton variant="secondary" aria-label="Columns" icon={<LuColumns3 />} />
<Button variant="secondary" leadingIcon={<LuDownload />}>
Export
</Button>
<Button leadingIcon={<LuUserPlus />}>Add employee</Button>
</>
}
{...props}
/>
);
}
export function NarrowContainer() {
return <FigmaToolbar aria-label="Employees table tools" />;
}
Right to left
Right-to-left: the search field and filters sit at the start (right), the actions at the end (left), and arrow keys mirror.
import { Button, FilterChip, IconButton, Input, MenuItem, TableToolbar } from "@nexera-ui/react";
import { LuSearch, LuSlidersHorizontal } from "react-icons/lu";
export function RightToLeft() {
return (
<TableToolbar
aria-label="Employees table tools"
search={
<Input
type="search"
size="sm"
leadingIcon={<LuSearch />}
aria-label="بحث عن موظفين"
placeholder="بحث"
/>
}
filters={
<FilterChip label="القسم" hasDropdown selectionMode="single">
<MenuItem id="eng" label="الهندسة" />
</FilterChip>
}
onClearFilters={() => undefined}
clearFiltersLabel="مسح التصفية"
actions={
<>
<IconButton
variant="secondary"
aria-label="خيارات العرض"
icon={<LuSlidersHorizontal />}
/>
<Button>إضافة موظف</Button>
</>
}
/>
);
}
Props 10
10 props shown
searchNexeraReactNodeSearch field: pass a <SearchField aria-label="Search employees" /> or an
Input. It is 260 px wide from a 480 px container up and fills narrower ones. It is not part of a toolbar (a text field
keeps its own arrow keys), so Tab reaches it separately.
filtersNexeraReactNodeFilter controls: FilterChips, usually hasDropdown. They sit in a labelled React
Aria Toolbar: Tab enters it once and the arrow keys move between the chips (mirrored in right-to-left layouts).
onClearFiltersNexera() => voidCalled by the "Clear filters" button, which shows after the filters when this is provided. Show it
only while a filter is applied, and say how many results remain (announceStatus, WCAG 4.1.3).
clearFiltersLabelNexerastringText of the "Clear filters" button. Translate it.
actionsNexeraReactNodeButtons and icon buttons at the end. They sit in a labelled React Aria Toolbar. Every icon-only
button needs its own aria-label.
filtersLabelNexerastringAccessible name of the filters toolbar. Translate it.
actionsLabelNexerastringAccessible name of the actions toolbar. Translate it.
classNameNexerastringExtra classes for the root, merged last so they win over the defaults.
aria-labelNexerastringAccessible name of the whole toolbar, for example "Employees table tools". Translate it.
Accessible name; aria-labelledby wins when both are set.
aria-labelledbyNexerastringId(s) of element(s) that name the toolbar; wins over aria-label.
Id(s) of visible element(s) that name the toolbar.
* Required. React Aria props shown are the ones most apps use; the component accepts the rest of its React Aria props too.
Accessible by default.
Built on React Aria, and covered by the WCAG 2.2 evidence generated on every build.
WCAG 2.2 evidence
5 direct · 3 supporting- 1.3.1Info and RelationshipsLevel A · tested directly
- 1.3.2Meaningful SequenceLevel A · tested directly
- 1.4.10ReflowLevel AA · supporting test
- 2.1.1KeyboardLevel A · tested directly
- 2.4.3Focus OrderLevel A · tested directly
- 2.4.7Focus VisibleLevel AA · supporting test
- 2.5.8Target Size (Minimum)Level AA · supporting test
- 4.1.2Name, Role, ValueLevel A · tested directly
Fits any width.
Nexera components respond to the space they are given. Drag the corner of the frame, or pick a width.
Styling hooks
Pass className to add Tailwind classes (merged last). State is exposed as data attributes, so you can style it with variants like data-pressed:.
data-disableddata-hovereddata-pressed
<TableToolbar className="data-disabled:opacity-90 shadow-sm" />Used in blocks
Related components
- AccordionItemOne collapsible section of an FAQ or settings page.
- AreaChartA stacked area chart: layers piled up over time with the title, range switch, actions and legend of the card.
- AvailabilityRowOne person's availability across a window of the day: an avatar with initials and the name, and a timeline with the busy periods drawn as bars.
- AvatarA person or organisation shown as a photo, initials or an icon.
- AvatarGroupA row of overlapping avatars with a "+N" chip for the rest.
- AvatarLabelAn avatar with a name and an optional subtitle beside it, for a person in a list, a header or a mention.
- BadgeShort status or category label: `Tone` x `Variant` x `Size`, with an optional dot, leading icon and remove button.
- BadgeGroupA pill that pairs a small badge with a short message, for an announcement or a status line.