HyChip
Usage examples on this page are written for Lit / plain HTML (
<hy-chip>). The same component ships asHyChipin@whitespaceux/harmony-react(native React 19), with typed wrappers for Angular, Solid, Svelte, and Vue. The API reference below applies to all frameworks.
A compact, interactive element for displaying tags, filters, selections, or actions.
Chips are versatile UI elements that can represent inputs, attributes, or actions. Unlike badges (which are non-interactive status indicators), chips support user interaction including clicking, selecting, and removing.
Key Features
- Multiple semantic variants (neutral, brand, success, warning, danger, info), and
highlightfor the one chip that stands out on a screen - Optional remove button for dismissible chips
- Action chips (
interactive: a click firesactivate, nothing toggles) and filter chips (interactive selectable: a click togglesselected) - Link mode when href is provided
- Icon and avatar support via prefix/suffix slots
- Full keyboard navigation (Enter, Space, Backspace, Delete)
- SSR compatible
Accessibility: built in
- Proper ARIA roles and states
- Keyboard navigation with focus indicators
- Screen reader announcements
- High contrast mode support
- Reduced motion support
- Named from the host:
aria-label/aria-labelledbyon<hy-chip>name the chip (resolved to text, since an IDREF can't cross the shadow boundary)
Accessibility: your part
- After you remove a chip on
remove, move focus to the next chip, or to the field when none is left. - Wrap a set of filter chips in an element with
role="group"and anaria-label, or put them under a visible heading.
Icon-only chip
A chip with a glyph (icon, the prefix or suffix slot) and nothing in its default slot is square at every size and density: the empty label makes no box, and the chip's width equals its height. Name it on the host with aria-label — an interactive icon-only chip without one dev-warns. A removable chip keeps its padding (the remove button needs the room). The server render keeps the label part; the first client update drops it.
When to use
- A filter people turn on and off, with
selectable. - A value people remove: an applied filter, a selected tag, with
removable. - A tag on a thing that people can act on.
Do
- Keep it to a word or two.
- Keep the chips of a row the same kind: all filters or all tags.
When not to use
- A label nothing happens on when clicked:
hy-badge. - The main action of a screen or form:
hy-button. - One setting chosen from several, kept on screen:
hy-toggle-group,hy-radio-grouporhy-select. - Tokens people type into a field, such as recipients or tags:
hy-multi-combobox.
Examples
Basic Chip
<hy-chip>Label</hy-chip>With Icon
<hy-chip icon="heart">Favorites</hy-chip>Removable Chip
<hy-chip removable>Removable</hy-chip>Filter chip (selectable)
<!-- A click toggles `selected` and fires `select`; announced as a toggle button. -->
<hy-chip selectable>Unread</hy-chip>
<hy-chip selectable selected>Starred</hy-chip>Action chip
<!-- A button that looks like a chip: fires `activate`, never changes its own state. -->
<hy-chip icon="filter">Filters</hy-chip>Icon-only chip
<!-- Square, named on the host. -->
<hy-chip icon="list-check" variant="success" aria-label="No accessibility issues"></hy-chip>Link Chip
<hy-chip href="/category/design">Design</hy-chip>Variants
<hy-chip variant="neutral">Neutral</hy-chip>
<hy-chip variant="brand">Brand</hy-chip>
<hy-chip variant="success">Success</hy-chip>
<hy-chip variant="warning">Warning</hy-chip>
<hy-chip variant="danger">Danger</hy-chip>Categorical / Identity tag
<!-- Hash an identity string to a stable slot in a named group. -->
<hy-chip category-group="team" identity="user-alice">Alice</hy-chip>
<!-- Explicit slot beats identity. -->
<hy-chip category-group="team" slot-index="3">Frontend</hy-chip>
<!-- A non-neutral variant wins — categorical is ignored. -->
<hy-chip variant="brand" category-group="team" slot-index="3">Brand (categorical skipped)</hy-chip>Event Handling
const chip = document.querySelector('hy-chip[removable]');
chip.addEventListener('remove', (event) => {
console.log('Chip removed:', event.detail.chip);
event.detail.chip.remove(); // Remove from DOM
});
chip.addEventListener('select', (event) => {
console.log('Selection changed:', event.detail.selected);
});API
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
variant | variant | 'neutral' | 'brand' | 'success' | 'warning' | 'danger' | 'info' | 'highlight' | 'neutral' | Visual style variant determining the chip's color scheme and semantic meaning. |
emphasis | emphasis | 'solid' | 'soft' | 'outlined' | 'tinted' | 'soft' | Visual chrome weight on top of the role. |
size | size | 'small' | 'medium' | 'large' | 'medium' | Physical size of the chip affecting dimensions, padding, and typography. |
interactive | interactive | boolean | true | Whether the chip is interactive (clickable, focusable, and responds to keyboard). An interactive chip is a button: a click fires activate and changes nothing on the chip — add selectable for a filter chip that toggles. When false, the chip renders as a static display element without button semantics, making it suitable for tags or labels that don't require user interaction. |
selectable | selectable | boolean | false | Makes an interactive chip a filter chip: a click (or Enter / Space) toggles selected and fires select, and the chip is announced as a toggle button (aria-pressed). Without it a click only fires activate — right for a chip that opens a menu or runs an action, which must not change its look when clicked. No effect on a link chip or a non-interactive one. |
removable | removable | boolean | false | Shows a remove button that allows the user to dismiss the chip. When clicked, fires the remove event. The chip can also be removed by pressing Backspace or Delete when focused. |
disabled | disabled | boolean | false | Disables the chip, preventing interaction and applying disabled styling. Disabled chips cannot be clicked, selected, or removed. |
selected | selected | boolean | false | Indicates whether the chip is in a selected state, and paints it with the role's on token. Useful for filter chips, toggle chips, or multi-select scenarios. A click toggles it only on a selectable chip (and fires select); on any other chip it is the app's to set. |
href | href | string | undefined | — | When set, renders the chip as an <a> tag with this href. The chip will navigate to the specified URL when clicked. |
target | target | '_blank' | '_parent' | '_self' | '_top' | — | Specifies where to open the linked document. Only used when href is set. When set to _blank, automatically adds rel="noreferrer noopener" for security. |
removeLabel | remove-label | string | 'Remove {label}' | Accessible label for the remove button, a template: {label} is replaced by the chip's text (Remove {label} reads "Remove JavaScript"); with no text the label is the template's own words, trimmed. A label without {label} is used as written. Use for internationalization. |
icon | icon | string | undefined | — | Name of the icon to display before the label. When set, renders an hy-icon in the shadow DOM instead of using the prefix slot. |
categoryGroup | category-group | string | undefined | — | Categorical group name to hash into. Only applied when variant is neutral; a non-neutral variant wins. Selects which palette in the active DS's roles.categorical map this chip pulls from. |
identity | identity | string | undefined | — | Identity string hashed to a stable slot within categoryGroup — same identity always produces the same color. Ignored when slotIndex is set. |
slotIndex | slot-index | number | undefined | — | Explicit 1-indexed slot within categoryGroup. Takes precedence over identity. Out-of-range values fall back to default styling. |
Events
| Event | Detail | Description |
|---|---|---|
activate | — | Fired when chip is clicked. Detail: { originalEvent: MouseEvent | KeyboardEvent } |
remove | — | Fired when remove button is clicked or Backspace/Delete pressed. Detail: { chip: HyChip, originalEvent: MouseEvent | KeyboardEvent } |
select | — | Fired when a selectable chip's selection state changes (never on a chip without selectable). Detail: { selected: boolean, originalEvent: MouseEvent | KeyboardEvent } |
Slots
| Slot | Description |
|---|---|
default | The chip's label text |
prefix | Content before the label (icon, avatar) |
suffix | Content after the label, before remove button (icon, count) |
CSS Parts
| Part | Description |
|---|---|
base | The chip's base wrapper element |
label | The default slot containing chip text |
remove-button | The remove button element (when removable) |
CSS Custom Properties
The documented hooks. Every override hook the component reads — one per token it paints — is listed in @whitespaceux/harmony/theme-api.json, and @whitespaceux/harmony/component-bindings.json (the Harmony MCP get-bindings tool) says which part and state each one paints.
| Property | Default | Description |
|---|---|---|
--hy-chip-background-brand-soft-rest | var(--hy-background-brand-soft-rest) | One of the generated color hooks (paint/chip.mjs): --hy-chip[-<part>]-<family>-<variant>-<emphasis-or-on>-<state> (family: background, foreground or border), one per cell and state, each falling back to the job token of the same name (the default cell is neutral soft). Components ▸ Add an override lists them all. |
--hy-chip-gap-pill-md-inline | var(--hy-gap-pill-md-inline) | Gap between elements |
--hy-chip-gap-pill-sm-inline | var(--hy-gap-pill-sm-inline) | Gap between elements |
--hy-chip-gap-pill-lg-inline | var(--hy-gap-pill-lg-inline) | Gap between elements |
--hy-chip-control-height-xs | var(--hy-control-height-xs) | Height at small size |
--hy-chip-control-height-sm | var(--hy-control-height-sm) | Height at medium size |
--hy-chip-control-height-md | var(--hy-control-height-md) | Height at large size |
--hy-chip-padding-pill-sm-inline | var(--hy-padding-pill-sm-inline) | Horizontal padding at small size |
--hy-chip-padding-pill-md-inline | var(--hy-padding-pill-md-inline) | Horizontal padding at medium size |
--hy-chip-padding-pill-lg-inline | var(--hy-padding-pill-lg-inline) | Horizontal padding at large size |
--hy-chip-radius-pill-md | var(--hy-radius-pill-md) | Border radius |
--hy-chip-stroke-sm | var(--hy-stroke-sm) | Border width |
--hy-chip-text-transform | none | Text transform for chip label |
--hy-chip-body-md-font-family | var(--hy-body-md-font-family) | Font family override for chip label |
--hy-chip-body-sm-font-family | var(--hy-body-sm-font-family) | Font family override for chip label |
--hy-chip-body-lg-font-family | var(--hy-body-lg-font-family) | Font family override for chip label |
--hy-chip-motion-transition-hover-duration | var(--hy-motion-transition-hover-duration) | Transition duration for hover and focus state feedback |
--hy-chip-motion-transition-hover-easing | var(--hy-motion-transition-hover-easing) | Transition easing for hover and focus state feedback |
Methods
focus()
Sets focus on the chip.
blur()
Removes focus from the chip.