Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions openspec/changes/combobox-validation-a11y/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-06-16
109 changes: 109 additions & 0 deletions openspec/changes/combobox-validation-a11y/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
## Context

The combobox widget currently has two accessibility issues affecting screen reader users:

1. **Decorative icons announced as "image"**: The DownArrow and ClearButton SVG icons are exposed to assistive technologies, causing screen readers to announce them as "image" elements. These icons are purely decorative - they duplicate information already conveyed through the combobox role, button roles, and ARIA labels.

2. **Empty menu structure exposed**: When the combobox has no options, the `<ul>` menu element is still rendered in the DOM and exposed to the accessibility tree, creating an empty group that screen reader users can navigate to but provides no value.

**Current Implementation:**

- `icons.tsx`: DownArrow and ClearButton components render SVGs wrapped in `<span className="widget-combobox-icon-container">`
- `ComboboxWrapper.tsx`: Renders the DownArrow inside a container div
- `ComboboxMenuWrapper.tsx`: Always renders the `<ul>` element, showing NoOptionsPlaceholder when empty

**Constraints:**

- Must not affect visual presentation
- Must preserve existing keyboard and mouse interaction behavior
- Must maintain compatibility with existing ARIA attributes (aria-invalid, aria-describedby)

## Goals / Non-Goals

**Goals:**

- Hide decorative icon SVGs from assistive technologies using `aria-hidden="true"`
- Prevent empty menu structures from being exposed to screen readers when no options exist
- Maintain WCAG 2.2 AA compliance
- Preserve all existing functionality and visual design

**Non-Goals:**

- Not changing the validation ARIA attributes (already correctly implemented)
- Not modifying icon visual appearance or interaction behavior
- Not addressing other accessibility improvements beyond these two specific issues

## Decisions

### Decision 1: Add aria-hidden to icon wrapper spans

**Approach:** Add `aria-hidden="true"` to the `<span className="widget-combobox-icon-container">` wrapper in both DownArrow and ClearButton components.

**Rationale:**

- These icons are purely decorative - they reinforce information already available through:
- DownArrow: The combobox role implies expandability; the actual button has proper ARIA labels
- ClearButton: The button element has `aria-label` with clear text (from `clearButtonAriaLabel` prop)
- Adding aria-hidden on the wrapper span hides both the span and its SVG child from the accessibility tree
- This is simpler and more maintainable than adding aria-hidden to each SVG element individually

**Alternatives considered:**

- Add aria-hidden directly on SVG elements: More granular but requires changes in multiple places; wrapper approach is cleaner
- Use `role="presentation"`: Less explicit than aria-hidden for hiding decorative content
- Remove icons from DOM when read-only: Overcomplicates the code for minimal benefit

### Decision 2: Conditionally render menu list when empty

**Approach:** In `ComboboxMenuWrapper.tsx`, only render the `<ul>` element when the menu is open AND there are items or loading state. When empty and closed, skip rendering the list structure entirely.

**Rationale:**

- An empty `<ul>` with no `<li>` children creates an empty group/list in the accessibility tree
- Screen readers can navigate to this empty group, providing no value and creating confusion
- Conditional rendering ensures the menu structure only exists when it has meaningful content
- The NoOptionsPlaceholder should still be visible when open but empty (useful for sighted users)

**Implementation approach:**

```tsx
// Only render <ul> when menu is open
{
isOpen && (
<ul className="...">
{isEmpty && !isLoading ? <NoOptionsPlaceholder>{noOptionsText}</NoOptionsPlaceholder> : children}
{loader}
</ul>
);
}
```

**Alternatives considered:**

- Add aria-hidden to empty `<ul>`: Would still render unnecessary DOM elements
- Use `role="presentation"` on empty list: Still exposed to some assistive technologies
- Always render but hide with CSS: Doesn't solve accessibility tree exposure

### Decision 3: Preserve spinner loader accessibility

**Approach:** Keep the SpinnerLoader in `ComboboxWrapper.tsx` accessible (no aria-hidden) as it provides meaningful loading state information.

**Rationale:**

- Unlike the decorative arrow, the spinner indicates an active loading state
- Screen readers should announce loading status to users
- The spinner is already rendered conditionally (`isLoading ? <SpinnerLoader /> : <DownArrow />`)

## Risks / Trade-offs

**[Risk]** Adding aria-hidden might hide icons from assistive technologies that users rely on for spatial navigation
→ **Mitigation:** These icons are purely decorative and duplicate information already available through proper ARIA labels and roles. The underlying button elements remain fully accessible.

