Task list — accessibility
Keyboard
- The state glyph
.tl-stateis a real<button>: Tab reaches it, Space or Enter finishes or reopens the task. - The title opens the task with Enter where the screen wires
onOpen. - The disclosure for subtasks and the add row are buttons and are reachable in order.
- Reordering by drag has no keyboard alternative (
onReorderis pointer-only). Every order it can produce stays reachable without a pointer throughŘadit— the drag only writes theVlastníorder, it does not gate anything the list cannot otherwise show.
ARIA
- Rows are list items with their controls inside; the state button carries
aria-pressed(done or not). - The state button's icon is decorative, so its
title— the state name plus what the click does (e.g. „Blokováno — označit jako hotové") — is the button's accessible name; nothing equivalent exists as visible row text since 15 Sep 2026 (readable id and state-name text were removed from the row). - A done task is struck through, its state glyph is filled, and
aria-pressed="true"carries it programmatically — the strike-through is never the only signal. - The priority glyph is decorative (
aria-hidden); priority is not announced in the row. - On an AI suggestion row (
.tl-row.sug) the whole state slot is decorative:.tl-state.sugcarriesaria-hidden="true"and is not a button (a suggestion has no state to toggle yet) — unlike a real task row, where the same slot is always a live, focusable button. - Counts (comments, subtasks) are numbers next to icons and are read with them.
- The suggested group's actions appear on hover or focus within the row, so keyboard users reach them by tabbing.
- Neither loading nor a failed load is announced; both are visible states (
SkeletonTasks,ErrorStatewith a retry button). - Host behaviour (
/tasks, ACRM-273): the Návrhy entry chip is a real<button>witharia-pressed={sugView}, reachable and toggleable by keyboard like any other chip on the bar. The „Seskupit" chip is the sameNavSelectpattern already used for „Řadit" — no separate a11y contract.
Focus
- Everything interactive in the row is a
<button>or an input, so the foundation ring fromds.cssapplies;task-list.cssdoes not switch outlines off. - The assignee link (
.tl-person) declares its own:focus-visible, because it is a dashed-underline text link inside a dense row.
Contrast
Computed from packages/ui/src/tokens/tokens.css:
- Title in ink on the row surface — above AA in both themes; a done title drops to muted with a strike-through.
- Due date: neutral muted,
.nowamber,.over--critical-ink= 5.37:1 light — the tone is carried by colour, and the word („dnes“, a date) carries it too. - Counts and the assignee name sit at the muted limit (2.85:1 light).
- The state glyph takes the bucket tone (
--ic-blueopen and done,--ic-amberin progress,--ic-greyblocked); a done task is a filled--ic-bluecircle with a--fg-on-accenttick — the same pair as the old accent checkbox, 4.44:1 light, 3.83:1 dark. - The row's hover fill (
--hover-surface, replacing the 15 Sep 2026-removed hairline) is a background change only — it carries no text and needs no contrast ratio of its own.
Mobile (≤720px)
- The state button
.tl-statestays visible and reachable — only the linked-entity slot.tl-relis removed from the DOM (display: none) below 720px; nothing that carries an accessible name disappears. - AI suggestion actions (
.tl-sugact) render at rest instead of on hover/focus — the touchscreen has no hover, so keyboard/AT users and touch users reach the same always-visible buttons; the app-only responsive layer also gives them an explicit touch height.
- Assignee as mark + initials (
.tl-person.ini) isrole="img"witharia-label= the full name (acrm !4114): the mark itself isaria-hiddenand the initials would be spelled out („Š S"). On a phone the initials are hidden, so the label is the only name — never drop it.
Touch (acrm !4069)
Suggestion actions are never hover-only on touch: visible at (hover: none) and ≤ 720 px, 44 px tall (calc(var(--sp-7) + var(--sp-1))), each a real <button> with a text label (no icon-only × ✓ any more — acrm !4067).
Known limits
- Drag reordering is pointer-only — but pointer, not mouse: since 16 September 2026 the gesture runs on pointer events (320ms long press on touch), so a finger can reorder too. Scrolling survives:
touch-action: noneis set only on the row that is actually lifted, and a press that travels more than 8px before the hold elapses is a scroll, not a drag. - The lifted row is marked visually only (
.dragging); there is no live region and noaria-grabbed— assistive technology hears nothing about the move until the list re-renders. - Priority is visual only: the scale glyph is hidden from assistive technology and the row has no text for it.
- Loading and error states are silent for assistive technology.
- Secondary text (counts, assignee, dates) sits below AA in light theme.
- In
narrowandfullmode the assignee is a badge plus initials whose full name lives in atitle— reachable, but only on hover or through assistive technology; onlywidemode shows the full name as visible text. - The state name is exposed only through the state button's
title/accessible name, never as visible row text — a sighted mouse user gets it only on hover, same limit as the assignee initials above. stateNameis a plain string supplied by the caller — if the host passes a non-empty but meaningless value (e.g. a raw internal code), assistive technology reads exactly that, uncorrected.- Switching into or out of the Návrhy view moves nothing and announces nothing: focus stays on the toggle chip and the swapped list content is silent for assistive technology, the same limit as loading/error above.
Source: docs/task-list/task-list.a11y.md · GitLab