Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Known accessibility limitations

This file records accessibility gaps that Teksilo cannot close from within its own code because they depend on an external component. Everything achievable inside the framework is implemented; the items below are the residual, documented for anyone assembling a conformance statement (EN 301 549 ACR, US Section 508 VPAT, RGAA déclaration de conformité).

WCAG 1.3.5 Identify Input Purpose — full autofill tokens (AccessKit-blocked)

Status: partially met. The remainder is blocked upstream in AccessKit; no Teksilo-side action is possible or planned.

Teksilo exposes a field's semantic purpose to assistive technology via TextInputField::input_purpose(..) / TextInput::input_purpose(..) (InputPurpose::{Normal, Email, Phone, Url, Number, Search}), which sets the matching specialised AccessKit role (Role::EmailInput, PhoneNumberInput, UrlInput, NumberInput, SearchInput). A screen reader therefore announces the field kind ("email, edit text") rather than a generic "edit text".

What is not achievable: the full WCAG 1.3.5 success criterion also expects the complete HTML autocomplete-token vocabulary — given-name, family-name, street-address, postal-code, cc-number, bday, and the rest — so the platform / an autofill agent can pre-fill known user data. AccessKit 0.24.1 has no node property that carries these tokens (its AutoComplete { Inline, List, Both } enum is the unrelated aria-autocomplete popup-behaviour hint, not the field-purpose token). This is an AccessKit / underlying platform-API limitation, not a Teksilo omission: there is nothing in the accessibility protocol to write the token into.

Consequence for conformance: an app can satisfy the role/kind half of 1.3.5 today; the autofill-token half is unavailable on this toolkit until AccessKit grows such a field. Note the specialised input roles Teksilo does set are 2.1-only concepts and are outside US Section 508's WCAG-2.0 scope.

Selection contrast (resolved)

The default theme's surface_selected is a subtle pale wash that does not, on its own, reach WCAG SC 1.4.11's 3:1 non-text contrast as a flat fill. This is now addressed on two levels: (1) selected StandardListItem / StandardTreeItem rows draw a thin BorderRole::Focused boundary (accent teal, >= 3:1) even without keyboard focus — a non-color-alone cue satisfying SC 1.4.1 / 1.4.11; and (2) the opt-in high-contrast theme (ColorTokens::for_high_contrast) also uses a stronger surface_selected fill.

Residual: the TableView / TreeTableView selection band is painted through a separate TableStyle path, not the StandardItemStyle boundary above, so it does not yet carry the same boundary cue — a follow-up for those two widgets.

WCAG 1.4.12 Text Spacing — Not Applicable (WCAG2ICT)

SC 1.4.12 is a web criterion: its premise is that content survives a user-injected text-spacing override (a browser user stylesheet / bookmarklet that forces line-height ≥ 1.5, letter-spacing ≥ 0.12em, etc.). Native desktop software exposes no such injection mechanism, so — per W3C WCAG2ICT, which EN 301 549 defers to for applying WCAG to non-web software — the criterion does not apply where the software provides no text-spacing-override facility.

Teksilo already ships the primary readability adjustment as a first-class control — the global text scale (TextScaleControl, 80–200 %, applied app-wide through effective_theme) — and its reflow/shrink layout prevents clipping or overlap when text enlarges. An independent line/letter-spacing control (a "reading-comfort" slider, chiefly of benefit to dyslexic readers) remains a possible product feature, not a conformance requirement; if built later it needs a text-typeset shaping hook (letter-spacing per glyph + line-height multiplier), the tokens threaded through TypographyTokensWidgetTree, and a settings-bound control parallel to TextScaleControl.

Conformance position: 1.4.12 → N/A for the toolkit (no user spacing-override mechanism); text enlargement is covered by the text-scale control. Defensible under EN 301 549 / RGAA per WCAG2ICT.

described_by reaches no assistive technology (AccessKit-blocked)

