Summary
Allow consumers to set an accessible name on the tree by accepting aria-label
and aria-labelledby as props on <Tree>, and forwarding them onto the
internal role="tree" element rendered by DefaultContainer.
Background
Per the W3C Treeview pattern,
the element with role="tree" should have an accessible name supplied via
aria-label or aria-labelledby. Today react-arborist (3.4.3) does not
expose any prop to do this — TreeProps<T> has no aria-label /
aria-labelledby fields, and DefaultContainer renders
<div role="tree" …> with hardcoded attributes that don't accept extra props.
The same gap exists at the row level: each row gets role="treeitem" plus
aria-level / aria-selected / aria-expanded (great), but no
aria-label / aria-labelledby. Consumers who do not control the row's
visible text DOM end up with treeitems that have no accessible name. Note that
this is workable today via a custom renderRow (which receives node and
attrs), but the tree-container case has no equivalent escape hatch.
Current workaround (what we're forced to do)
Because there is no public API:
// Forward aria-label onto the role="tree" element via DOM mutation
const treeWrapperRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const tree = treeWrapperRef.current?.querySelector('[role="tree"]')
if (tree && ariaLabel) tree.setAttribute('aria-label', ariaLabel)
}, [ariaLabel])
return (
<div ref={treeWrapperRef}>
<Tree {...} />
</div>
)
This works but:
- is a side-effect that mutates DOM produced by another component,
- can race with arborist's own renders,
- is invisible to React DevTools and to anyone reading the JSX,
- doesn't compose with SSR or strict-mode double-invocation cleanly.
`renderContainer` doesn't help — `DefaultContainer` is not exported, so
overriding it means re-implementing ~230 LOC of keyboard handling
(Backspace delete, Tab/Shift+Tab focus traversal, arrow keys, type-ahead
search, etc.).
Proposed API
<Tree
data={data}
aria-label="File explorer"
// or
aria-labelledby="file-explorer-heading"
>
…
</Tree>
Internally, `DefaultContainer` would spread the relevant ARIA attributes onto
its `role="tree"` div:
// DefaultContainer
return (
<div
role="tree"
aria-label={tree.props['aria-label']}
aria-labelledby={tree.props['aria-labelledby']}
aria-multiselectable={!tree.props.disableMultiSelection || undefined}
style={{ height: tree.height, width: tree.width, … }}
// … existing handlers
>
…
</div>
)
(`aria-multiselectable` would be a useful bonus while we're touching this
element — currently always omitted, even though the tree supports
multi-selection.)
A symmetrical option for rows (`<Tree treeItemAriaLabel={(node) => node.data.name}>`
or just always spreading any `aria-label` from `attrs`) would also be welcome,
though that's already workable via `renderRow`.
Prior accessibility work
Thanks for the previous a11y improvements — #198 (aria-level / role
violations) and #223 (aria-expanded) made the row attributes excellent.
This issue is the natural next step for the container.
Summary
Allow consumers to set an accessible name on the tree by accepting
aria-labeland
aria-labelledbyas props on<Tree>, and forwarding them onto theinternal
role="tree"element rendered byDefaultContainer.Background
Per the W3C Treeview pattern,
the element with
role="tree"should have an accessible name supplied viaaria-labeloraria-labelledby. Todayreact-arborist(3.4.3) does notexpose any prop to do this —
TreeProps<T>has noaria-label/aria-labelledbyfields, andDefaultContainerrenders<div role="tree" …>with hardcoded attributes that don't accept extra props.The same gap exists at the row level: each row gets
role="treeitem"plusaria-level/aria-selected/aria-expanded(great), but noaria-label/aria-labelledby. Consumers who do not control the row'svisible text DOM end up with treeitems that have no accessible name. Note that
this is workable today via a custom
renderRow(which receivesnodeandattrs), but the tree-container case has no equivalent escape hatch.Current workaround (what we're forced to do)
Because there is no public API:
This works but:
`renderContainer` doesn't help — `DefaultContainer` is not exported, so
overriding it means re-implementing ~230 LOC of keyboard handling
(Backspace delete, Tab/Shift+Tab focus traversal, arrow keys, type-ahead
search, etc.).
Proposed API
Internally, `DefaultContainer` would spread the relevant ARIA attributes onto
its `role="tree"` div:
(`aria-multiselectable` would be a useful bonus while we're touching this
element — currently always omitted, even though the tree supports
multi-selection.)
A symmetrical option for rows (`<Tree treeItemAriaLabel={(node) => node.data.name}>`
or just always spreading any `aria-label` from `attrs`) would also be welcome,
though that's already workable via `renderRow`.
Prior accessibility work
Thanks for the previous a11y improvements — #198 (aria-level / role
violations) and #223 (aria-expanded) made the row attributes excellent.
This issue is the natural next step for the container.