Skip to content

fix(Clickable): allow aria-haspopup to accept ARIA popup tokens, not just boolean - #3509

Merged
rivka-ungar merged 3 commits into
masterfrom
a11y/clickable-aria-haspopup-token
Sep 8, 2026
Merged

rivka-ungar merged 3 commits into
masterfrom
a11y/clickable-aria-haspopup-token

Conversation

@rivka-ungar

Copy link
Copy Markdown
Contributor

What & why

Clickable typed aria-haspopup as boolean, so consumers could not type-legally pass the literal "dialog" token. A Clickable that opens a dialog was announced by assistive tech as having a menu popup instead of a dialog.

WCAG: 4.1.2 Name, Role, Value (Level A).

Monday task: https://monday.monday.com/boards/18428773755/pulses/12987714364

Change

Widen the prop type in ClickableProps:

-  "aria-haspopup"?: boolean;
+  "aria-haspopup"?: React.AriaAttributes["aria-haspopup"];

This matches the pattern already used by siblings in this monorepo — Search (React.AriaAttributes["aria-haspopup"]), Button and IconButton (React.HTMLProps<HTMLButtonElement>["aria-haspopup"]). The AriaAttributes form is used because Clickable renders an arbitrary elementType (default div), not a button. The "dialog" token is already used internally by Vibe (Info, all three Dropdown triggers), so it's an established in-tree pattern.

The runtime coercion (!!D) that older builds had is already gone at HEAD — the value passes through uncoerced. This PR fixes the remaining defect: the type.

Added 3 tests ("dialog" renders verbatim; boolean true still renders "true"; attribute omitted when not passed) and a WithDialogPopup Storybook story.

Non-breaking

Type-only widening. The new union is a strict superset of boolean, the default stays undefined, and no runtime line changed — every currently-legal call site stays legal. A runtime opt-in prop can't gate a TypeScript type, so none was added.

Testing

  • lerna run build --scope=@vibe/clickable --include-dependencies → 3 projects succeeded.
  • yarn workspace @vibe/clickable test → 2 files, 9 tests passed (6 pre-existing + 3 new). Existing snapshots unchanged.
  • yarn workspace @vibe/clickable lint → clean.

Notes for reviewers (out of scope, worth follow-up items)

  • 3.x backport: the runtime !!D coercion is still live in 3.88.0 / 3.89.0-alpha (which the downstream consumer pins). This diff is against 4.x and fixes the type only; on 3.x the value would still coerce to true at runtime. Either a 3.x backport of the runtime fix or a consumer move to 4.x is needed — a release-owner call.
  • Same too-narrow boolean typing exists at Chips.tsx:115 (default false), and MenuButton.tsx:377 hardcodes aria-haspopup="true". Neither is this item's defect; flagging so the package fix isn't left half-applied.

🤖 Generated with Claude Code

…just boolean

Clickable typed aria-haspopup as boolean, so consumers could not pass the
literal "dialog" token — a Clickable opening a dialog was announced as
having a menu popup (WCAG 4.1.2). Widen the type to
React.AriaAttributes["aria-haspopup"], matching the existing pattern in
Search/Button/IconButton. Type-only widening (strict superset of boolean),
no runtime change; the value already passes through uncoerced at HEAD.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@rivka-ungar
rivka-ungar requested a review from a team as a code owner September 8, 2026 11:55
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Type changes stay in the implementation file 📘 Rule violation ⚙ Maintainability
Description
ClickableProps is maintained in Clickable.tsx instead of a dedicated *.types.ts file. Changing
the interface here keeps future prop changes split from the repository's expected type location and
leaves the component without a dedicated type file.
Code

packages/components/clickable/src/Clickable/Clickable.tsx[R50-54]

+   * Indicates the presence and type of popup associated with the element.
+   * Accepts the ARIA popup tokens (e.g. `"dialog"`, `"menu"`, `"listbox"`) in addition to a boolean,
+   * so assistive technology announces the correct popup type rather than defaulting to "menu".
  */
-  "aria-haspopup"?: boolean;
+  "aria-haspopup"?: React.AriaAttributes["aria-haspopup"];
Evidence
The changed ClickableProps declaration remains in Clickable.tsx, while the compliance rule
requires component interfaces to be maintained in a dedicated *.types.ts file and extend
VibeComponentProps. The repository contains no *.types.ts file under the Clickable component
directory.

