Skip to content

Commit d29a7cd

Browse files
committed
A11y: Fix all 12 violations axe-core found
the app now works correctly with screen readers! Also better now for low vision users, patched some keyboard-related gaps, and made the structure more robust to make great a11y easier to implement. - Fix ARIA role hierarchy: move `role="tablist"` to direct parent of tabs, keep "New tab" button outside; change header `role="row"` to `role="toolbar"` - Remove nested interactive: close button in tabs now `aria-hidden="true"` instead of `role="button"` - Add `aria-label="File list"` to both `BriefList` and `FullList` listboxes - Differentiate pane landmarks: "Left file pane" / "Right file pane" instead of duplicate "File pane" - Bump `--color-text-tertiary` for WCAG AA contrast (light: `#737373`, dark: `#a0a0a0`) - Add `aria-label` to all Ark UI form components in settings (Switch, Select, Slider, NumberInput, RadioGroup, ToggleGroup) and raw Ark UI usage in `AdvancedSection` - Add `aria-label` to icon-only buttons in `KeyboardShortcutsSection` - Add `<main>` landmark and visually hidden `<h1>` to all three routes (explorer, settings, viewer) - Change title bar from `<div>` to `<header>` for proper landmark - Move `FunctionKeyBar` inside `<main>`, add `role="toolbar"` - Wrap tooltips in a landmark container so body-level tooltips satisfy `region` rule - Add `tabindex="0"` to file viewer `.file-content` for keyboard scrolling - Expand E2E accessibility tests: 8 tests covering main explorer, Copy/Delete/Move/About/License dialogs, all settings sections, and file viewer - Tests assert zero critical violations; all views now pass with zero violations at every severity
1 parent 4094ef9 commit d29a7cd

29 files changed

Lines changed: 713 additions & 93 deletions

apps/desktop/coverage-allowlist.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@
238238
},
239239
"ai/ai-toast-sync.svelte.ts": {
240240
"reason": "Svelte $effect reactive sync, depends on Svelte runtime"
241+
},
242+
"ui/ProgressBar.svelte": {
243+
"reason": "Pure UI component, no logic to test beyond rendering"
241244
}
242245
}
243246
}

apps/desktop/knip.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"@wdio/types",
1515
"webdriverio",
1616
"@crabnebula/tauri-driver",
17-
"@crabnebula/test-runner-backend"
17+
"@crabnebula/test-runner-backend",
18+
"axe-core"
1819
],
1920
"ignoreExportsUsedInFile": true,
2021
"project": ["src/**/*.{ts,svelte}"],

apps/desktop/src/app.css

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
/* === Text === */
2626
--color-text-primary: #1a1a1a;
2727
--color-text-secondary: #666666;
28-
--color-text-tertiary: #888888;
28+
--color-text-tertiary: #737373;
2929

3030
/* === macOS system accent colors (for reference / testing) ===
3131
* These are the fixed colors returned by NSColor.controlAccentColor().
@@ -180,7 +180,7 @@
180180
/* === Text === */
181181
--color-text-primary: #e8e8e8;
182182
--color-text-secondary: #aaaaaa;
183-
--color-text-tertiary: #888888;
183+
--color-text-tertiary: #a0a0a0;
184184

185185
/* === Interactive States (fallbacks — see light-mode comment above) === */
186186
--color-accent: #ffc206;
@@ -240,6 +240,19 @@
240240
}
241241
}
242242

243+
/* Screen reader only — visually hidden but accessible to assistive technology */
244+
.sr-only {
245+
position: absolute;
246+
width: 1px;
247+
height: 1px;
248+
padding: 0;
249+
margin: -1px;
250+
overflow: hidden;
251+
clip: rect(0, 0, 0, 0);
252+
white-space: nowrap;
253+
border-width: 0;
254+
}
255+
243256
/* =============================================================================
244257
Reset from https://github.com/filipelinhares/ress/blob/master/dist/ress.min.css with some stripping
245258
============================================================================= */

apps/desktop/src/lib/command-palette/CommandPalette.svelte

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@
155155
autocapitalize="off"
156156
/>
157157

