Skip to content

a11y(2.1.1): ZoomSvg — add keyboard pan/zoom controls so the workflow family tree is operable without a mouse#3530

Merged
ardiewen merged 4 commits into
mainfrom
wcag/2.1.1-zoom-svg-pan-zoom-keyboard
Jun 22, 2026
Merged

a11y(2.1.1): ZoomSvg — add keyboard pan/zoom controls so the workflow family tree is operable without a mouse#3530
ardiewen merged 4 commits into
mainfrom
wcag/2.1.1-zoom-svg-pan-zoom-keyboard

Conversation

@rosanusi

Copy link
Copy Markdown
Contributor

Description & motivation 💭

ZoomSvg is the pan/zoom container that hosts the workflow family tree. Pan was bound to mouse drag only; zoom was bound to the mouse wheel only. The sole keyboard-reachable control was the Center reset button. Keyboard users could not reposition or zoom the tree — a real barrier when the tree has multiple generations or wide sibling sets and nodes fall off-viewport.

This PR adds three things:

  1. panBy / zoomBy helpers — shared viewport-mutation functions, reusable by both mouse and keyboard paths.
  2. Button cluster — Pan Up / Down / Left / Right and Zoom In / Out buttons alongside the existing Center button, each gated by the existing pannable / zoomable props. Zoom buttons become disabled at the maxZoomIn / maxZoomOut clamp boundary.
  3. Container keyboard shortcutstabindex="0" container with on:keydown; arrow keys pan and +/- zoom only when the container is focused (scoped to the element, never global).
  4. plus / minus icons — added to the icon system (src/lib/holocene/icon/svg/), registered in paths.ts.

Mouse drag, wheel zoom, and the existing Center button are all unchanged.

Closes SC 2.1.1, 2.5.1, and 2.5.7 — one fix resolves all three:

SC Defect Closed?
2.1.1 Keyboard (A) No keyboard alternative for drag-to-pan / wheel-to-zoom ✅ Tab-reachable buttons + container arrow-key shortcuts
2.5.1 Pointer Gestures (A) Drag-to-pan and wheel-to-zoom have no single-pointer alternative ✅ Pan/Zoom buttons are single-tap controls
2.5.7 Dragging Movements (AA) Pan requires a drag gesture, no single-pointer alternative ✅ Same buttons close 2.5.7

Files changed (4):

  • src/lib/holocene/zoom-svg.svelte — all logic and template changes
  • src/lib/holocene/icon/svg/plus.svelte — new icon
  • src/lib/holocene/icon/svg/minus.svelte — new icon
  • src/lib/holocene/icon/paths.ts — registers plus and minus

cloud-ui inherits the fix via the @temporalio/ui tarball repack.

Closes matrix defect (d) recorded at audit-output/vpat-matrix.md:119.

Screenshots (if applicable) 📸

New button cluster appears at top-right of the ZoomSvg container alongside the existing Center button: Pan Up ↑, Pan Down ↓, Pan Left ←, Pan Right →, Zoom In +, Zoom Out −, Center ⊙.

Design Considerations 🎨

Uses the existing Button variant="secondary" size="sm" pattern matching the Center button. No new design tokens. Button order in the cluster: pan directions first, zoom second, center last.

Testing 🧪

How was this tested 👻

  • Manual testing

Steps for others to test: 🚶🏽‍♂️🚶🏽‍♀️

  1. Open a workflow with multiple child workflows in its family tree (/namespaces/{ns}/workflows/{wf}/{run}/relationships).
  2. Tab into the ZoomSvg container. Confirm it is reachable and announced as Zoomable workflow graph…, group.
  3. Tab through the controls cluster. Confirm Pan Up, Pan Down, Pan Left, Pan Right, Zoom In, Zoom Out, Center are each focus stops in that order.
  4. Activate each Pan button with Enter and Space. Confirm the viewport moves ~10% in the expected direction.
  5. Activate Zoom In repeatedly until the button becomes disabled. Confirm the boundary clamp is respected.
  6. Activate Zoom Out repeatedly until disabled. Same check at maxZoomOut.
  7. Focus the outer container <div> itself. Press Arrow keys — confirm the viewport pans. Press + / - — confirm zoom. Confirm these shortcuts only fire when the container is focused (Tab away and press arrow keys; should have no effect on the graph).
  8. Regression: mouse drag still pans, wheel still zooms, Center still resets.
  9. Screen reader (VoiceOver): confirm container announcement includes the aria-label keyboard description; confirm each button announces as Pan up, button etc.
  10. axe-core on the relationships page: zero new violations.

