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}
/>Configure <Chip> defaults application-wide.
import { patchTheme } from '@dashforge/tw-theme';
patchTheme({
components: {
Chip: {
defaults: { color: 'neutral', variant: 'soft', size: 'md' },
},
},
});Configurable axes (ChipVariantProps):
| Axis | Type | Notes |
|---|---|---|
variant | 'soft' | 'solid' | 'outline' | Dashforge chip vocabulary (distinct from Button's / Alert's). |
color | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'neutral' | Semantic intent. |
size | 'sm' | 'md' | 'lg' | Font-size + padding. |
Non-visual axes (label, icon, avatar, clickable, onClick, selected, onDelete, deleteIcon, deleteLabel, disabled, access, visibleWhen) are not theme-configurable — content, behavior, and interaction state are per-instance.
Precedence chain (lowest → highest):
defaultVariants from the internal chipVariants recipe.theme.components.Chip.defaults (application-wide).<Chip color="danger" />) — wins over theme.sx — appended to the root and wins over conflicting classes via tailwind-merge.TypeScript: theme.components.Chip.defaults autocompletes to the three axes above.
Reactivity: useComponentDefaults('Chip') subscribes to the theme store — patchTheme re-renders every mounted instance inheriting the changed axis.
| Prop | Type | Default | Description |
|---|---|---|---|
label | ReactNode | — | The chip's textual content. Named label for MUI parity — most design systems (MUI, Radix Themes, Mantine, Tremor) use label as the canonical content prop on Chip / Badge / Pill primitives. |
access | AccessRequirement | — | RBAC requirement. Same contract as <Button> / <IconButton> — onUnauthorized: 'hide' | 'disable' | 'readonly'. (readonly maps to disabled for chips, matching Button.) |
avatar | ReactNode | — | Leading avatar — alternative to icon, typically a small circular image (Avatar component or <img>). When BOTH icon and avatar are set, avatar wins (matches MUI behaviour). |
className | string | — | Standard React className — appended to the root via cn(). |
clickable | boolean | — | Makes the chip interactive — renders as <button> (instead of <span>), adds focus ring + hover state. Set automatically to true when onClick is provided. |
color | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'neutral' | 'neutral' | Semantic intent role. Drives bg/fg color pair. |
deleteIcon | ReactNode | — | Custom delete icon. Defaults to an inline stroke-style × SVG. currentColor flows through, so the icon inherits the chip's text colour. |
deleteLabel | string | 'Remove' | aria-label for the delete button. Required for screen readers since the × glyph alone is not semantic. |
disabled | boolean | false | Disable both the main click and the delete button. |
icon | ReactNode | — | Leading icon — typically a small SVG (16px). Consumer brings their iconography. Rendered inside the chip BEFORE the label. Falls back to avatar if not provided. |
onClick | (event: React.MouseEvent<HTMLElement>) => void | — | Click handler. Implies clickable=true. Fires on chip-body click (NOT on the delete button — onDelete is the dedicated path for that). |
onDelete | (event: React.MouseEvent<HTMLElement>) => void | — | When set, renders a trailing × delete button. The handler fires on delete-button click and stops propagation so the chip's onClick doesn't also fire. |
selected | boolean | false | Toggle-style selected state. When true, sets aria-pressed on the clickable chip for filter-chip patterns. |
size | 'md' | 'sm' | 'md' | Density tier. |
sx | ClassValue | — | Root-element class shortcut (string or clsx-compatible value). Last-wins via tailwind-merge inside cn(). |
variant | 'soft' | 'solid' | 'outline' | 'soft' | Visual treatment — soft (tinted bg + saturated text), solid (filled), outline (border + transparent bg). |
visibleWhen | (engine: Engine) => boolean | — | Reactive visibility predicate. Re-evaluated on every engine state change when the chip is mounted inside a <DashForm>; outside a form, evaluated as a plain predicate (the consumer captures any external state in the closure).When the predicate returns false, the component renders null — same contract as <Alert> / <TextField> / other bridge-aware components. Useful for status chips that should appear / disappear based on form values without forcing JSX guards on the consumer. |
<Chip> has no slots. It renders a single element (a <span>, or a <button> when clickable) carrying the label (with optional icon / avatar prefix and onDelete suffix) — there is no anatomy to target separately.
The slotProps API pattern only exists on compound components with multiple visually distinct regions. Examples: <Autocomplete> (16 slots including popover, listItem, chip), <Menu>, <DataGrid>.
For <Chip>, use sx (documented in the Props table above) for any style override. The icon, avatar, deleteIcon, and label props already accept ReactNode, so you can compose custom sub-elements inline when you need more control than the base pill affords.
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.