Label Chip
FreeA tag in any colour a user picks, kept readable in both themes by CSS alone.
Static tags, the way they sit on a card. Switch the site theme: every one stays readable. Pick any colour and add one.
npx shadcn@latest add @ui-shrushank/label-chipThe install writes a single file to components/ui/LabelChip.tsx, which exports LabelChip.
import { LabelChip } from "@/components/ui/LabelChip";<LabelChip color="Color">Label</LabelChip>A tag in a colour the user chose. Issue trackers, CRMs and note apps all let people pick label colours, and all of them then have to make a stranger's pick readable: a pale yellow vanishes as text on white, a near-black vanishes on a dark card, and a saturated red shouts as a solid fill. The Badge has four fixed tones; this is the one for a palette nobody designed.
Every colour is derived in CSS from the one hex passed in, so there is no contrast maths in JavaScript and nothing for the server and the browser to disagree about. The fill is the hue mixed into the surface with color-mix(in oklab, ...) (14% on white, 20% on the stone-900 card). The text is the same hue with its OKLCH lightness clamped by relative colour syntax: at most 0.45 on light and at least 0.82 on dark, which lands at roughly 6:1 or better in both themes whatever was picked, and chroma is capped so a neon pick does not vibrate. The dot keeps the raw colour with an inset hairline, so a white or near-black label still shows an edge. One component covers the three places a label appears: static on a card, a toggle in a filter bar (aria-pressed, a deeper fill and a hairline in the text colour when on) and removable on an open item. A browser without relative colour syntax drops only the text tint and inherits the surrounding text colour, which is still legible.
A label keeps its identity (the hue is recognisably the one picked) without ever costing legibility, and it looks the same family in light and dark rather than being re-picked per theme.
Every line below was read out of the file you are about to copy, so you can check each one against the source at the bottom of this page.
- Carries
aria-label,aria-pressed, so its state is exposed and not left to the visual treatment alone. - Draws a
focus-visiblering that is separate from its hover treatment, so keyboard users get an affordance mouse users do not take away.
2 required, 6 optional.
| Prop | Type | Default |
|---|---|---|
colorrequiredAny CSS colour, usually a hex the user picked. | string | none |
childrenrequired | ReactNode | none |
size | "sm" | "md" | "md" |
dotHide the leading dot, e.g. in a dense row where the fill already carries the hue. | boolean | true |
pressedTurns the chip into a toggle button (a filter). | boolean | none |
onPressedChange | (pressed: boolean) => void | none |
onRemoveAdds a remove button after the name. | () => void | none |
className | string | none |
colorrequiredstringAny CSS colour, usually a hex the user picked.
childrenrequiredReactNodesize"sm" | "md"· default"md"dotboolean· defaulttrueHide the leading dot, e.g. in a dense row where the fill already carries the hue.
pressedbooleanTurns the chip into a toggle button (a filter).
onPressedChange(pressed: boolean) => voidonRemove() => voidAdds a remove button after the name.
classNamestring
One file, no runtime package. Copy it, or install it with the command at the top of this page.