Accessible names
Dockable renders no text. Every accessible name comes from you, as the children, an aria-label, or a render function that reads the part's state.
Dockable renders no text of its own: no "Close", no "Maximize", no "Resize". Every accessible name comes from you. There are three sources.
1. Children
A primitive renders its children, and for most parts the children are the name. A
Dockable.Tab's accessible name is its content:
<Dockable.Tab node={tab}>{tab.label}</Dockable.Tab>The model keeps each tab's name as its label, the raw string your app gave it, and never renders
it: the tab's children are still the name, so {tab.label} (or its translation) is what you put
there. An icon-only tab needs a name another way: aria-label={tab.label} on the tab, or visually
hidden text inside it.
The panel is labelled by its tab automatically: Dockable.Panel sets
aria-labelledby to the tab button's id, and the tab sets aria-controls to the panel's id.
2. aria-label (and other props)
Every primitive forwards arbitrary props, so you name elements the usual way. The tab strip is
a role="tablist" and should be named:
<Dockable.TabList<Types> aria-label={tabset.data?.name ?? "Tabs"}>A tabset's name is your data too: declare it in the registry (tabset: { name?: string }) and
set it in the layout JSON ("data": { "name": "Editors" }). TabList reads nothing by itself;
pass it as above.
Buttons you add (close, maximize, pop out) are your own elements: give each one a name.
<button type="button" aria-label={`Close ${tab.label}`} onClick={close}>
<XIcon aria-hidden="true" />
</button>The parts the primitives render
A few elements are rendered by the primitives themselves: the splitters, the overflow trigger and the pop out trigger. They have no name of their own either, and you name them the same way.
A Dockable.Row inserts its splitters, so name them through renderSplitter. Pass the same
function to every Row you render, nested rows included:
function renderSplitter(props: RowSplitterProps<Types>) {
return <Dockable.Splitter {...props} aria-label="Resize" />;
}
<Dockable.Row<Types> renderSplitter={renderSplitter}>{renderNode}</Dockable.Row>A border's splitter is inserted by Dockable.BorderContent, which has its own renderSplitter:
<Dockable.BorderContent
node={border}
renderSplitter={(node) => <Dockable.Splitter node={node} aria-label="Resize" />}
/>The overflow trigger and the pop out trigger take an aria-label like any button:
<Dockable.TabOverflowTrigger aria-label="More tabs">
<ChevronsRightIcon aria-hidden="true" />
</Dockable.TabOverflowTrigger>3. render, for a name that follows the state
A render function receives the part's state with its props, so a name can depend on it.
Dockable.PopoutTrigger does two things: it pops a tab out, and in a window it docks the tab
back. Name each one from mode ("popout" or "dock"):
<Dockable.PopoutTrigger
render={(props, state) => (
<button {...props} aria-label={state.mode === "dock" ? "Dock back" : "Pop out"} />
)}
>
<ExternalLinkIcon aria-hidden="true" />
</Dockable.PopoutTrigger>No fallback, and translation
An element you give no name has no name: nothing falls back to English.
Translating is yours as well. These are ordinary props, so pass your i18n library's strings
(aria-label={t("resize")}) like any other text of your app.
What the primitives set for you
| part | role and ARIA |
|---|---|
TabList | role="tablist", aria-orientation, aria-keyshortcuts (tabset focus bindings) |
Tab | role="tab", aria-selected, aria-controls, aria-keyshortcuts, a roving tabIndex |
Panel | role="tabpanel", aria-labelledby, tabIndex={-1} |
Splitter | role="separator", aria-orientation, aria-valuenow/min/max/text, tabIndex={0} (the name is yours) |
PopoutTrigger, TabOverflowTrigger | a <button type="button"> (the name is yours) |
DropIndicator | aria-hidden |
See Keyboard and accessibility for the key bindings.