Accessibility and browser support
This page says plainly what accessibility support the chart has, what is missing, and how to serve users who need more. It then lists the browsers, runtimes and tools the library supports, and how that support is tested.
Accessibility
Section titled “Accessibility”What the chart does today
Section titled “What the chart does today”- The scroller is a named, focusable region. It has
role="region",tabindex="0"and anaria-labeltaken fromstrings.chartLabel(default'Gantt chart'). Translate it with thestringsoption. Once the region has focus, the browser’s own keyboard scrolling works on it (the arrow keys, Page Up, Page Down, Home and End). - A visible focus ring. The focus indicator is drawn with
:focus-visible, and its colour and width are the tokens--gantt-focus-ring-colorand--gantt-focus-ring-width(theming). - Tooltips are described to assistive technology. A tooltip is
role="tooltip", and the bar it describes getsaria-describedbypointing at it, for as long as it is shown. - The link menu is a menu. It is
role="menu"withrole="menuitem"buttons. Opening it moves the focus to its first item, Tab moves between the items, andEscapecloses it and gives the focus back to where it was. - Motion respects the user. Under
prefers-reduced-motion: reduceevery transition is off. - Colour comes from tokens, in a light and a dark theme. With
theme: 'auto'the chart followsprefers-color-scheme. Every colour is a--gantt-*custom property, so a high-contrast theme of your own is a matter of overriding tokens (see Theming). - What the chart draws is also data. The core carries what an accessible renderer needs:
stable task and link ids, row order and depth, the expanded state, the selection, per-task
permissions, the names and dates of every
TaskRecord, and issues without UI strings. A renderer of your own can build on them (Custom renderers).
Focus on the bars
Section titled “Focus on the bars”With keyboard.barFocus, which is off by default, bars take the keyboard
focus with one tab stop for all of them: the focused bar, else the one that last was if it is still
drawn, else the first drawn. So tabbing back in never jumps far away. A bar then has role="button"
and a name that describes the task in one line, as its tooltip does: its name, its dates, its
progress and its working days (Design, Mar 3, 2026 – Mar 7, 2026, 80% done · 4 working days), in
the chart’s locale and with its strings. A task with no name is
named by its id. Where selection.onClick makes a click select a bar, it
also has aria-pressed. The ring shows from the keyboard only (:focus-visible), and it is an
outline, so a selected, invalid or conflicting bar shows its own state beside it.
| Key | What it does |
|---|---|
| Up, Down | The bar of the row above or below; a row without one is skipped |
| Home, End | The first or the last bar |
| Page Up, Page Down | A page of rows |
| Enter, Space | A click: taskClick, and the selection if selection.onClick is on |
| Ctrl or Cmd + Enter | With selection.multiple, adds the task or takes it out; Cmd + Space belongs to macOS |
| Left, Right | Scroll the chart sideways, as on the scroller (or edit, see below) |
The keys do nothing while a gesture runs or a link is armed, and Escape still cancels those. The
focused bar stays in the document when it scrolls out of view, so it keeps the focus, and it keeps it
when rows are sorted. If it goes away with its row, the focus goes to the tab stop. A window that
loses the focus keeps the bar as the place to return to, and focusTask(undefined) called from your
code takes the focus from the bar to the chart.
Beside a task list, the bars and the rows share one focused task, and the surface that holds the browser’s focus keeps it.
Editing with the keyboard
Section titled “Editing with the keyboard”With keyboard.edit (also off by default, and it needs barFocus) the arrow
keys edit the bar that has the focus:
| Key | What it does |
|---|---|
| Left, Right | Moves the task one snap step earlier or later |
| Shift + Left, Right | Moves the end of the task: shorter or longer |
| Alt + Shift + Left, Right | Moves the start of the task: later or earlier |
Ctrl, Cmd, or Alt alone with an arrow belong to the browser and the system (Ctrl and an arrow
switches the desktop on a Mac), so the chart leaves them alone. Each press is a whole change: the
snap step of that gesture (scheduling.snap, else the view mode’s, else the data step), skipped
non-working days, the minimum length of one step, the propagation to successors and children, the
before-hooks and the permissions of a drag. onTasksChange hears one change per
press, with the reason move, resize-start or resize-end; a save-on-change application
receives one per press, as it does per drag. A task that cannot be edited that way (it is
locked, editable: false, or editing is off for that kind) does not take the key, and Left and
Right scroll the chart as they would without the option. The same step is a method, nudge, for
buttons of your own (Methods).
There is no edit mode to enter, and so nothing to cancel with Escape.
Undo and redo keys
Section titled “Undo and redo keys”With keyboard.undo and history on, Ctrl or Cmd + Z undoes the
last action, and Ctrl or Cmd + Shift + Z or Ctrl + Y redoes it (Cmd + Y is the browser’s history, so it
is left alone). The keys work while the focus is on a bar or on the chart’s scrollable region and
nowhere else: an input, a contenteditable or a control of a slot or a bar label keeps its own undo,
and so does the rest of the page. They do nothing during a drag or while a change waits to be applied
(controlled data). A key that has nothing to do, with no history or with nothing to undo or redo, is
not taken from the browser (no preventDefault). A Latin letter is read from the typed character, so
Dvorak and AZERTY follow what is printed; on a layout that types a letter of another script (Cyrillic,
Greek) the physical Z and Y keys are used. A symbol or an accented letter on those keys is no
combination. Holding the key repeats it, and each repeat undoes one more step, as a text field does.
With announce, or edit with barFocus, the result is said in the live region:
Undone. Design, Mar 2, 2026 – …, Redone. …, or only Undone for a link.
Screen readers that have a browse mode (NVDA and JAWS on Windows) keep the arrow keys for their own cursor there, and the keys reach the chart only in the mode that passes them on (focus mode). Such a reader switches to it on its own for a grid or a dialog, and may not for a button, so a user may have to switch (NVDA: Insert + Space). This has not been tried with a real reader.
Announcements
Section titled “Announcements”keyboard.announce, and keyboard.edit with barFocus (nothing can speak
without a focusable bar), add a hidden live region (div.gantt__live, role="status") to the chart. A
keyboard change says the task with its new dates, progress and working days, and how many other tasks the
change moved (for example Design, Mar 3, 2026 – Mar 7, 2026, 80% done · 4 working days; 1 other task changed), or that the task was not changed (a hook cancelled it, or it is at its minimum length).
announce also says when the selection changes other than by a press on the bar that has the focus and
says it is pressed (select(), a task list): Design selected, 3 tasks selected,
Selection cleared. The words are keys of strings,
taskChanged, notChanged, selection, undone and redone, which are functions of the values and the locale, so that a
language with more plural forms can say it right. The
keyboard editing example shows both.
What it does not do
Section titled “What it does not do”These are not available in this version:
- The chart has no grid roles of its own. The task list is the grid: a
role="treegrid"with rows and cells, which the keyboard walks cell by cell when you turn on itscellNavigation. The bars are buttons beside it. - Links and the handles of a bar are not focusable. The arrow keys on the bar resize it instead
of the handles. There is no keyboard way to change the progress, to draw a link, or to open the
tooltip (
tooltip.trigger: 'focus'is reported as unsupported). - The contrast of the default themes is pinned by a test of the token pairs (WCAG AA 4.5:1 for text, 3:1 for bars, links, the focus ring and the invalid and conflict states). A theme of your own has to be checked by you; the tokens are in Theming.
Covering the rest
Section titled “Covering the rest”If your users rely on assistive technology or a keyboard, give them the same plan in a form that works for them, next to the chart, and let them edit it there:
- The task list, a
role="treegrid"that the keyboard walks, kept in sync with the chart. Add a column for what your users need. - A table or a list of your own (name, start, end, progress, predecessors), built from the
same data you give the chart. For an uncontrolled chart, read it from
getTask(id)and theonTasksChangeevent; for a controlled one the data is already yours. - Edit forms. A dialog that edits a task through
updateTask(id, patch)gives a keyboard-and-screen-reader path to every change that dragging makes. Connect it to the chart with the click event of a task (onTaskClick), or to a button in your own list. The programmatic updates example and the edit session example show the calls. - Links by form.
addLinkandremoveLinkdo what drawing and removing a link do, without a pointer.
Both the table and the chart then show one source of truth, and any change in either shows in the other.
With React
Section titled “With React”The chart is the vanilla chart, so its accessibility is the same. What React adds:
id,aria-*anddata-*props go to the containerdiv, so you can label the whole widget.- The content you draw through the slots (the tooltip, the link menu, the bar label) is your
React, so its semantics, labels and focus behaviour are yours. A tooltip or menu that holds
buttons or inputs is interactive: it takes pointer events, a link menu also moves the focus to
its first button, and
Escapeor a press outside closes it. See React.
Testing your own use
Section titled “Testing your own use”Tab to the chart and check that the focus ring shows and that the name is announced; with
keyboard.barFocus, edit and announce on, Tab to a bar, move it with the arrow keys and listen
to what is said, with the screen readers your users have (VoiceOver, NVDA, JAWS); switch the
OS to a high-contrast or dark colour scheme and check each theme you ship; turn on
prefers-reduced-motion and check that nothing animates; open the tooltip and the link menu with
a keyboard-only session and a screen reader.
Browser support
Section titled “Browser support”The chart supports the last two versions of Chrome, Edge, Firefox and Safari, and iOS Safari 16.4 or newer.
| Feature the chart uses | Where | Needs |
|---|---|---|
Pointer Events, ResizeObserver, SVG |
the renderer | every supported browser |
Cascade layers (@layer), :is(), overflow: clip |
the stylesheet | every supported browser |
color-mix() |
the stylesheet | Chrome and Edge 111, Firefox 113, Safari and iOS Safari 16.2 |
| A regular expression with a lookbehind | the core’s JavaScript | iOS Safari 16.4, the oldest iOS Safari that can load the core |
The progress colour has a fallback for the one thing in the list that is newest (color-mix()),
and every browser above has all four stylesheet features.
How it is tested
Section titled “How it is tested”The end-to-end suite runs in Chromium, Firefox and WebKit through Playwright, in a production build of the playground: scrolling, pointer events and layout differ between engines, and that is what a chart is made of. Real touch input is tested in Chromium only, and iOS momentum scrolling is not tested automatically. The React bindings are run in a production build, in a development build (StrictMode) and on React 18.3.
Not supported
Section titled “Not supported”- Left to right only.
.ganttsetsdirection: ltrwhatever the page says, anddirection: 'rtl'is reported as unsupported. - A browser is needed for the chart.
createGanttdraws into a DOM element: on a server, import types only, or use@ganttlane/core. There is no server-rendered markup of the vanilla chart.@ganttlane/reactrenders its container on the server and draws the chart after hydration. - A closed shadow root. A chart in an open shadow root works if you adopt the stylesheet into the root; a closed one is not supported.
- Clipping ancestors with automatic height. With
layout.height: 'auto', no ancestor between the chart and what scrolls it may clip withoverflow: hidden.
Runtimes and tools
Section titled “Runtimes and tools”| Piece | Support |
|---|---|
@ganttlane/core |
Node 20 or newer; the same browsers as above. Standard ECMAScript and Intl only. No DOM, no timers, no window or document. It runs in a worker as it is |
@ganttlane/dom |
A browser with a DOM |
| React | @ganttlane/react for React 18.3 and 19 (react and react-dom are peer dependencies, ^18.3.0 || ^19.0.0). The whole unit-test suite runs against both |
| Module format | ESM only, with TypeScript declarations. TypeScript must resolve package exports: moduleResolution of bundler, node16 or nodenext |
| Bundlers | Anything that understands ESM, package exports and CSS imports. The packages mark CSS as a side effect (sideEffects: ["**/*.css"]) |
| TypeScript | Typed throughout; TypeScript 6 works. The stylesheet subpath ships a declaration file, so import '@ganttlane/dom/styles.css' type-checks (see the FAQ) |
The core reads the system clock only for “now” (replace it with the now option),
so it is deterministic in tests, and it starts no timers of its own.
React 18.3 differs from 19 in a few ways the React guide lists: how StrictMode reuses
state on the first render, the absence of <Activity> (19.2 only), a warning about
useLayoutEffect that a server render with a document (a jsdom test) provokes, and the type of
a useRef<GanttHandle>(null).