**[Risk]** Conditional rendering of `<ul>` might cause layout shifts or affect CSS selectors
→ **Mitigation:** The menu is already hidden when closed via CSS classes. Removing it from DOM when closed won't affect visible behavior. Test thoroughly to ensure no CSS regressions.

**[Risk]** Changes might affect existing E2E tests that query for menu elements when closed
→ **Mitigation:** Review and update E2E tests if they attempt to query closed menu elements. This is actually a test improvement - tests should verify elements exist only when they should be visible.

**[Trade-off]** Slightly more complex conditional rendering logic in ComboboxMenuWrapper
→ **Accepted:** The improvement in accessibility tree cleanliness outweighs the minor complexity increase. The logic is straightforward and well-documented.
30 changes: 30 additions & 0 deletions openspec/changes/combobox-validation-a11y/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
## Why

The combobox widget has accessibility gaps that create noise and confusion for screen reader users: decorative icons (arrow, clear button) are announced as "image", and empty structural elements are exposed in the accessibility tree when they provide no value. These issues violate WCAG 2.2 AA guidelines and degrade the experience for assistive technology users.

## What Changes

- Add `aria-hidden="true"` to decorative icon elements (DownArrow, ClearButton SVG containers) to hide them from assistive technologies
- Prevent empty group-like nodes from being exposed to screen readers when the combobox has no value or options
- Ensure validation-related ARIA attributes remain functional (aria-invalid, aria-describedby already implemented)

## Capabilities

### New Capabilities

- `decorative-icon-hiding`: Hide decorative icons from assistive technologies using aria-hidden
- `empty-group-prevention`: Prevent empty structural elements from being exposed in accessibility tree

### Modified Capabilities

<!-- No existing capability requirements are changing - these are additive improvements -->

## Impact

**Affected Files:**

- `packages/pluggableWidgets/combobox-web/src/assets/icons.tsx` - Add aria-hidden to DownArrow and ClearButton SVG elements
- `packages/pluggableWidgets/combobox-web/src/components/ComboboxWrapper.tsx` - Ensure arrow icon container has proper accessibility attributes
- `packages/pluggableWidgets/combobox-web/src/components/ComboboxMenuWrapper.tsx` - Prevent empty ul/group elements from being exposed when no options exist

**No Breaking Changes**: These are non-breaking accessibility enhancements that improve screen reader experience without affecting visual presentation or API.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
## ADDED Requirements

### Requirement: Decorative icon SVGs must be hidden from assistive technologies

The combobox widget SHALL hide decorative icon elements from assistive technologies using `aria-hidden="true"`. Decorative icons are those that do not convey unique information and duplicate functionality already available through proper ARIA labels, roles, or semantic HTML.

#### Scenario: Down arrow icon hidden from screen readers

- **WHEN** the combobox renders the down arrow icon
- **THEN** the icon wrapper span SHALL have `aria-hidden="true"`
- **AND** screen readers SHALL NOT announce the icon as "image"

#### Scenario: Clear button icon hidden from screen readers

- **WHEN** the combobox renders the clear button with its icon
- **THEN** the icon wrapper span SHALL have `aria-hidden="true"`
- **AND** the parent button element SHALL remain accessible with its `aria-label`
- **AND** screen readers SHALL announce the button by its label, not as "image"

#### Scenario: Spinner loader remains accessible

- **WHEN** the combobox is in loading state and displays a spinner
- **THEN** the spinner element SHALL NOT have `aria-hidden="true"`
- **AND** screen readers SHALL be able to perceive the loading state

### Requirement: Icon functionality must remain intact

Hiding decorative icons from assistive technologies SHALL NOT affect their visual presentation or interactive behavior.

#### Scenario: Icons remain visible to sighted users

- **WHEN** decorative icons have `aria-hidden="true"`
- **THEN** the icons SHALL remain visually displayed
- **AND** their CSS styling SHALL be unchanged

#### Scenario: Buttons remain interactive

- **WHEN** a button contains a hidden decorative icon
- **THEN** the button SHALL remain fully interactive via mouse and keyboard
- **AND** click handlers SHALL continue to function normally
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
## ADDED Requirements

### Requirement: Empty menu structures must not be exposed to assistive technologies

The combobox widget SHALL NOT expose empty menu list elements to the accessibility tree when the menu is closed or has no content. This prevents screen readers from navigating to meaningless empty groups.

#### Scenario: Menu list not rendered when closed

- **WHEN** the combobox menu is closed
- **THEN** the menu `<ul>` element SHALL NOT be present in the DOM
- **AND** screen readers SHALL NOT encounter an empty list structure

#### Scenario: Menu list rendered when open with items

- **WHEN** the combobox menu is open
- **AND** there are items to display
- **THEN** the menu `<ul>` element SHALL be present in the DOM
- **AND** screen readers SHALL be able to navigate the list items

#### Scenario: Menu list rendered when open but empty

- **WHEN** the combobox menu is open
- **AND** there are no items to display (empty state)
- **THEN** the menu `<ul>` element SHALL be present in the DOM
- **AND** the "No options" placeholder SHALL be rendered inside the list
- **AND** screen readers SHALL be able to perceive the empty state message

#### Scenario: Menu list rendered during loading

- **WHEN** the combobox is loading items
- **AND** the menu is open
- **THEN** the menu `<ul>` element SHALL be present in the DOM
- **AND** the loading indicator SHALL be accessible to screen readers

### Requirement: Visual behavior must remain unchanged

Preventing empty menu structures in the accessibility tree SHALL NOT affect the visual presentation or user experience for sighted users.

#### Scenario: Menu visibility controlled by CSS

- **WHEN** the menu transitions between open and closed states
- **THEN** the visual appearance SHALL match previous behavior
- **AND** CSS animations and transitions SHALL continue to work
- **AND** layout SHALL NOT shift unexpectedly

#### Scenario: No options placeholder displays when appropriate

- **WHEN** the menu is open and has no items
- **THEN** the "No options" text SHALL be visible to sighted users
- **AND** it SHALL be announced by screen readers
44 changes: 44 additions & 0 deletions openspec/changes/combobox-validation-a11y/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
## 1. Hide Decorative Icons from Assistive Technologies

- [x] 1.1 Add `aria-hidden="true"` to DownArrow icon wrapper span in `src/assets/icons.tsx`
- [x] 1.2 Add `aria-hidden="true"` to ClearButton icon wrapper span in `src/assets/icons.tsx`
- [x] 1.3 Verify SpinnerLoader does NOT have aria-hidden (should remain accessible)
- [x] 1.4 Verify clear button parent elements retain their aria-label attributes

## 2. Prevent Empty Menu Structure Exposure

- [x] 2.1 Modify `ComboboxMenuWrapper.tsx` to conditionally render `<ul>` only when `isOpen` is true
- [x] 2.2 Ensure NoOptionsPlaceholder still renders when menu is open but empty
- [x] 2.3 Ensure loader element renders when menu is open and loading
- [x] 2.4 Verify menu header and footer render only when menu is open

## 3. Unit Tests

- [x] 3.1 Add test for DownArrow having aria-hidden="true" on wrapper span
- [x] 3.2 Add test for ClearButton having aria-hidden="true" on wrapper span
- [x] 3.3 Add test that menu `<ul>` is not in DOM when closed
- [x] 3.4 Add test that menu `<ul>` is in DOM when open with items
- [x] 3.5 Add test that menu `<ul>` is in DOM when open but empty (with NoOptionsPlaceholder)
- [x] 3.6 Add test that SpinnerLoader does not have aria-hidden
- [x] 3.7 Verify existing aria-invalid and aria-describedby tests still pass

## 4. E2E Tests

- [x] 4.1 Review existing E2E tests for queries on closed menu elements
- [x] 4.2 Update E2E tests if they expect menu DOM elements when closed
- [x] 4.3 Add E2E test verifying menu appears only when opened

## 5. Manual Accessibility Testing

- [x] 5.1 Test with screen reader (NVDA/JAWS/VoiceOver) - verify no "image" announcements for decorative icons
- [x] 5.2 Test with screen reader - verify no empty group navigation when menu is closed
- [x] 5.3 Test with screen reader - verify clear button announces with proper label
- [x] 5.4 Verify keyboard navigation still works (Tab, Enter, Arrow keys)
- [x] 5.5 Verify visual appearance unchanged for sighted users

## 6. Final Verification

- [x] 6.1 Verify existing validation ARIA attributes (aria-invalid, aria-describedby) still function
- [x] 6.2 Run full test suite for combobox widget
- [x] 6.3 Test both single and multi-selection modes
- [x] 6.4 Test with different data sources (static, association, database)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-06-05
Loading
Loading