Checklists

Draft Checklist

  • Visual review of button cluster placement in the UI
  • VoiceOver smoke test on container and each button
  • Cross-browser: Firefox + Safari (wheel-zoom and arrow-key pan)

Merge Checklist

  • All pan directions and zoom levels tested with keyboard
  • disabled state at clamp boundaries verified
  • Mouse regression confirmed
  • axe-core: zero new violations

Issue(s) closed

Closes finding #10 from audit-output/issues/2.1.1-keyboard-audit.md.
Closes matrix defect (d) at audit-output/vpat-matrix.md:119.
Also closes 2.5.1 and 2.5.7 angles per audit-output/issues/2.5.1-pointer-gestures-verification.md §1.4.

Docs

Any docs updates needed?

No doc changes needed.

A11y-Audit-Ref: 2.1.1-zoom-svg-pan-zoom-keyboard

…2.5.1, 2.5.7)

Pan was mouse-drag only; zoom was wheel only — no keyboard or single-pointer
alternative. Adds panBy/zoomBy helpers, a tab-reachable button cluster (Pan
Up/Down/Left/Right, Zoom In/Out, Center), and arrow-key/+/- shortcuts on
the focused container. Mouse paths are unchanged. Also adds plus/minus icons.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
holocene Ready Ready Preview, Comment Jun 19, 2026 5:26pm

Request Review

@github-actions github-actions Bot added a11y Accessibility audit PR a11y:bucket-3 Bucket 3: engineer required a11y:sc-2.1.1 labels Jun 10, 2026
@temporal-cicd

temporal-cicd Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor
Warnings
⚠️

📊 Strict Mode: 4 errors in 1 file (0.4% of 897 total)

src/lib/holocene/zoom-svg.svelte (4)
  • L17:6: Variable 'svg' implicitly has type 'any' in some locations where its type cannot be determined.
  • L54:17: Variable 'svg' implicitly has an 'any' type.
  • L79:59: Variable 'svg' implicitly has an 'any' type.
  • L80:60: Variable 'svg' implicitly has an 'any' type.

Generated by 🚫 dangerJS against 93faee6

@rosanusi
rosanusi marked this pull request as ready for review June 11, 2026 15:52
@rosanusi
rosanusi requested a review from a team as a code owner June 11, 2026 15:52
@ardiewen

Copy link
Copy Markdown
Contributor

@rosanusi The controls bar from the one call site (workflow-family-tree.svelte) is 520px in width regardless of screen size. This overflows on smaller width viewports with the default controls:

Screenshot 2026-06-18 at 10 16 34 AM

This problem will become exasperated if there are future call sites that add more than just "expand all" into the controls bar.

let zoomLevel = initialZoom;
let panX = 0;
let panY = 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ Variable 'svg' implicitly has type 'any' in some locations where its type cannot be determined.

@@ -68,8 +80,8 @@
const dx = (startX - event.clientX) * (viewBox.width / svg.clientWidth);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ Variable 'svg' implicitly has an 'any' type.

@ardiewen

Copy link
Copy Markdown
Contributor

@rosanusi Updated per design feedback:

Narrow / mobile viewports:
Screenshot 2026-06-19 at 1 24 17 PM

Wide / desktop viewports:
Screenshot 2026-06-19 at 1 23 04 PM

@ardiewen
ardiewen merged commit f365416 into main Jun 22, 2026
18 checks passed
@ardiewen
ardiewen deleted the wcag/2.1.1-zoom-svg-pan-zoom-keyboard branch June 22, 2026 12:53
laurakwhit added a commit that referenced this pull request Jun 23, 2026
Auto-generated version bump from 2.51.0 to 2.51.1

Specific version: 2.51.1

