fix(a11y): replace aria-disabled div with native button disabled in DisabledLinkRow #1153
No reviewers
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/agent-hooks!1153
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "dev/1149"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
<div aria-disabled="true">with<button type="button" disabled>inDisabledLinkRowdisabledattribute prevents Tab focus and is announced correctly by screen readers (VoiceOver/NVDA) as "dimmed/unavailable"w-fullandcursor-defaultTailwind classes to preserve visual appearanceTest plan
Closes #1149
Review — fix(a11y): replace
aria-disableddiv with nativebutton disabledinDisabledLinkRowCI: ✅ green. The direction is correct —
aria-disabledon a non-interactive<div>has no semantic meaning (the attribute is only meaningful on interactive roles), so moving to a native<button disabled>is the right fix. Two issues need addressing before merge.🔴 Missing
disabled:opacity-100guardBrowsers apply UA-level styling to
button:disabled. Chrome, for example, sets a reducedcoloron disabled buttons. While Tailwind's utility classes win that specificity fight (sotext-text-dimholds), some browsers also reduce overallopacityon disabled form controls. Tailwind Preflight does not neutralise disabled-button opacity, so a browser that applies e.g.opacity: 0.5will stack on top oftext-text-dim, making the row visually double-dimmed compared to the original<div>.Please add
disabled:opacity-100to the className to make the intended opacity explicit and browser-proof:🔴 Test plan is entirely unchecked
All three test plan items are left unchecked (
- [ ]), including "Visual appearance unchanged" — the most critical one for a div-to-button swap, precisely because buttons carry browser default styling. This PR should not be merged with an unverified test plan. Please:<div>rendering.✅ What's good
aria-disabled="true"on a<div>(a non-interactive ARIA role) never conveyed disabled state to screen readers; nativedisabledon a<button>does.disabledexcludes the element from Tab navigation;aria-disabledon a<div>never did (there was nothing to navigate to anyway, but the semantics are cleaner now).type="button"— prevents accidental form submission. Correct.w-full— necessary to stretch the button to full width as the previous block-level<div>did.cursor-default— intentional; avoids thenot-allowedcursor some browsers show on disabled buttons, which would look out of place for a purely informational "not yet available" row.ℹ️ Out of scope (pre-existing)
DisabledLinkRowrenders directly inside<Menu.Popup role="menu">without being wrapped in<Menu.Item>, so it lacksrole="menuitem". This means screen readers traversing the menu may encounter a button child without the expected menuitem role. Not introduced by this PR — same situation existed with the<div>— but worth a follow-up issue.789e3f29ed4ed86ee197Review — fix(a11y): replace aria-disabled div with native button disabled in DisabledLinkRow
Overview
Minimal, targeted fix that replaces a semantically incorrect
<div aria-disabled="true">with a native<button type="button" disabled>inDisabledLinkRow. All four acceptance criteria from #1149 are satisfied.Correctness ✅
disabledon<button>removes the element from the tab sequence in all browsers — more reliable than the previousaria-disabledon adivwhich required a separatetabIndex={-1}to achieve the same effect.<button disabled>is universally announced as "dimmed" / "unavailable" / "grayed" by VoiceOver and NVDA. The olddiv aria-disabledhad inconsistent AT support.w-fullis the right addition —<button>is inline-level by default so without it the element would shrink-wrap its content instead of filling the row.cursor-defaultcorrectly preserves the plain-pointer cursor the<div>had.type="button": correctly prevents any accidental form submission if the component is ever nested in a<form>.Minor notes (non-blocking)
text-leftabsent: Every other<button>in this menu (notifications,keyboard-shortcuts,logout) includestext-left. Withflex items-centerit has no visual effect here, but adding it would be consistent with the house style. Low priority.cursor-defaultvscursor-not-allowed:cursor-defaultis the right call given the issue spec ("visual appearance unchanged") — just noting it as a deliberate choice rather than an oversight.role="menuitem"not added (pre-existing, out of scope): The<button>sits insiderole="menu"without arole="menuitem". This was true of the original<div>too and is not introduced here. For a fully conformant ARIA menu the ideal pattern would berole="menuitem" aria-disabled="true" tabIndex={-1}, but that's a separate refactor and not required to close #1149.AC check
<div aria-disabled>replacedjust qa/ CIClean, well-scoped fix. LGTM.