Skip to content

Conversation

@fredvisser
Copy link
Contributor

@fredvisser fredvisser commented Nov 25, 2025

Pull Request

🤨 Rationale

Adds support for selection-mode on the nimble-chip component to allow for toggleable chips. This enables use cases where chips act as filter toggles or selectable items.

👩‍💻 Implementation

  • Added selectionMode attribute to Chip component.
  • Implemented selected state and styling.
  • Added click and keyboard handlers (Space/Enter to toggle, Escape to remove).
  • Managed tabindex to ensure proper accessibility for selectable chips.

🧪 Testing

  • Unit tests added for selection logic and keyboard interactions.
  • Verified accessibility behavior (tabindex management).

✅ Checklist

  • I have updated the project documentation to reflect my changes or determined no changes are needed.

@fredvisser fredvisser force-pushed the users/fvisser/chip-toggle branch from fc883c7 to 33e0dc4 Compare November 25, 2025 15:51
@fredvisser fredvisser requested a review from jattasNI December 1, 2025 15:49
Copy link
Contributor

@jattasNI jattasNI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just reviewed API design, proposed behavior, and document structure so far, not actual implementation.


_Consider the accessibility of the component, including:_

- _Keyboard Navigation and Focus_
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update the API section above to include the new APIs too? The other md file seems more like copilot instructions for this specific feature but it's still helpful to keep the entire API summarized in this higher level spec.

- The chip itself is focusable and receives keyboard events
- Space/Enter toggles the selected state
- Escape removes the chip (emits `remove` event)
- 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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is unclear to me. Is it saying that this proposal violates the rule and we're ok with it or that this choice is made to avoid violating that rule? If it's the former, what's the justification for violating the rule?

- When the chip is selectable (`selection-mode="single"`) and removable:
- The chip itself is focusable and receives keyboard events
- Space/Enter toggles the selected state
- Escape removes the chip (emits `remove` event)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you consider whether Escape should also emit the event when the chip is not selectable? I'd advocate for it to work in both cases unless there's a reason not to.

- 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).
- the remove button will have its content set to provide a label provider token for "Remove".
- title will not be set, which aligns with decisions for the filterable select clear button and the banner
- ideally this would include the contents of the chip itself (so a screen reader would announce "Remove <Chip>") but differing word order between
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a Future Work section below that mentions making the chip selectable which could be removed now.

import { withActions } from 'storybook/internal/actions/decorator';
import { chipTag } from '@ni/nimble-components/dist/esm/chip';
import { ChipAppearance } from '@ni/nimble-components/dist/esm/chip/types';
import {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR should include updates to the matrix story.

- `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.
- `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.
- **Attribute:** `selected` (boolean, default: `false`)
- Indicates the current selection state when `selection-mode="single"`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the intent that the attribute should reflect the interactive state? That doesn't seem to be working in the current implementation. If that's not the intent, it'd be good to document and justify.

- `removable` - set to show the remove button
- `appearance` - supports `outline` and `block` appearances
- `disabled` - styles the chip in the typical Nimble manner, including any user-slotted content (text and icon), and additionally hides the remove button.
- `selection-mode` - controls whether the chip can be selected. Values: `'none'` (default) or `'single'`. When `'single'`, the chip acts as a toggle button with `role="button"` and `aria-pressed`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'single' implies there might someday be a 'multiple'. I'm not sure about how that would work though. Right now we only have individual chips that are not aware of other chips. We don't have a container component like the table is for rows or the radio group is for radio buttons. Without that, I'm not sure what would enforce how many chips can be selected and thus I'm not sure it makes sense for clients to specify it via the API on the chip. I don't want to imply that Nimble will coordinate selection state across chips somehow.

Some options:

  1. take the time to design that parent component and move the selection-mode API to the parent component. But then we'd need to figure out other aspects of its behavior like sizing / layout / spacing.
  2. change this to a boolean attribute like selectable for now. If it later becomes important for apps to enforce single vs multiple selection mode, we'd have some options:
    • provide instructions for apps to implement single vs multi select patterns themselves. (It might require adding some new cancelable "beforeselection" event, I haven't thought it through)
    • build a Nimble container component with an API to specify selection behavior and make a breaking change to remove the attribute from the chip if it's now redundant.

- use the `medium-padding` token to create horizontal and vertical space between
chips
- all chips have the same appearance

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be good to think through the feedback from the demo meeting about the difference between a selectable (non-removable) chip and a toggle button. What use cases is each one best suited for? Then we could add those docs to each component and consider cross linking them.

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.

3 participants