Accessibility & Internationalization (Desktop)
Why this belongs in the craft floor, not the backlog
A UI that looks considered but can't be used by keyboard, doesn't survive the OS's own high-contrast mode, or falls apart the moment its labels are translated isn't actually finished — it's a screenshot that works for one user, in one language, with one input method. Desktop apps in particular are used for hours at a time by people who rely on exactly these things: keyboard-first power users, users of Windows/macOS built-in accessibility features, and the large share of the world that doesn't use the app in English. Treat this skill as part of the same craft floor as spacing and color, not a compliance pass at the end.
Keyboard & focus baseline
- Every interactive element must be reachable by
Tab, in an order that matches the visual/reading order on screen — not DOM or scene-graph order that happens to differ from it.
- A visible focus indicator, distinct from hover, is required on every focusable element (see
desktop-ui-buttons-controls). Never remove the default focus ring without shipping a replacement.
- Modals and dialogs trap focus inside themselves while open, and
Escape always closes them and returns focus to the element that opened them.
- Custom controls (comboboxes, trees, sliders, node graphs, canvases) need their own real keyboard model — arrow keys to move,
Home/End to jump, Enter/Space to activate — not just mouse handling with Tab bolted on afterward. See desktop-ui-domain-components for components with no off-the-shelf pattern to copy.
- Global/app-level shortcuts should avoid colliding with the OS's own screen-reader and system shortcuts.
Screen readers & platform accessibility APIs
A desktop screen reader doesn't read pixels — it reads what the app exposes to the platform's accessibility layer: UI Automation (UIA) on Windows, NSAccessibility on macOS, AT-SPI on Linux (GTK/Qt). This has real consequences:
- Any custom-drawn surface (canvas, WebGL, a hand-rolled component that isn't a native control) exposes nothing to that layer by default. Plan a parallel accessible representation — an accessible name, role, and state for each meaningful element — rather than treating it as unsolvable and skipping it.
- Icon-only buttons need an accessible name attached to the control itself, not just a visual tooltip (see
desktop-ui-icons) — tooltips are invisible to screen readers and to touch-only or keyboard-only users who never hover.
- Toasts, inline status text, and banners (see
desktop-ui-feedback-messaging) need to reach screen reader users without stealing their focus — announce through a live region (assertive for errors that need immediate attention, polite for routine confirmations), not a purely visual pop-in.
- Form fields need their label and any error text programmatically associated with the field, not just placed nearby on screen (see
desktop-ui-text-inputs, desktop-ui-settings-layout).
High-contrast / forced-colors modes
Windows High Contrast and macOS Increase Contrast override an app's own palette with a small, user-chosen set of colors. Apps that hardcode subtle borders, light-gray dividers, or color-only state indicators tend to lose them entirely under these modes:
- Confirm every important boundary (input borders, focus rings, selected-state markers) survives with the OS's forced palette applied, not just the app's default theme.
- Never let a state depend on a color difference alone (this is already the color-system rule) — under forced-colors mode, subtle color differences are frequently the first thing to disappear.
Motion & vestibular safety
Beyond the prefers-reduced-motion handling in desktop-ui-motion, treat large-scale parallax, zoom, or spin effects as a vestibular-safety issue, not just a taste preference — some users experience genuine physical discomfort from them, not mild annoyance. When reduced motion is on, replace the animation with an instant state change, don't just slow it down.
Touch & hybrid input
Plenty of "desktop" apps run on touchscreen laptops, 2-in-1s, and tablet-with-keyboard devices. Wherever touch is a plausible input path:
- Give interactive targets a minimum 24×24px hit area even when the visible icon or control is smaller — pad the hit area invisibly rather than inflating the artwork itself.
- Dense mouse-first UI (small toolbar icon buttons, table row actions) should get generous invisible hit padding, since the visual density that works for a precise mouse cursor doesn't work for a fingertip.
Internationalization & right-to-left layouts
This is the area most desktop UI work skips entirely until it breaks in QA. Build it in from the start:
- Never bake text into images or icons. It can't be translated, full stop.
- Leave headroom for text expansion. German, Finnish, and several other languages commonly run 30–40% longer than the equivalent English string. A button or settings-row label sized to fit its English text exactly will clip or wrap unpredictably the moment it's translated — size with room to grow, not pixel-perfect to the source string.
- True RTL locales (Arabic, Hebrew) mirror the whole layout, not just text alignment: the sidebar moves to the opposite edge, and back/forward or next/previous chevrons flip direction. Icons with no inherent directionality (checkmark, play button, star) do not flip.
- Numbers, code, file paths, and URLs stay left-to-right even inside an otherwise-RTL sentence.
- Dates, times, numbers, and currency should use the OS/runtime's locale-formatting APIs, not hardcoded formats — the correct format depends on the user's system locale, not the app's source language.
Testing checklist
Run these before calling any screen "done," the same way desktop-ui-foundations has an anti-slop checklist:
- Navigate the entire flow using only the keyboard — no mouse.
- Turn on the OS screen reader (Narrator/VoiceOver/Orca) for a few minutes and attempt the core workflow.
- Toggle the OS's high-contrast/forced-colors mode and confirm nothing important disappears.
- Toggle the OS's "reduce motion" setting and confirm animations become instant, not just slower.
- Pseudo-localize the UI (pad every string ~30% and wrap it in brackets) to catch layout breaks before real translations exist.
Review format
| Before |
After |
Why |
| Icon-only toolbar button with a tooltip as its only label |
Same button, plus an accessible name exposed to the platform accessibility API |
Tooltips are invisible to screen readers and to touch/keyboard-only users who never hover |
| Settings row sized exactly to fit "Enable notifications" |
Same row with layout headroom before locking in fixed widths |
Translated strings routinely run 30–40% longer and will clip or wrap without room to grow |
| A toast fades in and out with no announcement |
The same toast paired with a live-region announcement |
Screen reader users otherwise have no way to know the confirmation happened |
Focus ring removed via outline: none with nothing to replace it |
Focus ring removed and replaced with a custom-styled but equally visible indicator |
Keyboard users lose all positional feedback without a focus indicator, styled or not |
1---2name: desktop-ui-accessibility-i18n3description: Use whenever designing or reviewing keyboard access, screen reader support, high-contrast/forced-colors modes, hybrid touch input, or right-to-left and translated layouts in a desktop app. Depends on desktop-ui-foundations. Read this alongside foundations on any real project — it is not an optional add-on skill. Trigger on "accessibility," "a11y," "screen reader," "keyboard navigation," "high contrast," "forced colors," "RTL," "right-to-left," "localization," "translation," or "this breaks in German/Arabic/Japanese."4---56# Accessibility & Internationalization (Desktop)78## Why this belongs in the craft floor, not the backlog910A UI that looks considered but can't be used by keyboard, doesn't survive the OS's own high-contrast mode, or falls apart the moment its labels are translated isn't actually finished — it's a screenshot that works for one user, in one language, with one input method. Desktop apps in particular are used for hours at a time by people who rely on exactly these things: keyboard-first power users, users of Windows/macOS built-in accessibility features, and the large share of the world that doesn't use the app in English. Treat this skill as part of the same craft floor as spacing and color, not a compliance pass at the end.1112## Keyboard & focus baseline1314- Every interactive element must be reachable by `Tab`, in an order that matches the visual/reading order on screen — not DOM or scene-graph order that happens to differ from it.15- A visible focus indicator, distinct from hover, is required on every focusable element (see `desktop-ui-buttons-controls`). Never remove the default focus ring without shipping a replacement.16- Modals and dialogs trap focus inside themselves while open, and `Escape` always closes them and returns focus to the element that opened them.17- Custom controls (comboboxes, trees, sliders, node graphs, canvases) need their own real keyboard model — arrow keys to move, `Home`/`End` to jump, `Enter`/`Space` to activate — not just mouse handling with `Tab` bolted on afterward. See `desktop-ui-domain-components` for components with no off-the-shelf pattern to copy.18- Global/app-level shortcuts should avoid colliding with the OS's own screen-reader and system shortcuts.1920## Screen readers & platform accessibility APIs2122A desktop screen reader doesn't read pixels — it reads what the app exposes to the platform's accessibility layer: **UI Automation (UIA)** on Windows, **NSAccessibility** on macOS, **AT-SPI** on Linux (GTK/Qt). This has real consequences:2324- Any custom-drawn surface (canvas, WebGL, a hand-rolled component that isn't a native control) exposes *nothing* to that layer by default. Plan a parallel accessible representation — an accessible name, role, and state for each meaningful element — rather than treating it as unsolvable and skipping it.25- Icon-only buttons need an accessible name attached to the control itself, not just a visual tooltip (see `desktop-ui-icons`) — tooltips are invisible to screen readers and to touch-only or keyboard-only users who never hover.26- Toasts, inline status text, and banners (see `desktop-ui-feedback-messaging`) need to reach screen reader users without stealing their focus — announce through a live region (assertive for errors that need immediate attention, polite for routine confirmations), not a purely visual pop-in.27- Form fields need their label and any error text programmatically associated with the field, not just placed nearby on screen (see `desktop-ui-text-inputs`, `desktop-ui-settings-layout`).2829## High-contrast / forced-colors modes3031Windows High Contrast and macOS Increase Contrast override an app's own palette with a small, user-chosen set of colors. Apps that hardcode subtle borders, light-gray dividers, or color-only state indicators tend to lose them entirely under these modes:3233- Confirm every important boundary (input borders, focus rings, selected-state markers) survives with the OS's forced palette applied, not just the app's default theme.34- Never let a state depend on a color difference alone (this is already the color-system rule) — under forced-colors mode, subtle color differences are frequently the first thing to disappear.3536## Motion & vestibular safety3738Beyond the `prefers-reduced-motion` handling in `desktop-ui-motion`, treat large-scale parallax, zoom, or spin effects as a vestibular-safety issue, not just a taste preference — some users experience genuine physical discomfort from them, not mild annoyance. When reduced motion is on, replace the animation with an instant state change, don't just slow it down.3940## Touch & hybrid input4142Plenty of "desktop" apps run on touchscreen laptops, 2-in-1s, and tablet-with-keyboard devices. Wherever touch is a plausible input path:4344- Give interactive targets a minimum **24×24px** hit area even when the visible icon or control is smaller — pad the hit area invisibly rather than inflating the artwork itself.45- Dense mouse-first UI (small toolbar icon buttons, table row actions) should get generous invisible hit padding, since the visual density that works for a precise mouse cursor doesn't work for a fingertip.4647## Internationalization & right-to-left layouts4849This is the area most desktop UI work skips entirely until it breaks in QA. Build it in from the start:5051- **Never bake text into images or icons.** It can't be translated, full stop.52- **Leave headroom for text expansion.** German, Finnish, and several other languages commonly run 30–40% longer than the equivalent English string. A button or settings-row label sized to fit its English text exactly will clip or wrap unpredictably the moment it's translated — size with room to grow, not pixel-perfect to the source string.53- **True RTL locales (Arabic, Hebrew) mirror the whole layout**, not just text alignment: the sidebar moves to the opposite edge, and back/forward or next/previous chevrons flip direction. Icons with no inherent directionality (checkmark, play button, star) do **not** flip.54- **Numbers, code, file paths, and URLs stay left-to-right** even inside an otherwise-RTL sentence.55- **Dates, times, numbers, and currency should use the OS/runtime's locale-formatting APIs**, not hardcoded formats — the correct format depends on the user's system locale, not the app's source language.5657## Testing checklist5859Run these before calling any screen "done," the same way `desktop-ui-foundations` has an anti-slop checklist:60611. Navigate the entire flow using only the keyboard — no mouse.622. Turn on the OS screen reader (Narrator/VoiceOver/Orca) for a few minutes and attempt the core workflow.633. Toggle the OS's high-contrast/forced-colors mode and confirm nothing important disappears.644. Toggle the OS's "reduce motion" setting and confirm animations become instant, not just slower.655. Pseudo-localize the UI (pad every string ~30% and wrap it in brackets) to catch layout breaks before real translations exist.6667## Review format6869| Before | After | Why |70|---|---|---|71| Icon-only toolbar button with a tooltip as its only label | Same button, plus an accessible name exposed to the platform accessibility API | Tooltips are invisible to screen readers and to touch/keyboard-only users who never hover |72| Settings row sized exactly to fit "Enable notifications" | Same row with layout headroom before locking in fixed widths | Translated strings routinely run 30–40% longer and will clip or wrap without room to grow |73| A toast fades in and out with no announcement | The same toast paired with a live-region announcement | Screen reader users otherwise have no way to know the confirmation happened |74| Focus ring removed via `outline: none` with nothing to replace it | Focus ring removed and replaced with a custom-styled but equally visible indicator | Keyboard users lose all positional feedback without a focus indicator, styled or not |