Accessibility
Every Component exposes ARIA attributes through a typed Aria accessor. The framework also provides RovingTabIndex for keyboard navigation across grouped controls. The built-in components wire both correctly out of the box; this page is for when you build custom UI.
ARIA via Component.getAria()
Each component has its own Aria instance. Get it with getAria():
import { Component } from '@jimka/typescript-ui/core';
class CustomGrid extends Component {
constructor() {
super('div');
this.getAria().setRole('grid');
this.getAria().setRowCount(100);
}
}Aria is a typed wrapper, not a string-based attribute setter — every WAI-ARIA attribute the framework supports has its own getter and setter:
component.getAria().setRole('button');
component.getAria().setSelected(true);
component.getAria().setLabel('Save');
component.getAria().setExpanded(false);
component.getAria().setHasPopup('menu');State you set before the DOM exists is replayed onto the element when it's first rendered. After render, sets propagate immediately.
See Aria for the full list of attribute methods.
Roles used by built-in components
The framework sets WAI-ARIA roles automatically for its components. Some highlights:
| Component | Role |
|---|---|
Table | grid (with rowgroup, row, gridcell, columnheader on its parts) |
Tree | tree with treeitem rows |
Tab | tablist / tab / tabpanel |
MenuBar | menubar / menu / menuitem |
Menu | menu / menuitem / separator |
Button | inherits the native <button> semantics |
ComboBox | combobox (with the underlying <select> providing the listbox) |
You don't need to set these on built-in components; they're applied at construction time.
Keyboard navigation: RovingTabIndex
RovingTabIndex implements the standard pattern where exactly one item in a group has tabindex=0 at any time, and arrow keys move focus among the items:
import { RovingTabIndex } from '@jimka/typescript-ui/core';
const tabs = new RovingTabIndex();
tabs.add(button1);
tabs.add(button2);
tabs.add(button3);
tabs.moveTo(0); // button1 is now the active itemmoveTo does three things:
- Sets
tabindex=-1on the previous active item. - Sets
tabindex=0on the new active item. - Calls
focus()on the new active item's DOM element.
ButtonGroup uses RovingTabIndex automatically for ToggleButton groups when you call setContainer(container). For RadioButton groups, the browser's built-in radio-group navigation handles arrow keys, so RovingTabIndex isn't needed.
Building an accessible custom widget
A custom selectable list is the simplest non-trivial case:
import { Component, Event, RovingTabIndex } from '@jimka/typescript-ui/core';
class SelectableList extends Component {
private items: Component[] = [];
private rti = new RovingTabIndex();
constructor() {
super('ul');
this.getAria().setRole('listbox');
Event.addSubtreeListener(this, 'keydown', (e: KeyboardEvent) => this.onKey(e));
}
addItem(item: Component): void {
item.getAria().setRole('option');
this.addComponent(item);
this.items.push(item);
this.rti.add(item);
Event.addListener(item, 'click', () => this.select(item));
}
private select(item: Component): void {
for (const i of this.items) i.getAria().setSelected(false);
item.getAria().setSelected(true);
this.rti.moveTo(this.items.indexOf(item));
}
private onKey(e: KeyboardEvent): void {
const idx = this.items.findIndex(i =>
i.getElement() === document.activeElement);
if (idx === -1) return;
if (e.key === 'ArrowDown' && idx < this.items.length - 1) {
this.select(this.items[idx + 1]);
e.preventDefault();
}
if (e.key === 'ArrowUp' && idx > 0) {
this.select(this.items[idx - 1]);
e.preventDefault();
}
}
}The pattern: set the role, manage aria-selected on items, wire arrow keys via addSubtreeListener, and let RovingTabIndex handle tab focus.
Live announcements
For dynamic announcements (toasts, status messages), the framework's Notification component is the right tool — its container has role="alert" semantics so screen readers announce new toasts as they appear.
For your own live regions, set the role:
const status = Component();
status.getAria().setRole('status');role="status" implies aria-live="polite" and aria-atomic="true" per the ARIA spec, so screen readers announce updates without any further configuration. Aria exposes typed setters for every ARIA attribute the framework writes — if you need one that isn't there yet, extend Aria in src/typescript/lib/core/Aria.ts rather than reaching into the now-protected setElementAttribute.
Testing
- Keyboard-only — unplug your mouse and verify every interaction works with
Tab,Shift+Tab, arrow keys, andSpace/Enter. - Screen reader — VoiceOver (macOS) and NVDA (Windows) are the two readers most commonly tested against. Both should announce roles and labels for built-in components correctly.
- Browser dev tools — Chrome's "Accessibility" panel under DevTools shows the computed accessibility tree for each element.
See also
- API: Aria, AriaRole, AriaSort
- API: RovingTabIndex
ButtonGroup— usesRovingTabIndexinternally