Skip to content

fix(site): add aria-label to icon-only Back navigation links#26222

Open
tracyjohnsonux wants to merge 3 commits into
mainfrom
a11y/icon-back-link-aria-label
Open

fix(site): add aria-label to icon-only Back navigation links#26222
tracyjohnsonux wants to merge 3 commits into
mainfrom
a11y/icon-back-link-aria-label

Conversation

@tracyjohnsonux

Copy link
Copy Markdown
Contributor

Summary

Two icon-only "Back" navigation links in the topbars have no accessible name. The visible tooltip text is wired via Radix's aria-describedby, which provides a description, not an accessible name, so screen readers announce them as just "link" with no purpose.

This adds an aria-label to each link matching the visible tooltip text, following the existing aria-label convention already used in these same files (e.g. aria-label="Create File", aria-label="Daily usage").

WCAG 2.1 criteria addressed

  • SC 4.1.2 Name, Role, Value (Level A)
  • SC 2.4.4 Link Purpose (In Context) (Level A)

Changes

File Element Added
site/src/pages/WorkspacePage/WorkspaceTopbar.tsx Back-to-workspaces chevron link aria-label="Back to workspaces"
site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx Back-to-template chevron link aria-label="Back to the template"

No visual change. No behavior change for sighted users. aria-label is safe here because the elements have no visible text content (only an icon), so it is not overriding a visible name.

Why not rely on the existing Tooltip?

Radix UI's Tooltip wires aria-describedby from the trigger to the tooltip content. aria-describedby provides an accessible description, not an accessible name. WCAG 4.1.2 requires interactive controls to have a programmatically determinable name, and screen readers do not consistently announce descriptions, especially when no name is present. Adding aria-label gives the link a stable, programmatic name that exactly matches the visible tooltip text.


Created by Coder Agents on behalf of @tracyjohnsonux.

Two icon-only Back navigation links in WorkspaceTopbar and
TemplateVersionEditor had no accessible name. Their visible tooltip text
is wired via aria-describedby, which provides a description, not an
accessible name, so screen readers announce them as just "link".

Add aria-label matching the tooltip text to satisfy:
- WCAG 2.1 SC 4.1.2 Name, Role, Value (Level A)
- WCAG 2.1 SC 2.4.4 Link Purpose (In Context) (Level A)
@tracyjohnsonux

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Chat: Review in progress | View chat
Requested: 2026-06-10 16:27 UTC by @tracyjohnsonux
Spend: $8.85 / $100.00

deep-review v0.7.1 | Round 2 | a26c46a..6dbd0e5

Last posted: Round 2, 2 findings (2 P3), APPROVE. Review

Finding inventory

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P3 Author fixed (6dbd0e5) WorkspaceTopbar.tsx:124 No story asserts back link's accessible name R1 Bisky Yes
CRF-2 P3 Author fixed (6dbd0e5) TemplateVersionEditor.tsx:227 No story asserts back link's accessible name R1 Bisky Yes

Round log

Round 1

Panel (11 reviewers). Netero clean. 2 P3 from panel (both Bisky). 9 reviewers reported no findings. Reviewed against a26c46a..f36ba10.

Round 2

Panel (4 reviewers: Bisky, Mafuuu, Mafu-san, Razor). Netero clean, verified CRF-1 and CRF-2 fixes. All 4 panel reviewers confirmed fixes. No new code findings. Reviewed against a26c46a..6dbd0e5.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@coder-agents-review coder-agents-review Bot left a comment

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.

Clean, well-scoped accessibility fix. Both aria-label values match their TooltipContent text exactly, satisfying WCAG 2.5.3 (Label in Name). The PR correctly identifies the only two icon-only TopbarIconButton links in the codebase and fixes both; all other ChevronLeftIcon usages include visible text and already have accessible names. The WCAG analysis in the PR description is accurate and the fix follows existing aria-label conventions in both files.

Severity: 2 P3.

"I tried to build a case against this change and couldn't."

🤖 This review was automatically generated with Coder Agents.

<TooltipTrigger asChild>
<TopbarIconButton asChild>
<RouterLink to="/workspaces">
<RouterLink to="/workspaces" aria-label="Back to workspaces">

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.

P3 [CRF-1] No story asserts this link's accessible name. WorkspaceTopbar.stories.tsx already has play functions for other stories, but the Example story has no assertion verifying getByRole('link', { name: 'Back to workspaces' }). A one-line check would prove the fix works and catch the exact regression this PR exists to prevent. The pattern is established elsewhere (AgentsPageView.stories.tsx, SecretsPageView.stories.tsx).

Sketch:

export const Example: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    await expect(canvas.getByRole("link", { name: "Back to workspaces" })).toBeVisible();
  },
};

🤖

<RouterLink to={templateLink}>
<RouterLink
to={templateLink}
aria-label="Back to the template"

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.

P3 [CRF-2] Same gap: the Example story in TemplateVersionEditor.stories.tsx renders this back link but has no play function asserting getByRole('link', { name: 'Back to the template' }). This file has zero play functions across all stories, but this would be the natural place to start; it directly tests the fix.

Sketch:

export const Example: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    await expect(canvas.getByRole("link", { name: "Back to the template" })).toBeVisible();
  },
};

🤖

Add play function assertions to the Example stories for WorkspaceTopbar
and TemplateVersionEditor that verify the icon-only Back links are
reachable by their accessible name, locking in the WCAG 4.1.2 fix.

Addresses CRF-1 and CRF-2 from coder-agents-review on #26222.
@tracyjohnsonux

Copy link
Copy Markdown
Contributor Author

Addressed CRF-1 and CRF-2: added play function assertions to the Example stories in both WorkspaceTopbar.stories.tsx and TemplateVersionEditor.stories.tsx that verify the icon-only Back links are reachable by their accessible name via getByRole("link", { name: ... }). Both stories pass under vitest --project=storybook locally.

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

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.

Both R1 findings addressed. CRF-1 and CRF-2 verified fixed: each Example story now asserts getByRole("link", { name: "..." }) with the exact aria-label text. The assertions are non-vacuous (they would fail without the aria-label) and follow the project's Storybook testing conventions.

All 4 R2 panel reviewers and Netero independently confirmed the fixes. All PR claims re-verified against the code: aria-labels match tooltip text exactly, the two links are the only icon-only TopbarIconButton instances in the codebase, and the WCAG analysis is accurate.

Process note: the initial submission omitted story assertions for an accessibility change despite site/AGENTS.md requiring Storybook stories for accessibility behavior. The correction was swift, complete, and used the right pattern.

🤖 This review was automatically generated with Coder Agents.

const canvas = within(canvasElement);
await expect(
canvas.getByRole("link", { name: "Back to the template" }),
).toBeVisible();

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.

Check attribute too:

        await expect(backLink).toHaveAttribute(
			"aria-label",
			"Back to the template",
		);

const canvas = within(canvasElement);
await expect(
canvas.getByRole("link", { name: "Back to workspaces" }),
).toBeVisible();

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.

Same as above, check the attribute too

@linear-code

linear-code Bot commented Jun 11, 2026

Copy link
Copy Markdown

PRODUCT-396

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants