You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: packages/nimble-components/src/chip/specs/README.md
+8-2Lines changed: 8 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -145,12 +145,18 @@ We will provide styling for the `disabled` attribute state.
145
145
_Consider the accessibility of the component, including:_
146
146
147
147
-_Keyboard Navigation and Focus_
148
-
- when the chip component is removable, the remove button will be focusable, otherwise it will not receive focus (following the `nimble-banner` pattern).
148
+
- When the chip is selectable (`selection-mode="single"`) and removable:
149
+
- The chip itself is focusable and receives keyboard events
150
+
- Space/Enter toggles the selected state
151
+
- Escape removes the chip (emits `remove` event)
152
+
- The remove button is **not** focusable (`tabindex="-1"`) to avoid nested interactive controls (violates [WCAG 4.1.2](https://dequeuniversity.com/rules/axe/4.11/nested-interactive))
153
+
- When the chip is removable but not selectable (`selection-mode="none"`):
154
+
- The remove button is focusable and can be activated with Space or Enter
149
155
-_Form Input_
150
156
- N/A
151
157
-_Use with Assistive Technology_
158
+
- When selectable, the chip has `role="button"` and `aria-pressed` to indicate its toggle state
152
159
- a `chip`'s accessible name comes from the element's contents by default
153
-
- no ARIA `role` seems necessary to define for the chip, as it isn't interactive itself (only the remove button is which has a `role`). The only valid role seemed to be [`status`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/status_role), but that also didn't seem helpful from an accessibility perspective, particularly since it mainly relates to providing helpful information when the content changes (which we don't expect).
154
160
- the remove button will have its content set to provide a label provider token for "Remove".
155
161
- title will not be set, which aligns with decisions for the filterable select clear button and the banner
156
162
- ideally this would include the contents of the chip itself (so a screen reader would announce "Remove <Chip>") but differing word order between
The `nimble-chip` component currently lacks a built-in mechanism to represent a selected or toggled state. This feature is required to support use cases where chips act as filter toggles or selectable items in a list. This design proposes adding a selection state to the `nimble-chip`.
6
+
7
+
## Links To Relevant Work Items and Reference Material
*`none`: The chip is not selectable. Does not have `role="button"` or `aria-pressed`. User-supplied `tabindex` is forwarded to the remove button if removable.
24
+
*`single`: The chip can be toggled on/off via click or Space/Enter keys. Has `role="button"` and `aria-pressed`. Automatically receives `tabindex="0"` unless user provides a different value.
* Indicates the current selection state when `selection-mode="single"`.
27
+
* When `true`, the chip displays with `fillSelectedColor` background.
28
+
***Event:**`selected-change`
29
+
* Emitted when the user toggles the chip state via click or keyboard (Space/Enter).
30
+
* Only emitted when `selection-mode="single"`.
31
+
***Event:**`remove`
32
+
* Emitted when the user activates the remove button (click) or presses Escape key (when selectable and removable).
33
+
34
+
### Keyboard Interaction
35
+
36
+
***Space/Enter:** Toggles `selected` state (when `selection-mode="single"`).
37
+
***Escape:** Removes the chip (when `selection-mode="single"`, `removable`, and not `disabled`).
38
+
* This provides keyboard access to the remove functionality without requiring the remove button to be focusable, which would violate WCAG 4.1.2 (nested interactive controls).
39
+
40
+
### Accessibility
41
+
42
+
***Role:**
43
+
* If `selection-mode="none"`: No explicit role (generic container).
44
+
* If `selection-mode="single"`: `role="button"` with `aria-pressed="true"` or `aria-pressed="false"`.
45
+
***Tabindex Management:**
46
+
* When `selection-mode="single"`: The chip automatically manages its own `tabindex` (defaults to `0`). User-supplied values are preserved.
47
+
* When `selection-mode="none"`: The chip does not manage `tabindex`. User-supplied values are forwarded to the remove button if `removable`.
48
+
***Nested Interactive Controls:** To comply with WCAG 4.1.2, when a chip is both selectable and removable, the remove button is set to `tabindex="-1"` (not keyboard-focusable) and the Escape key provides the keyboard mechanism for removal.
49
+
50
+
### Visual Design
51
+
52
+
Visual states follow the [Figma design specification](https://www.figma.com/design/PO9mFOu5BCl8aJvFchEeuN/Nimble_Components?node-id=2227-78839&m=dev).
53
+
54
+
***Default State:**
55
+
* Border: `rgba(actionRgbPartialColor, 0.3)` for selectable chips; `rgba(borderRgbPartialColor, 0.3)` for non-selectable, non-block appearance
***Note:** Active styling is suppressed when the remove button is in mousedown state (via `remove-button-active` attribute)
75
+
***Disabled State:**
76
+
* Text color: `bodyDisabledFontColor`
77
+
* Icons: 30% opacity
78
+
* No hover, focus, or active styling
79
+
* Remove button hidden
80
+
81
+
### Implementation Details
82
+
83
+
***CSS Cascade Layers:** Styles are organized using `@layer base, hover, focusVisible, active, disabled, top` to ensure proper precedence of interactive states.
84
+
***Tabindex Management:**
85
+
* The chip tracks whether it's managing tabindex via internal `managingTabIndex` flag.
86
+
* When `selection-mode` changes or the chip connects/disconnects, `updateManagedTabIndex()` is called.
87
+
* User-supplied tabindex values are preserved by detecting attribute changes that didn't originate from internal management.
88
+
***Remove Button Active State:**
89
+
* The `remove-button-active` attribute is set during remove button mousedown to prevent chip active styling from appearing.
90
+
* A document-level mouseup listener clears this state, ensuring it works even if the mouse moves outside the chip.
91
+
* The listener is cleaned up in `disconnectedCallback()` to prevent memory leaks.
92
+
93
+
## Alternative Implementations / Designs
94
+
95
+
### Alternative 1: New Component
96
+
* Create a `nimble-toggle-chip` instead of modifying `nimble-chip`.
97
+
***Pros:** Separation of concerns. `nimble-chip` stays simple (just for display/dismiss).
98
+
***Cons:** Code duplication. Users might expect `nimble-chip` to handle this common case.
99
+
***Decision:** Rejected. The `selection-mode` attribute provides a clean API for opt-in behavior without requiring a separate component.
100
+
101
+
### Alternative 2: Focusable Remove Button (Initial Implementation)
102
+
* Make the remove button keyboard-focusable when the chip is selectable and removable.
103
+
***Pros:** Direct keyboard access to remove button matches mouse interaction.
104
+
***Cons:** Violates WCAG 4.1.2 (nested interactive controls - a button within a button).
### Alternative 3: Use AbortController for Event Cleanup
108
+
* Use `AbortController` to manage the document mouseup listener instead of storing the handler.
109
+
***Pros:** Modern JavaScript pattern, automatic cleanup.
110
+
***Cons:** No precedent in Nimble codebase for this pattern.
111
+
***Decision:** Rejected. Followed established Nimble pattern of storing handler and cleaning up in `disconnectedCallback()`.
112
+
113
+
## Open Issues
114
+
115
+
### Resolved
116
+
*~~Does this affect the `remove` functionality? Can a chip be both selectable and removable?~~
117
+
***Resolution:** Yes, chips can be both selectable and removable. When both are true, the chip uses the Escape key for keyboard removal to avoid nested interactive controls (WCAG 4.1.2 violation).
0 commit comments