Changes included:
- [`c848ec54`](c848ec5) fix(a11y): announce workflow / event status on timeline graph nodes (WCAG 1.4.1) (#3443)
- [`af6b12a9`](af6b12a) fix(deployments): align version/status/compute badges in worker deployments table (#3489)
- [`df86587c`](df86587) Add SDK version to standalone activity details (#3447)
- [`59695290`](5969529) Use shared FilterBar component for workflows (#3448)
- [`ded94469`](ded9446) Set display contents in wrapper div if no afterLabel in Input component (#3513)
- [`d7e5eb43`](d7e5eb4) fix(standalone-activities): decode failure encodedAttributes in activity detail (#3507)
- [`81a255a9`](81a255a) fix(DT-4151): fix bottom nav scrollability and double-menu on mobile (#3514)
- [`368f646e`](368f646) Fix exports to work properly with vite8 (#3519)
- [`231bdaff`](231bdaf) chore(deps-dev): bump vitest from 3.2.4 to 3.2.6 (#3509)
- [`1de44ea6`](1de44ea) fix(workers): soften no-workers-polling alert for serverless worker deployments (#3524)
- [`689955a8`](689955a) fix(DT-4080): surface error when validate connection has no compute config, warn on set current (#3516)
- [`93323db9`](93323db) feat(DT-4078): Add Set Ramping Version to Worker Deployment Versions (#3508)
- [`9d3450c0`](9d3450c) fix(DT-4081): surface task queue not registered error in validate connection modal (#3517)
- [`b7f56237`](b7f5623) feat(DT-4079): miscellaneous WD UI fixes (#3511)
- [`83b2d4ec`](83b2d4e) feat(deployments): simplify create worker deployment UX (#3518)
- [`ccb4cb73`](ccb4cb7) Standalone Activity write guards (#3534)
- [`ab20ef03`](ab20ef0) Worker insights UI updates (#3520)
- [`aaf63bdc`](aaf63bd) [wcag] Replace text-[12px] with text-xs in DatePicker action buttons (SC 1.4.4) (#3525)
- [`743be902`](743be90) a11y(2.4.3): replace flex-wrap-reverse with flex-wrap on workflow history toolbar (#3527)
- [`41b73e87`](41b73e8) a11y(2.4.2): ui-main — set distinct, descriptive titles on login/signin, task-queues list, and activity-workers sub-tab (#3535)
- [`3d64cdbf`](3d64cdb) [WCAG 2.4.3] group-details-row — remove redundant outer focus stop on <g> wrapper (#3537)
- [`883e5db1`](883e5db) a11y(2.4.6): datepicker — replace empty label with translate('common.start') (#3543)
- [`9da915e2`](9da915e) a11y(2.4.5): import history — add back-link to Import landing page (#3542)
- [`ca4c1d61`](ca4c1d6) Cache new Intl.DateTimeFormat (#3565)
- [`38376e4d`](38376e4) Fix required activity timeout inputs on Start a Standalone Activity form (#3567)
- [`49d7c9e4`](49d7c9e) Use separate copy for title vs menu item (#3568)
- [`3c7dcdc3`](3c7dcdc) a11y(2.5.8): pagination — grow caret buttons from 12×12 to 24×24 CSS px (#3547)
- [`b5171825`](b517182) a11y(2.5.8): chip — pad remove button to 24×24 CSS px minimum (#3544)
- [`1fea5d5c`](1fea5d5) a11y(2.1.1): fake-button widgets — replace deprecated onkeypress with onkeydown handling Enter and Space (#3528)
- [`5bf09000`](5bf0900) a11y(2.1.1): navigation primitives — replace hover-only CSS :after label tooltip with focusable Tooltip (#3529)
- [`deed43f0`](deed43f) a11y(2.1.1): event summary row — add keyboard-accessible expand control alongside the mouse-only <tr onclick> (#3526)
- [`d401123e`](d401123) a11y(2.5.8): checkbox — add min-h-6 min-w-6 to Label for 24×24 hit area (#3545)
- [`aef4166f`](aef4166) [a11y] Expose toggled state via aria-pressed on ToggleButton (#3559)
- [`797f8831`](797f883) Show single attempt in Standalone Activity details (#3572)
- [`893bc58c`](893bc58) feat(DT-4183): Add Tabs (Primitive) (#3574)
- [`323c1993`](323c199) Date picker input field bug/kt (#3522)
- [`3ac34ce2`](3ac34ce) Various serverless UI updates (#3573)
- [`f365416a`](f365416) a11y(2.1.1): ZoomSvg — add keyboard pan/zoom controls so the workflow family tree is operable without a mouse (#3530)
- [`feb6897e`](feb6897) Fix gaps in slot cards on worker details page (#3580)
- [`b3c80072`](b3c8007) Remove font weight on cache size (#3583)
- [`0e7337ea`](0e7337e) Bump API to v1.62.14 (#3575)

Co-authored-by: laurakwhit <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a11y:bucket-3 Bucket 3: engineer required a11y:sc-2.1.1 a11y Accessibility audit PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants