@mshafiqyajid/react-chip
Headless chip hook and styled component. Four variants, six tones, selectable, dismissible, icon slots, fully accessible.
Playground #
NeutralPrimarySuccessWarningDangerInfoDismissible
Chip label
Props
TSX
import { ChipStyled } from "@mshafiqyajid/react-chip/styled";
import "@mshafiqyajid/react-chip/styles.css";
<ChipStyled />Install #
npm install @mshafiqyajid/react-chip Quick start #
import { ChipStyled } from "@mshafiqyajid/react-chip/styled";
import "@mshafiqyajid/react-chip/styles.css";
<ChipStyled tone="primary">Label</ChipStyled>
<ChipStyled tone="success" variant="solid">Active</ChipStyled>
<ChipStyled tone="danger" dismissible onDismiss={() => {}}>Remove me</ChipStyled> Selectable chips #
const [selected, setSelected] = useState(false);
<ChipStyled
tone="primary"
selectable
selected={selected}
onSelect={setSelected}
>
Toggle me
</ChipStyled>
// Uncontrolled with defaultSelected
<ChipStyled tone="primary" selectable defaultSelected>
Pre-selected
</ChipStyled> With icons #
<ChipStyled
tone="primary"
icon={<StarIcon />}
iconRight={<ChevronDownIcon />}
>
Featured
</ChipStyled>
// Avatar takes priority over icon
<ChipStyled tone="neutral" avatar={<img src="/avatar.jpg" alt="" />}>
Shafiq Yajid
</ChipStyled> Headless #
import { useChip } from "@mshafiqyajid/react-chip";
function MyChip({ label }: { label: string }) {
const { chipProps, dismissProps, isSelected, isDismissed } = useChip({
selectable: true,
dismissible: true,
onSelect: (s) => console.log("selected:", s),
onDismiss: () => console.log("dismissed"),
});
if (isDismissed) return null;
return (
<span {...chipProps} className="my-chip" data-selected={isSelected || undefined}>
{label}
<button {...dismissProps}>×</button>
</span>
);
} API #
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "solid" | "subtle" | "outline" | "soft" | "subtle" | Visual style |
| tone | "neutral" | "primary" | "success" | "warning" | "danger" | "info" | "neutral" | Color tone |
| size | "sm" | "md" | "lg" | "md" | Size scale |
| selectable | boolean | false | Enable selection toggle; adds role="option" and keyboard support |
| selected | boolean | — | Controlled selected state |
| defaultSelected | boolean | false | Uncontrolled initial selected state |
| onSelect | (selected: boolean) => void | — | Called when selection changes |
| dismissible | boolean | false | Show dismiss button with aria-label="Remove" |
| onDismiss | () => void | — | Called when chip is dismissed |
| icon | ReactNode | — | Leading icon |
| iconRight | ReactNode | — | Trailing icon |
| avatar | ReactNode | — | Leading avatar element (takes priority over icon) |
| disabled | boolean | false | Disable all interactions |
| children | ReactNode | — | Chip label (required) |
| className | string | — | Extra class on the root element |
| style | CSSProperties | — | Inline style override |
Headless API — useChip #
| Option | Type | Default | Description |
|---|---|---|---|
| selectable | boolean | false | Enable selection behavior |
| selected | boolean | — | Controlled selected state |
| defaultSelected | boolean | false | Initial uncontrolled selected state |
| onSelect | (selected: boolean) => void | — | Called on selection change |
| dismissible | boolean | false | Enable dismiss behavior |
| onDismiss | () => void | — | Called when dismissed |
| disabled | boolean | false | Disable all interactions |
Returns chipProps, dismissProps, isSelected, isDismissed, select(v?), and dismiss(). Dismissed chips return isDismissed=true — render null in your component to remove them from the DOM.