Documentation
Getting started

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

partrole and ARIA
TabListrole="tablist", aria-orientation, aria-keyshortcuts (tabset focus bindings)
Tabrole="tab", aria-selected, aria-controls, aria-keyshortcuts, a roving tabIndex
Panelrole="tabpanel", aria-labelledby, tabIndex={-1}
Splitterrole="separator", aria-orientation, aria-valuenow/min/max/text, tabIndex={0} (the name is yours)
PopoutTrigger, TabOverflowTriggera <button type="button"> (the name is yours)
DropIndicatoraria-hidden

See Keyboard and accessibility for the key bindings.