Comprehensive accessibility design covering all four WCAG principles (Perceivable, Operable, Understandable, Robust) for the Driftwood GTK4/libadwaita AppImage manager.
12 KiB
WCAG 2.2 AAA Compliance Design for Driftwood
Context
Driftwood is a GTK4/libadwaita AppImage manager. The app already uses semantic libadwaita widgets (ActionRow, PreferencesGroup, SwitchRow, NavigationView) and has partial accessibility support: ~11 accessible labels, status badges with text+color, keyboard shortcuts, and reduced-motion CSS. However, it falls short of full WCAG 2.2 AAA compliance.
This design covers every change needed to achieve AAA compliance across all four WCAG principles: Perceivable, Operable, Understandable, and Robust.
Approach: Hybrid Helpers + Direct Properties
Create centralized accessibility helper functions in widgets.rs for patterns that repeat (labeled buttons, described badges, live announcements), then add direct accessible properties for unique cases in each UI file. This keeps the code DRY while ensuring nothing is missed.
Scope
In scope: All UI code in src/ui/, src/window.rs, and data/resources/style.css.
Out of scope: CLI (src/cli.rs), core backend modules, build system.
1. Perceivable (WCAG 1.x)
1.1 Non-text Content (1.1.1 - Level A)
Current state: 8 icon-only buttons, some with accessible labels, some without.
Changes needed:
- Add
update_property(&[AccessibleProperty::Label(...)])to every icon-only button:library_view.rs: menu button, search button, grid button, list buttonduplicate_dialog.rs: delete button per rowwidgets.rs: copy buttonpreferences.rs: remove directory button
- Add accessible descriptions to integration emblem overlay in
app_card.rs - Add accessible labels to all
gtk::Imageicons used as prefixes in rows (check icons in integration_dialog, category icons in cleanup_wizard)
1.2 Time-based Media - N/A (no audio/video)
1.3 Adaptable
1.3.1 Info and Relationships (A):
- Add
AccessibleRole::Groupto badge boxes in detail_view, library_view list rows, and app_card - Add
AccessibleProperty::Labelto ListBox containers in library_view (list view), preferences (directory list, cleanup wizard lists) - Add
AccessibleRelation::LabelledByconnecting PreferencesGroup titles to their child row containers where applicable
1.3.6 Identify Purpose (AAA):
- Set
AccessibleRole::Banneron the detail view banner - Set
AccessibleRole::Navigationon the NavigationView wrapper - Set
AccessibleRole::Searchon the search bar - Set
AccessibleRole::Statuson status badges - Set
AccessibleRole::Mainon the main content area
1.4 Distinguishable
1.4.6 Enhanced Contrast (AAA - 7:1 ratio):
- Add a
prefers-contrast: moremedia query instyle.cssfor high-contrast mode - In high-contrast mode: solid opaque borders on app cards (no alpha), bolder status badge colors, thicker focus rings (3px)
- Verify that libadwaita theme variables meet 7:1 in both light and dark mode (they do - libadwaita's named colors are designed for WCAG AA, and the
prefers-contrast: morevariant handles AAA)
1.4.8 Visual Presentation (AAA):
- Text is already relative-sized (em units)
- libadwaita handles line height and spacing according to system preferences
- No changes needed beyond ensuring we do not override user-configured text spacing
1.4.11 Non-text Contrast (AA):
- Increase focus ring width to 3px (currently 2px) in high-contrast mode
- Ensure status badge borders are visible at 3:1 ratio against their background
2. Operable (WCAG 2.x)
2.1 Keyboard Accessible
2.1.3 Keyboard No Exception (AAA):
- Verify all dashboard actionable rows are keyboard-activatable (they use
activatable: true+action_name) - Verify cleanup wizard checkboxes are keyboard-toggleable via
activatable_widget - Add keyboard shortcut Ctrl+Q for quit (already exists in GNOME via app.quit)
- Ensure delete button in duplicate_dialog can be reached via Tab
2.2 Enough Time
2.2.4 Interruptions (AAA):
- Toast notifications already have timeouts and can be dismissed
- No other auto-updating content exists
2.3 Seizures and Physical Reactions
2.3.3 Animation from Interactions (AAA):
- Expand
prefers-reduced-motionCSS to cover ALL transitions:navigation viewslide transitions (currently only coversstack)adw::Dialogpresentation animationsflowboxchildhover/active transitions- Search bar reveal animation
- Spinner animations (already respected by adw::Spinner)
2.4 Navigable
2.4.7 Focus Visible (AA) + 2.4.13 Focus Appearance (AAA):
- Add focus-visible styles for ALL focusable elements, not just app cards:
button:focus-visible- 3px solid outline with accent colorrow:focus-visible- highlight with outlineswitch:focus-visiblecheckbutton:focus-visiblesearchentry:focus-visiblecomborow:focus-visiblespinrow:focus-visibleexpander:focus-visible
- Focus indicator must be at least 2px thick and have 3:1 contrast (AAA requires this)
2.4.8 Location (AAA):
- Update the window title dynamically to reflect current page: "Driftwood - Dashboard", "Driftwood - Security Report", "Driftwood - {App Name}"
- This is already partially done via NavigationPage titles; ensure the window's actual title property updates
2.5.8 Target Size (AA):
- Audit all clickable targets for minimum 24x24px
- The copy button, delete button (duplicate dialog), and remove directory button need
min-width: 24px; min-height: 24pxin CSS - Status badges in actionable rows are not click targets (the row is), so this is fine
3. Understandable (WCAG 3.x)
3.1 Readable
3.1.1 Language of Page (A):
- The GTK accessible layer reads the locale from the system. No explicit action needed for a native desktop app.
3.1.3 Unusual Words (AAA):
- Add
tooltip_textexplanations for technical terms displayed in the UI:- "FUSE" -> tooltip: "Filesystem in Userspace - required for mounting AppImages"
- "XWayland" -> tooltip: "X11 compatibility layer for Wayland desktops"
- "AppImage Type 1/2" -> tooltip: "Type 1 uses ISO9660, Type 2 uses SquashFS"
- "CVE" -> tooltip: "Common Vulnerabilities and Exposures - security vulnerability identifier"
- "SHA256" -> tooltip: "Cryptographic hash for verifying file integrity"
- "Firejail" -> tooltip: "Linux application sandboxing tool"
- "zsync" -> tooltip: "Efficient delta-update download protocol"
- "fusermount" -> tooltip: "User-space filesystem mount utility"
3.1.4 Abbreviations (AAA):
- Expand "CVE" to "CVE (vulnerability)" on first use in security report
- Expand "SHA256" to "SHA256 checksum" in detail view
3.1.5 Reading Level (AAA):
- Review all user-facing strings for plain language (most are already simple)
- Replace "No update information embedded" with "This app cannot check for updates automatically"
- Replace "AppImage does not contain update information" with "No automatic update support"
3.2 Predictable - Already compliant (no unexpected changes)
3.3 Input Assistance
3.3.5 Help (AAA):
- Add contextual descriptions to all PreferencesGroup widgets (already mostly done)
- Add
descriptiontext to any PreferencesGroup missing it
3.3.6 Error Prevention - All (AAA):
- Destructive actions already have confirmation (confirm-before-delete setting, alert dialogs)
- Add confirmation to bulk "Remove All Suggested" in duplicate dialog (currently executes immediately)
- Add confirmation to "Clean Selected" in cleanup wizard (currently executes immediately)
4. Robust (WCAG 4.x)
4.1.2 Name, Role, Value (A)
Accessible Names (all interactive elements): Every button, toggle, switch, row, and input must have an accessible name. The full list of items needing labels:
| Widget | File | Label to add |
|---|---|---|
| Menu button | library_view.rs | "Main menu" |
| Search toggle | library_view.rs | "Toggle search" |
| Grid view toggle | library_view.rs | "Switch to grid view" |
| List view toggle | library_view.rs | "Switch to list view" |
| Copy button | widgets.rs | "Copy to clipboard" |
| Delete button | duplicate_dialog.rs | "Delete this AppImage" |
| Remove directory | preferences.rs | "Remove scan directory" |
| Close button | cleanup_wizard.rs | "Close dialog" |
| Clean Selected | cleanup_wizard.rs | "Clean selected items" |
| Remove All Suggested | duplicate_dialog.rs | "Remove all suggested duplicates" |
| Add Location | preferences.rs | "Add scan directory" |
| Scan Now | library_view.rs | "Scan for AppImages" |
| Preferences | library_view.rs | "Open preferences" |
Accessible Roles:
FlowBox-> already has labelListBox(list view) -> addAccessibleProperty::Label("AppImage library list")ListBox(preferences directory list) -> add label "Scan directories"ListBox(cleanup items) -> add label per categoryListBox(integration dialog identity) -> add label "Application details"ListBox(integration dialog actions) -> add label "Integration actions"- Status badges -> add
AccessibleRole::Status(or useupdate_propertywithAccessibleProperty::Label)
Accessible States:
- Switch rows -> GTK handles this automatically via
SwitchRow - Scan button -> add
AccessibleState::Busywhile scanning is in progress - Security scan row -> add
AccessibleState::Busyduring scan - Analyze toolkit row -> add
AccessibleState::Busyduring analysis
4.1.3 Status Messages (AA)
Live region announcements for async operations:
Create a helper function announce(text: &str) in widgets.rs that uses a hidden GTK Label with AccessibleRole::Alert to broadcast status changes to screen readers.
Operations needing announcements:
- Scan start ("Scanning for AppImages...")
- Scan complete ("{n} AppImages found, {m} new")
- Update check start/complete
- Security scan start/complete
- Cleanup analysis start/complete
- Search results count change ("{n} results" or "No results")
Files Modified
| File | Changes |
|---|---|
data/resources/style.css |
Focus indicators for all widgets, high-contrast media query, reduced-motion expansion, target size minimums |
src/ui/widgets.rs |
New announce() live region helper, accessible label on copy_button, accessible role on status badges |
src/ui/library_view.rs |
Accessible labels on all header buttons, list box label, search results announcement, accessible roles |
src/ui/app_card.rs |
Accessible description for emblem overlay |
src/ui/detail_view.rs |
Accessible roles on banner, busy states on async rows, tooltips for technical terms, plain-language rewrites |
src/ui/dashboard.rs |
Tooltips for technical terms, accessible labels on any unlabeled elements |
src/ui/duplicate_dialog.rs |
Accessible label on delete buttons, confirmation before bulk remove, list box labels |
src/ui/cleanup_wizard.rs |
Accessible labels on buttons, confirmation before cleanup, list box labels, busy announcement |
src/ui/preferences.rs |
Accessible label on remove button and add button, list box labels |
src/ui/security_report.rs |
Accessible labels, tooltips for CVE terms |
src/ui/integration_dialog.rs |
List box labels, accessible descriptions |
src/ui/update_dialog.rs |
Plain-language rewrites |
src/window.rs |
Window title updates per page, live announcements for scan/update/clean operations |
Verification
After implementation:
cargo build- zero errors, zero warningscargo test- all tests pass- Run with Orca screen reader - verify every element is announced correctly
- Tab through entire app - verify all elements have visible focus indicators
- Set
GTK_THEME=Adwaita:dark- verify dark mode focus/contrast - Set high-contrast theme - verify enhanced contrast mode
- Set
GTK_DEBUG=interactive- inspect accessible tree - Keyboard-only navigation test through every screen
- Verify all targets meet 24x24px minimum
- Verify reduced-motion disables all animations