158-
<div class="results-container" bind:this={resultsContainer}>
158+
<div class="results-container" bind:this={resultsContainer} role="listbox" aria-label="Commands">
159159
{#if results.length === 0 && query.trim()}
160160
<div class="no-results">No commands found</div>
161161
{:else}
@@ -282,20 +282,19 @@
282282
white-space: nowrap;
283283
}
284284
285-
/* Match highlight - macOS Spotlight-style with visible background */
285+
/* Match highlight - underline style that doesn't compromise text contrast */
286286
.match-highlight {
287-
background: var(--color-tint-hover-strong);
288287
color: inherit;
289-
border-radius: var(--radius-sm);
290-
/* stylelint-disable-next-line declaration-property-value-disallowed-list */
291-
padding: 1px 2px;
292-
/* stylelint-disable-next-line declaration-property-value-disallowed-list */
293-
margin: 0 -2px;
288+
background: none;
289+
text-decoration: underline;
290+
text-decoration-color: var(--color-accent);
291+
text-underline-offset: 2px;
292+
text-decoration-thickness: 2px;
294293
}
295294
296295
/* When item is under cursor, make the match highlight even more visible */
297296
.result-item.is-under-cursor .match-highlight {
298-
background: color-mix(in srgb, white, transparent 65%);
297+
text-decoration-color: var(--color-text-primary);
299298
}
300299
301300
.shortcuts {

apps/desktop/src/lib/file-explorer/pane/FilePane.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1644,7 +1644,7 @@
16441644
onclick={handlePaneClick}
16451645
onkeydown={() => {}}
16461646
role="region"
1647-
aria-label="File pane"
1647+
aria-label="{paneId === 'left' ? 'Left' : 'Right'} file pane"
16481648
>
16491649
<div class="header">
16501650
<VolumeBreadcrumb

apps/desktop/src/lib/file-explorer/pane/FunctionKeyBar.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
<!-- svelte-ignore a11y_no_static_element_interactions -->
4747
<div
4848
class="function-key-bar"
49+
role="toolbar"
50+
aria-label="Function keys"
4951
onmousedown={(e) => {
5052
e.preventDefault()
5153
}}

apps/desktop/src/lib/file-explorer/tabs/TabBar.svelte

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@
5959
}
6060
</script>
6161

62-
<div class="tab-bar" role="tablist" aria-label="{paneId} pane tabs" onclick={onPaneFocus}>
63-
<div class="tab-list">
62+
<div class="tab-bar" onclick={onPaneFocus}>
63+
<div class="tab-list" role="tablist" aria-label="{paneId} pane tabs">
6464
{#each tabs as tab, index (tab.id)}
6565
{@const isActive = tab.id === activeTabId}
6666
{@const isBeforeActive = index < tabs.length - 1 && tabs[index + 1].id === activeTabId}
@@ -108,10 +108,8 @@
108108
{#if !isSingleTab}
109109
<span
110110
class="close-btn"
111-
role="button"
112-
tabindex="-1"
111+
aria-hidden="true"
113112
use:tooltip={'Close tab'}
114-
aria-label="Close tab"
115113
onclick={(e: MouseEvent) => {
116114
handleCloseClick(e, tab.id)
117115
}}>&#xd7;</span

apps/desktop/src/lib/file-explorer/views/BriefList.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@
472472

473473
<div class="brief-list-container" class:is-focused={isFocused}>
474474
<!-- Header row with sort options -->
475-
<div class="header-row" role="row">
475+
<div class="header-row" role="toolbar" aria-label="Sort columns">
476476
<SortableHeader
477477
column="name"
478478
label="Name"
@@ -519,6 +519,7 @@
519519
onscroll={handleScroll}
520520
tabindex="-1"
521521
role="listbox"
522+
aria-label="File list"
522523
aria-activedescendant={cursorIndex >= 0 ? `file-${String(cursorIndex)}` : undefined}
523524
>
524525
<!-- Spacer div provides accurate scrollbar for full list width -->

apps/desktop/src/lib/file-explorer/views/FullList.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@
379379

380380
<div class="full-list-container" class:is-focused={isFocused} class:is-compact={isCompact}>
381381
<!-- Header row with sortable columns (outside scroll container for correct height calculation) -->
382-
<div class="header-row" style="grid-template-columns: 16px 1fr 60px 115px {dateColumnWidth}px;">
382+
<div class="header-row" role="toolbar" aria-label="Sort columns" style="grid-template-columns: 16px 1fr 60px 115px {dateColumnWidth}px;">
383383
<span class="header-icon"></span>
384384
<SortableHeader
385385
column="name"
@@ -419,6 +419,7 @@
419419
onscroll={handleScroll}
420420
tabindex="-1"
421421
role="listbox"
422+
aria-label="File list"
422423
aria-activedescendant={cursorIndex >= 0 ? `file-${String(cursorIndex)}` : undefined}
423424
>
424425
<!-- Spacer div provides accurate scrollbar for full list size -->

apps/desktop/src/lib/settings/components/SettingNumberInput.svelte

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
const { id, disabled = false, unit = '' }: Props = $props()
2020
2121
const definition = getSettingDefinition(id)
22+
const label = definition?.label ?? id
2223
const min = definition?.constraints?.min ?? 0
2324
const max = definition?.constraints?.max ?? 999999
2425
const step = definition?.constraints?.step ?? 1
@@ -42,9 +43,9 @@
4243
<div class="number-input-wrapper">
4344
<NumberInput.Root value={String(value)} onValueChange={handleChange} {min} {max} {step} {disabled}>
4445
<NumberInput.Control class="number-control">
45-
<NumberInput.DecrementTrigger class="number-btn">−</NumberInput.DecrementTrigger>
46-
<NumberInput.Input class="number-input" />
47-
<NumberInput.IncrementTrigger class="number-btn">+</NumberInput.IncrementTrigger>
46+
<NumberInput.DecrementTrigger class="number-btn" aria-label="Decrease {label}">−</NumberInput.DecrementTrigger>
47+
<NumberInput.Input class="number-input" aria-label={label} />
48+
<NumberInput.IncrementTrigger class="number-btn" aria-label="Increase {label}">+</NumberInput.IncrementTrigger>
4849
</NumberInput.Control>
4950
</NumberInput.Root>
5051

0 commit comments

Comments
 (0)