,
}
});
export interface ChipRemoveEvent extends CustomEvent {
target: Chip;
}
+
+export interface ChipSelectedChangeEvent extends CustomEvent {
+ target: Chip;
+}
diff --git a/packages/react-workspace/react-client-app/src/components/ChipSection.tsx b/packages/react-workspace/react-client-app/src/components/ChipSection.tsx
index fecb1ca87e..4cdd72b0d0 100644
--- a/packages/react-workspace/react-client-app/src/components/ChipSection.tsx
+++ b/packages/react-workspace/react-client-app/src/components/ChipSection.tsx
@@ -1,7 +1,10 @@
+import { useState } from 'react';
import { NimbleChip } from '@ni/nimble-react/chip';
import { SubContainer } from './SubContainer';
export function ChipSection(): React.JSX.Element {
+ const [chipSelected, setChipSelected] = useState(false);
+
function onChipRemove(): void {
alert('Chip removed');
}
@@ -10,6 +13,9 @@ export function ChipSection(): React.JSX.Element {
Outline Chip
Block Chip
+ setChipSelected(value => !value)}>
+ Selectable Chip
+
Removable Chip
Disabled Chip
diff --git a/packages/storybook/src/nimble/chip/chip-matrix.stories.ts b/packages/storybook/src/nimble/chip/chip-matrix.stories.ts
index e02629d1d5..78f86b1292 100644
--- a/packages/storybook/src/nimble/chip/chip-matrix.stories.ts
+++ b/packages/storybook/src/nimble/chip/chip-matrix.stories.ts
@@ -60,6 +60,19 @@ const widthStates = [
type WidthState = (typeof widthStates)[number];
const widthStatesOnlyDefault = widthStates[0];
+const selectableStates = [
+ ['Not Selectable', false],
+ ['Selectable', true]
+] as const;
+type SelectableState = (typeof selectableStates)[number];
+const selectableStatesOnlySelectable = selectableStates[1];
+
+const selectedStates = [
+ ['Not Selected', false],
+ ['Selected', true]
+] as const;
+type SelectedState = (typeof selectedStates)[number];
+
const metadata: Meta = {
title: 'Tests/Chip',
parameters: {
@@ -75,16 +88,20 @@ const component = (
[removableName, removable]: RemovableStates,
[showStartSlotIconName, showStartSlotIcon]: ShowStartSlotIconState,
[labelName, label]: LabelState,
- [widthName, width]: WidthState
+ [widthName, width]: WidthState,
+ [selectableName, selectable]: SelectableState,
+ [selectedName, selected]: SelectedState
): ViewTemplate => html`
<${chipTag}
appearance="${() => appearance}"
?removable="${() => removable}"
?disabled=${() => disabled}
+ ?selectable="${() => selectable}"
+ ?selected=${() => selected}
style="margin-right: 8px; margin-bottom: 8px; ${() => width}; ${() => size};">
${when(() => showStartSlotIcon, html`<${iconKeyTag} slot="start">${iconKeyTag}>`)}
${label}
@@ -92,7 +109,7 @@ const component = (
`;
-export const themeMatrix: StoryFn = createMatrixThemeStory(
+const createThemeMatrix = (selectableState: SelectableState): StoryFn => createMatrixThemeStory(
createMatrix(component, [
disabledStates,
appearanceStates,
@@ -100,10 +117,36 @@ export const themeMatrix: StoryFn = createMatrixThemeStory(
removableStates,
showStartSlotIconStates,
labelStates,
- widthStates
+ widthStates,
+ [selectableState],
+ [selectedStates[0]] // Only not selected
])
);
+const createSelectableThemeMatrix = (selectedState: SelectedState): StoryFn => createMatrixThemeStory(
+ createMatrix(component, [
+ disabledStates,
+ appearanceStates,
+ sizeStates,
+ removableStates,
+ showStartSlotIconStates,
+ labelStates,
+ widthStates,
+ [selectableStates[1]],
+ [selectedState]
+ ])
+);
+
+export const themeMatrix: StoryFn = createThemeMatrix(selectableStates[0]);
+
+export const selectableThemeMatrix: StoryFn = createSelectableThemeMatrix(
+ selectedStates[0]
+);
+
+export const selectableSelectedThemeMatrix: StoryFn = createSelectableThemeMatrix(
+ selectedStates[1]
+);
+
const interactionStates = cartesianProduct([
disabledStates,
appearanceStates,
@@ -111,7 +154,9 @@ const interactionStates = cartesianProduct([
removableStates,
[showStartSlotIconStatesOnlyIcon],
[labelStatesOnlyShort],
- [widthStatesOnlyDefault]
+ [widthStatesOnlyDefault],
+ [selectableStatesOnlySelectable],
+ selectedStates
] as const);
const interactionStatesHover = cartesianProduct([
@@ -121,7 +166,9 @@ const interactionStatesHover = cartesianProduct([
removableStates,
[showStartSlotIconStatesOnlyIcon],
[labelStatesOnlyShort],
- [widthStatesOnlyDefault]
+ [widthStatesOnlyDefault],
+ [selectableStatesOnlySelectable],
+ selectedStates
] as const);
export const interactionsThemeMatrix: StoryFn = createMatrixThemeStory(
diff --git a/packages/storybook/src/nimble/chip/chip.mdx b/packages/storybook/src/nimble/chip/chip.mdx
index d4aece4eea..f8682c2c39 100644
--- a/packages/storybook/src/nimble/chip/chip.mdx
+++ b/packages/storybook/src/nimble/chip/chip.mdx
@@ -8,7 +8,7 @@ import { chipTag } from '@ni/nimble-components/dist/esm/chip';
A component for displaying information in a compact format. Provides an optional
-remove button to allow users to delete the chip.
+remove button and optional local selection state.
@@ -39,8 +39,28 @@ is:
chips
- all chips have the same appearance
+Use a selectable chip when the UI needs a compact, tag-like control that can be
+toggled in place as part of a collection of chips, such as filter tokens.
+
+Use a [toggle button](?path=/docs/components-toggle-button--docs) when the
+control is primarily a button-shaped action toggle rather than compact chip
+content.
+
{/* ## Examples */}
-{/* ## Accessibility */}
+## Accessibility
+
+### Keyboard Interaction
+
+When a chip is both selectable (`selectable`) and removable:
+
+- **Space/Enter**: Toggles the chip's selected state
+- **Escape**: Removes the chip (emits the `remove` event)
+
+The remove button is non-focusable when the chip is selectable to avoid nested
+interactive controls. Instead, use the Escape key to remove a focused chip.
+
+When a chip is removable but not selectable, the remove button is focusable and
+can be activated with Space or Enter.
{/* ## Resources */}
diff --git a/packages/storybook/src/nimble/chip/chip.stories.ts b/packages/storybook/src/nimble/chip/chip.stories.ts
index fc35ab667a..8ff40a3cd5 100644
--- a/packages/storybook/src/nimble/chip/chip.stories.ts
+++ b/packages/storybook/src/nimble/chip/chip.stories.ts
@@ -26,19 +26,29 @@ const chipSize = {
interface ChipArgs {
appearance: keyof typeof ChipAppearance;
+ selectable: boolean;
+ selected: boolean;
size: keyof typeof chipSize;
removable: boolean;
content: string;
icon: boolean;
disabled?: boolean;
remove: undefined;
+ selectedChange: undefined;
}
const metadata: Meta = {
title: 'Components/Chip',
render: createUserSelectedThemeStory(html`
${disableStorybookZoomTransform}
- <${chipTag} appearance="${x => x.appearance}" style="${x => chipSize[x.size]}" ?removable="${x => x.removable}" ?disabled="${x => x.disabled}">
+ <${chipTag}
+ appearance="${x => x.appearance}"
+ style="${x => chipSize[x.size]}"
+ ?selectable="${x => x.selectable}"
+ ?selected="${x => x.selected}"
+ ?removable="${x => x.removable}"
+ ?disabled="${x => x.disabled}"
+ >
${x => x.content}
${when(x => x.icon, html`
@@ -52,6 +62,18 @@ const metadata: Meta = {
control: { type: 'radio' },
table: { category: apiCategory.attributes }
},
+ selectable: {
+ name: 'selectable',
+ description: 'Whether the chip behaves like a toggle button for local selection state.',
+ control: { type: 'boolean' },
+ table: { category: apiCategory.attributes }
+ },
+ selected: {
+ name: 'selected',
+ description: 'Whether the chip is selected. Only affects selectable chips.',
+ control: { type: 'boolean' },
+ table: { category: apiCategory.attributes }
+ },
size: {
name: 'Chip sizing',
description:
@@ -103,13 +125,21 @@ const metadata: Meta = {
table: { category: apiCategory.slots }
},
remove: {
- description: 'Emitted when the user presses the remove button.',
+ description: 'Emitted when the user presses the remove button or presses Escape on a removable chip.',
table: { category: apiCategory.events },
control: false
},
+ selectedChange: {
+ name: 'selected-change',
+ description: 'Emitted when the user toggles a selectable chip.',
+ table: { category: apiCategory.events },
+ control: false
+ }
},
args: {
appearance: 'outline',
+ selectable: false,
+ selected: false,
size: 'default',
removable: false,
content: 'Homer Simpson',
@@ -124,7 +154,7 @@ export const chip: StoryObj = {
decorators: [withActions],
parameters: {
actions: {
- handles: ['remove']
+ handles: ['remove', 'selected-change']
}
}
};