CLAUDE.md: Component Types Must Extend VibeComponentProps: CLAUDE.md: Component Types Must Extend VibeComponentProps
packages/components/clickable/src/Clickable/Clickable.tsx[50-54]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ClickableProps` is defined in the implementation file rather than a dedicated `*.types.ts` file, contrary to the component type organization requirement.
## Issue Context
Preserve the existing public API while moving the interface and updating imports or exports as needed.
## Fix Focus Areas
- packages/components/clickable/src/Clickable/Clickable.tsx[50-54]
- packages/components/clickable/src/Clickable/Clickable.types.ts[1-1]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can tweak Display preferences with a live preview to see your comment before it ships

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Support ARIA popup tokens in Clickable

🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 10-20 Minutes

Grey Divider

AI Description

• Widen Clickable's aria-haspopup type to support standard ARIA popup tokens.
• Preserve popup tokens while retaining boolean and omitted-value behavior.
• Add regression tests and a dialog-popup Storybook example.
Diagram

graph TD
  Consumer["Consumer props"] -->|dialog token| Types["ClickableProps"] --> Component["Clickable"] --> Hook["useClickableProps"] --> DOM["Rendered element"] -->|popup semantics| AT["Assistive tech"]
Loading
High-Level Assessment

The chosen approach is optimal: React.AriaAttributes provides the canonical ARIA token union and fits Clickable's arbitrary element type. A custom union would duplicate React definitions, while button-specific HTMLProps would incorrectly couple Clickable to a button element.

Files changed (3) +36 / -2

Bug fix (1) +4 / -2
Clickable.tsxAllow standard ARIA popup tokens +4/-2

Allow standard ARIA popup tokens

• Widens 'aria-haspopup' from boolean-only to React's canonical ARIA attribute type. Expanded documentation explains popup tokens and their assistive-technology impact.

packages/components/clickable/src/Clickable/Clickable.tsx

Tests (1) +15 / -0
Clickable.test.tsxCover aria-haspopup rendering behavior +15/-0

Cover aria-haspopup rendering behavior

• Adds regression coverage confirming that 'dialog' passes through verbatim, boolean 'true' remains supported, and the attribute is omitted by default.

packages/components/clickable/src/Clickable/tests/Clickable.test.tsx

Documentation (1) +17 / -0
Clickable.stories.tsxDemonstrate a dialog popup trigger +17/-0

Demonstrate a dialog popup trigger

• Adds a Storybook example showing Clickable configured with 'aria-haspopup="dialog"' and a corresponding expanded state.

packages/docs/src/pages/components/Clickable/Clickable.stories.tsx

@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 7dd759f

Comment on lines +50 to +54
* Indicates the presence and type of popup associated with the element.
* Accepts the ARIA popup tokens (e.g. `"dialog"`, `"menu"`, `"listbox"`) in addition to a boolean,
* so assistive technology announces the correct popup type rather than defaulting to "menu".
*/
"aria-haspopup"?: boolean;
"aria-haspopup"?: React.AriaAttributes["aria-haspopup"];

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.

Remediation recommended

1. Type changes stay in the implementation file 📘 Rule violation ⚙ Maintainability

ClickableProps is maintained in Clickable.tsx instead of a dedicated *.types.ts file. Changing
the interface here keeps future prop changes split from the repository's expected type location and
leaves the component without a dedicated type file.
Agent Prompt
## Issue description
`ClickableProps` is defined in the implementation file rather than a dedicated `*.types.ts` file, contrary to the component type organization requirement.

## Issue Context
Preserve the existing public API while moving the interface and updating imports or exports as needed.

## Fix Focus Areas
- packages/components/clickable/src/Clickable/Clickable.tsx[50-54]
- packages/components/clickable/src/Clickable/Clickable.types.ts[1-1]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 600817f

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Size Analysis

✅ No bundle size changes detected.

Unchanged Components
Component Base PR Diff
@vibe/a11y 5.36KB 5.35KB -7B 🟢
@vibe/accordion 30.28KB 30.31KB +29B 🔺
@vibe/alert-banner 35.25KB 35.17KB -86B 🟢
@vibe/attention-box 37.47KB 37.36KB -121B 🟢
@vibe/avatar-group 55.21KB 55.02KB -189B 🟢
@vibe/avatar 30.14KB 30.09KB -55B 🟢
@vibe/badge 7.75KB 7.75KB -4B 🟢
@vibe/base-list 34.41KB 34.38KB -24B 🟢
@vibe/breadcrumbs 44.62KB 44.51KB -114B 🟢
@vibe/button-group 31.66KB 31.66KB -2B 🟢
@vibe/button 14.89KB 14.84KB -55B 🟢
@vibe/checkbox 30.33KB 30.3KB -32B 🟢
@vibe/chips 38.08KB 38.05KB -28B 🟢
@vibe/clickable 3.63KB 3.59KB -43B 🟢
@vibe/color-picker 37.4KB 37.36KB -37B 🟢
@vibe/combobox 46.74KB 46.67KB -68B 🟢
@vibe/counter 6.92KB 6.89KB -32B 🟢
@vibe/date-picker 75.89KB 75.84KB -47B 🟢
@vibe/dialog 17.69KB 17.59KB -104B 🟢
@vibe/divider 2.82KB 2.81KB -8B 🟢
@vibe/dropdown 58.85KB 58.66KB -195B 🟢
@vibe/editable 30.53KB 30.41KB -121B 🟢
@vibe/empty-state 33.79KB 33.72KB -75B 🟢
@vibe/expand-collapse 29.7KB 29.62KB -83B 🟢
@vibe/formatted-number 3.29KB 3.26KB -27B 🟢
@vibe/icon-button 31.56KB 31.54KB -25B 🟢
@vibe/icon 10.51KB 10.48KB -30B 🟢
@vibe/info 35.24KB 35.25KB +11B 🔺
@vibe/label 31.95KB 31.91KB -45B 🟢
@vibe/layer 412B 412B 0B ➖
@vibe/layout 7.49KB 7.51KB +14B 🔺
@vibe/link 11.47KB 11.46KB -16B 🟢
@vibe/list 39.99KB 39.92KB -70B 🟢
@vibe/loader 3.26KB 3.25KB -14B 🟢
@vibe/menu-button 29.06KB 29.04KB -21B 🟢
@vibe/menu 45.5KB 45.42KB -86B 🟢
@vibe/modal 47.91KB 47.8KB -107B 🟢
@vibe/progress-bars 4.36KB 4.34KB -21B 🟢
@vibe/radio-button 29.4KB 29.35KB -49B 🟢
@vibe/search 33.88KB 33.87KB -4B 🟢
@vibe/skeleton 3.41KB 3.4KB -14B 🟢
@vibe/slider 36.78KB 36.72KB -62B 🟢
@vibe/split-button 32.31KB 32.29KB -20B 🟢
@vibe/table 47.21KB 47.3KB +89B 🔺
@vibe/tabs 30.32KB 30.24KB -84B 🟢
@vibe/text-inputs 39.74KB 39.72KB -13B 🟢
@vibe/text-with-highlight 27.89KB 27.8KB -91B 🟢
@vibe/theme-provider 1.45KB 1.43KB -24B 🟢
@vibe/tipseen 37.31KB 37.23KB -76B 🟢
@vibe/toast 37.14KB 37.01KB -125B 🟢
@vibe/toggle 30.1KB 30.04KB -63B 🟢
@vibe/tooltip 26.78KB 26.73KB -43B 🟢
@vibe/transitions 3.02KB 3KB -23B 🟢
@vibe/typography 28.92KB 28.84KB -86B 🟢
@vibe/virtualized-grid 9.65KB 9.58KB -81B 🟢
@vibe/virtualized-list 9.34KB 9.34KB -4B 🟢
@vibe/wizard 37.67KB 37.66KB -10B 🟢

📊 Summary:

  • Total Base Size: 1.51MB
  • Total PR Size: 1.5MB
  • Total Difference: 2.66KB

@rivka-ungar
rivka-ungar merged commit 708e2fa into master Sep 8, 2026
17 checks passed
@rivka-ungar
rivka-ungar deleted the a11y/clickable-aria-haspopup-token branch September 8, 2026 12:48
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