Browse docs
Browse docs
Display a status. Filter a list. Tag a record.
import { Chip } from '@dashforge/tw';
<Chip label="Active" color="success" /><Chip label="Pending" color="warning" />
<Chip label="Premium" color="primary" variant="solid" icon={<Crown size={14} />} />
<Chip label="Beta" color="info" variant="outline" />Chip ships three visual variants — note that soft is unique to Chip (it doesn't exist on Button or Alert). It's the default because it's the natural tinted pill look.
import { Chip, Stack } from '@dashforge/tw';
<Stack direction="row" gap={2} align="center">
<Chip label="Soft (default)" color="primary" variant="soft" />
<Chip label="Solid" color="primary" variant="solid" />
<Chip label="Outline" color="primary" variant="outline" />
</Stack>See Design decisions → variant vocabularies for why the variant names differ across components.
The full 7-color palette at the default soft variant:
import { Chip, Stack } from '@dashforge/tw';
<Stack direction="row" gap={2} wrap align="center">
<Chip label="Default" color="default" />
<Chip label="Primary" color="primary" />
<Chip label="Secondary" color="secondary" />
<Chip label="Success" color="success" />
<Chip label="Warning" color="warning" />
<Chip label="Danger" color="danger" />
<Chip label="Info" color="info" />
</Stack>Total addressable matrix: 3 variants × 7 colors = 21 chip styles, all token-driven.
Two leading-slot patterns, MUI Chip parity:
// Icon slot (any ReactNode)
<Chip label="Verified" color="success" icon={<CheckCircle size={14} />} />
// Avatar slot — typically an <Avatar /> from the same lib
<Chip label="Jane Doe" avatar={<Avatar name="Jane Doe" size="xs" />} />When clickable is true the chip becomes a <button role="button"> — full keyboard support (Enter and Space activate, focus ring visible). onClick is required when clickable.
<Chip label="Filter" color="info" clickable onClick={() => applyFilter()} />selected toggles aria-pressed for the filter chip UX. Combine with clickable:
function FilterChips() {
const [active, setActive] = useState<Set<string>>(new Set(['frontend']));
const toggle = (tag: string) => {
const next = new Set(active);
next.has(tag) ? next.delete(tag) : next.add(tag);
setActive(next);
};
return (
<Stack direction="row" spacing="sm">
{['frontend', 'backend', 'design'].map((tag) => (
<Chip key={tag} label={tag}
color="primary"
clickable
selected={active.has(tag)}
onClick={() => toggle(tag)}
/>
))}
</Stack>
);
}onDelete adds an inline X button. It does NOT trigger the main onClick — the two handlers are independent (the close button stops propagation).
import { useState } from 'react';
import { Chip, Stack, Button } from '@dashforge/tw';
function TagList() {
const [tags, setTags] = useState(['frontend', 'backend', 'design', 'devops']);
return (
<Stack gap={2}>
<Stack direction="row" gap={2} wrap align="center">
{tags.map((tag) => (
<Chip key={tag} label={tag} color="primary"
onDelete={() => setTags(prev => prev.filter(t => t !== tag))}
/>
))}
</Stack>
</Stack>
);
}<Chip label="Small" size="sm" />
<Chip label="Medium" size="md" /> {/* default */}<Chip label="Admin badge" color="warning"
access={{ requires: 'workspace.admin', when: 'denied:hide' }}
/>
<Chip label="New" color="info"
visibleWhen={(engine) => engine.getValue('feature.flags.new') === true}
/>| Prop | Type | Default | Description |
|---|---|---|---|
label | ReactNode | (required) | The chip's text content. |
color | 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'default' | Semantic intent. |
variant | 'soft' | 'solid' | 'outline' | 'soft' | Visual treatment. soft is Chip-specific (tinted bg + same-color fg). |
size | 'sm' | 'md' | 'md' | Height + padding + font size. |
icon | ReactNode | — | Leading icon slot. |
avatar | ReactNode | — | Leading avatar slot (typically an <Avatar />). Mutually exclusive with icon. |
clickable | boolean | false | Render as <button> with keyboard a11y. onClick is required when true. |
onClick | () => void | — | Click handler (required when clickable). |
selected | boolean | false | Toggle aria-pressed for filter chip pattern. |
onDelete | () => void | — | When set, renders an inline X delete button. |
deleteIcon | ReactNode | default X SVG | Custom icon for the delete button. |
deleteLabel | string | 'Delete' | Accessible label for the delete button. |
disabled | boolean | false | Disable both main click and delete. |
access | AccessSpec | — | RBAC gating. |
visibleWhen | (engine: Engine) => boolean | — | Engine-reactive predicate. |
className | string | — | Merged via tailwind-merge. |
sx | string | — | String-form Tailwind override. |
chipVariants is exported and is the single source of truth shared with the Table cell renderer <RenderChip> — which was refactored in Sprint 4.4 to be a thin <Chip label={children}> wrapper. No two looks duplicated.
soft is intentional, not a typo. See Design decisions for why the Chip vocabulary differs from Button (solid/outline/ghost/link) or Alert (standard/filled/outlined).<RenderChip> consolidation: the Table cell renderer is a thin wrapper since 1.1.1. If you find yourself rebuilding Chip-style pills inside a Table cell, just import <Chip> directly.clickable. The chip renders as a <span>, which is the right semantics for a passive badge.