Chip component
Chip is a compact pill for a tag, a status label, or one item in a set of selectable filters. It covers three related but distinct jobs: a plain static label, a closable tag with its own dismiss button, and a selectable/toggleable chip for filter-style UI — which one it is depends entirely on which props you pass.
Playground
Change a prop on the right and the preview updates. Switch to Code to copy what you see.
Recipes
Copy-paste patterns for the ways Chip actually gets used — each one a real, self-contained example.
Variants
Four weights — solid, soft, bordered, and a dot variant for status labels.
<Chip>Solid</Chip>
<Chip variant="soft">Soft</Chip>
<Chip variant="bordered">Bordered</Chip>
<Chip variant="dot">Dot</Chip>Semantic colors
Soft chips make great status pills — colour them by meaning (paid, pending, failed).
<Chip variant="soft" color="success">Paid</Chip>
<Chip variant="soft" color="warning">Pending</Chip>
<Chip variant="soft" color="danger">Failed</Chip>
<Chip variant="soft" color="primary">New</Chip>With an icon
startContent puts an icon before the label — handy for tags and categories.
<Chip variant="soft" startContent={<TagIcon />}>Design</Chip>
<Chip variant="soft" color="primary" startContent={<CheckIcon />}>
Verified
</Chip>Closable (removable)
Pass onClose and Chip shows a × button — the building block of a filter/token input. Track the list in state and drop the chip on close.
const [tags, setTags] = useState(["Design", "Dev", "QA"]);
{tags.map((t) => (
<Chip key={t} variant="soft" onClose={() => setTags(tags.filter((x) => x !== t))}>
{t}
</Chip>
))}With an avatar
The avatar prop tucks a small image at the chip's start — a person or entity token.
<Chip variant="bordered" avatar={<Avatar src="/hero/person-woman.jpg" name="Sara" />}>
Sara Whitfield
</Chip>Plain and closable chips
With no onClose, Chip is a static, non-interactive label — for status tags ("Active", "Pending") that aren't meant to be dismissed. Pass onClose and Chip grows a close button automatically; onClose fires when it's pressed, and removing the chip from your own state list is on you.
<Chip color="success">Active</Chip>
<Chip onClose={() => removeTag(tag.id)} color="default">
{tag.label}
</Chip>Selectable chips
Pass selected (controlled) or defaultSelected (uncontrolled) and Chip becomes a real toggleable control: role="button", aria-pressed reflecting the state, and both click and Enter/Space toggling it. Reach for this instead of onClose for a filter-style multi-select set of chips.
const TagFilter = ({ tags }: { tags: string[] }) => {
const [active, setActive] = useState<Set<string>>(new Set());
return (
<div className="flex flex-wrap gap-2">
{tags.map((tag) => (
<Chip
key={tag}
selected={active.has(tag)}
onSelectedChange={(isSelected) => {
setActive((prev) => {
const next = new Set(prev);
isSelected ? next.add(tag) : next.delete(tag);
return next;
});
}}
variant="bordered"
>
{tag}
</Chip>
))}
</div>
);
};Avatar and content slots
avatar renders at the very start of the chip (ahead of startContent), for a chip representing a person or entity with an image next to the label — a common pattern for a "tagged people" list. startContent/endContent sit around the label itself for anything else — an icon, a small count.
<Chip avatar={<Avatar size="xs" src={user.avatarUrl} />} onClose={() => untag(user.id)}>
{user.name}
</Chip>Variants
variant="dot" is a distinct fourth option alongside solid/soft/bordered — it renders a small colored dot ahead of the label instead of tinting the whole chip, a lighter-weight way to convey the same semantic color without as much visual weight.
<Chip variant="dot" color="success">Online</Chip>Props
The full public API for Chip. Every prop is stable and intentionally typed.
Content
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Chip label content. |
| avatar | ReactNode | — | Avatar rendered at the start of the chip. |
| startContent | ReactNode | — | Content rendered before the label. |
| endContent | ReactNode | — | Content rendered after the label, before the close button. |
| closeIcon | ReactNode | — | Overrides the default × close icon. |
children
ReactNode
default: —
Chip label content.
avatar
ReactNode
default: —
Avatar rendered at the start of the chip.
startContent
ReactNode
default: —
Content rendered before the label.
endContent
ReactNode
default: —
Content rendered after the label, before the close button.
closeIcon
ReactNode
default: —
Overrides the default × close icon.
Appearance
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "solid" | "soft" | "bordered" | "dot" | solid | Visual style variant. |
| shadow | "none" | "xs" | "xs-sm" | "sm" | "md" | "lg" | none | Box-shadow depth. |
| size | "xs" | "xs-sm" | "sm" | "md" | "lg" | "xl" | md | Chip size. |
| color | "default" | "primary" | "secondary" | "info" | "success" | "warning" | "danger" | default | Semantic color. |
| radius | "none" | "xs" | "xs-sm" | "sm" | "md" | "lg" | "full" | full | Border radius. |
| colorDepth | 50 | 100 | ... | 950 | 500 | Shade depth for the selected color. |
variant
"solid" | "soft" | "bordered" | "dot"
default: solid
Visual style variant.
shadow
"none" | "xs" | "xs-sm" | "sm" | "md" | "lg"
default: none
Box-shadow depth.
size
"xs" | "xs-sm" | "sm" | "md" | "lg" | "xl"
default: md
Chip size.
color
"default" | "primary" | "secondary" | "info" | "success" | "warning" | "danger"
default: default
Semantic color.
radius
"none" | "xs" | "xs-sm" | "sm" | "md" | "lg" | "full"
default: full
Border radius.
colorDepth
50 | 100 | ... | 950
default: 500
Shade depth for the selected color.
Behavior & states
| Prop | Type | Default | Description |
|---|---|---|---|
| isDisabled | boolean | false | Disables interaction. |
| selected | boolean | — | Controlled selected state; renders as a toggleable role="button" chip. |
| defaultSelected | boolean | — | Uncontrolled initial selected state. |
isDisabled
boolean
default: false
Disables interaction.
selected
boolean
default: —
Controlled selected state; renders as a toggleable role="button" chip.
defaultSelected
boolean
default: —
Uncontrolled initial selected state.
Events
| Prop | Type | Default | Description |
|---|---|---|---|
| onClose | (e: PressEvent) => void | — | Shows a close button and is called when it's pressed. |
| onSelectedChange | (selected: boolean) => void | — | Called when selected state changes. |
onClose
(e: PressEvent) => void
default: —
Shows a close button and is called when it's pressed.
onSelectedChange
(selected: boolean) => void
default: —
Called when selected state changes.
Styling
| Prop | Type | Default | Description |
|---|---|---|---|
| classNames | Partial<Record<ChipSlot, string>> | — | Per-slot class overrides (base/content/dot/avatar/closeButton/endContent). |
| className | string | — | Class applied to the root element. |
| style | CSSProperties | — | Inline styles for the root element. |
classNames
Partial<Record<ChipSlot, string>>
default: —
Per-slot class overrides (base/content/dot/avatar/closeButton/endContent).
className
string
default: —
Class applied to the root element.
style
CSSProperties
default: —
Inline styles for the root element.
Accessibility
- Only chips with selected/defaultSelected get button semantics (role="button", aria-pressed, keyboard activation) — a plain or closable chip with no selection state is not independently interactive itself, only its close button is.
- The close button rendered by onClose is a real, keyboard-operable button with its own accessible name, separate from the chip's own label.
- For a selectable chip, aria-pressed reflects the actual selected state, so assistive technology announces the toggle correctly on every change, not just visually.
Best practices
- Don't combine onClose and selected on the same chip — closable tags and selectable filter chips are different interaction patterns, and stacking both on one chip makes it unclear what a click actually does.
- Use variant="dot" for a dense list of status labels where a full-color pill per item would be visually heavy.
- For filter-style chip groups, drive selected from your own state (as in the TagFilter example) rather than relying on defaultSelected — you'll almost always need to read the current selection elsewhere (to filter a list, to show a count).
- Keep chip labels to a word or two; a chip stretching across multiple lines defeats the compact, scannable point of the component.