Status: not met upstream. There is nothing for Teksilo to write.

AccessKit 0.25 carries a described_by relation on the node, but nothing resolves it: accesskit_consumer::Node::description() reads only the node's own description property, and none of the three adapters exports the relation — macOS maps AXHelp from description(), Windows maps UIA_FullDescriptionPropertyId from description, and the AT-SPI relation set carries controls alone. A node described only through the relation therefore describes itself to nobody.

Teksilo consequently copies the description string onto the described node (set_description) wherever a description must actually be heard — the plain tooltip tier does this — and keeps the relation for the day upstream resolves it. Note also that VoiceOver exposes a description as AXHelp, a hint: it is read after a delay or on VO-Shift-H, not in the focus utterance, so the ARIA aria-describedby reading behaviour is an NVDA / Orca one on AccessKit 0.25.

Text-run colour and per-run language (AccessKit-shaped, Teksilo-side work)

A text run carries weight, italic, underline and strikethrough but no foreground colour and no per-run language, so a syntax-highlighted editor exposes runs whose colours assistive technology cannot see, and a quoted sentence in another language is read in the document's voice. AccessKit has the properties (set_foreground_color, set_background_color, set_language); Teksilo does not yet emit them. Tracked as §5.9 of the internal audit — a Teksilo omission, not an upstream block.

Last reviewed: 2026-09-06.

Two AT actions deliberately not advertised (decisions, not gaps)

An AccessKit client acts on what a node advertises: accesskit_consumer filters by supports_action, and VoiceOver's rotor and Narrator's scan build their verb lists from it. Advertising an action the widget does not service is therefore worse for a user than not advertising it — the verb appears and does nothing when chosen. Two omissions were raised as possible defects and are recorded here as decisions.

TextInputField does not advertise Action::ScrollIntoView. Its handle_access_action (primitives/text_input_field.rs) answers Ignored to that action, and nothing else would pick it up: ScrollIntoView is inert in the top-level event router and reaches a container only through the clipping-ancestor walk from EventContext::ensure_visible, never through the AT action path. Revealing a field is the enclosing ScrollArea's job and already works. What the field does advertise — Focus, and, when it can service them, SetValue, ReplaceSelectedText and SetTextSelection — is pinned by the_field_advertises_the_actions_an_assistive_client_may_invoke and a_read_only_or_protected_field_withdraws_the_actions_it_cannot_service in text_input/tests.rs.

RichTextEditor does advertise ScrollIntoView and services it, by revealing its caret (rich_text.rs, the (Action::ScrollIntoView, _) arm).

TextWidget advertises no actions at all. It is a leaf label: it has no on_access_action handler, takes no focus, and holds no value a client could set. Every action AccessKit defines for it would be unserviced. Its accessibility contribution is its name and its Role::TextRun children (see the text-ranges work), which is what a screen reader reviews it by; a client that wants it brought on screen goes through the scroll container around it, as above.

CodeEditor and LogView advertise ScrollIntoView and do not service it

Status: open defect, not a limit. code_editor/a11y.rs's finish adds Action::ScrollIntoView to the node for both wrappers, but the shared handle_access_action in the same file has no arm for it — it falls through to _ => EventResponse::Ignored. So a screen reader offers the verb on a code editor and on a log view, and choosing it does nothing. This is the same mistake as advertising an unserviced action anywhere else, and it is the opposite of the TextInputField decision above.

Fixing the CodeEditor half is small: keyboard::ensure_caret_visible(state) already exists and does exactly what RichTextEditor's arm does. The LogView half needs a decision first — a log view follows its tail, so "reveal the caret" may fight follow_tail, and it is not obvious whether an AT reveal should suspend tail-following the way a user scroll does. Left unfixed rather than guessed at, because a log that silently stops following its tail is a worse bug than a verb that does nothing.

Added 2026-09-08 alongside the touch programme's scrollables review.

Last reviewed: 2026-07-02.