--primitiv-tag-background--primitiv-tag-foreground--primitiv-tag-height--primitiv-tag-padding-inline--primitiv-tag-font-family--primitiv-tag-font-size--primitiv-tag-font-weight--primitiv-tag-line-height--primitiv-tag-radiusA small label chip that tags content by category, topic, or status — typically shown in a group. Read-only, never interactive. Five tones (a plain neutral treatment plus four semantic tones), sized xs–xl; data-density scales each size further.
primitiv add tag installs it whichever mode you are reading.Preview
import { Tag } from "@/components/ui/tag";
<Tag size="md" tone="neutral">Design</Tag>Density is set by a data-density ancestor — the Context system, not a Tag prop.
npx primitiv add tagpnpm dlx primitiv add tagyarn dlx primitiv add tagbunx primitiv add tagImport
import { Tag } from "@/components/ui/tag";No headless primitive — this one ships only as a copied styled surface, so primitiv add is the only way in, whichever mode you are reading.
Extends HTMLSpanElement — every native attribute of that element is accepted and forwarded.
| Prop | Type | Default | Description |
|---|---|---|---|
| asChild | boolean | — | Render the single child element instead of a wrapping <span>, merging
the tag classes onto it — e.g. <Tag asChild><a>...</a></Tag>. |
| tone | "neutral" | "success" | "warning" | "info" | "danger" | neutral | Semantic colour. neutral is the plain grey default; the other four are reserved for status. |
| size | "xs" | "sm" | "md" | "lg" | "xl" | md | Tag size; data-density scales each size further. |
--primitiv-tag-background--primitiv-tag-foreground--primitiv-tag-height--primitiv-tag-padding-inline--primitiv-tag-font-family--primitiv-tag-font-size--primitiv-tag-font-weight--primitiv-tag-line-height--primitiv-tag-radius<span>, takes no focus and handles no events — if you need something clickable, removable, or filterable that looks like this, use Chip, which is built for it, rather than putting a handler on a tag.neutral is the default, most tags rely on their words entirely, which is the right instinct for a semantic tone too.<ul>/<li> or a region with an aria-label rather than leaving a bare run of <span>s.asChild the classes merge onto your element and its semantics stay yours — an <abbr> is still an <abbr>. Nothing about the tag overrides the role, which is why it is the right hook for marking up an abbreviation or a list item.neutral tone for categories and topics; Badge drops neutral so every badge is one of four statuses. Choose on whether the label is a category (Tag) or a state (Badge) — and choose Chip the moment it needs to be interactive.Five tones, and neutral leads for a reason: a tag usually labels a category or topic, which carries no status, so the plain grey treatment is the default and the workhorse. The four semantic tones (success, warning, info, danger) are the exception — reach for one only when the tag genuinely signals state. If you want a status pill that is meant to stand out, that is Badge, whose palette drops the neutral this one keeps.
import { Tag } from "@/components/ui/tag";
<Tag tone="neutral">Neutral</Tag><Tag tone="success">Success</Tag><Tag tone="warning">Warning</Tag><Tag tone="info">Info</Tag><Tag tone="danger">Danger</Tag>Tags rarely travel alone — the common shape is a group labelling one thing by several categories at once. The group is your own layout (a row-direction Stack that wraps here), and the tags inside it stay neutral, since a list of topics is not a list of statuses. Give the group an accessible name if the set of tags means something as a whole.
import { Tag } from "@/components/ui/tag";import { Stack } from "@/components/ui/stack";
<Stack direction="row" gap="sm" wrap="wrap"> <Tag>Design</Tag> <Tag>Engineering</Tag> <Tag>Research</Tag> <Tag>Marketing</Tag></Stack>Five sizes, each rescaling again with the nearest data-density ancestor — a tag in a dense table row wants xs, one beside a heading lg. Change the density above and the whole ramp shifts: that is the Context system, not a Tag prop.
import { Tag } from "@/components/ui/tag";
<div data-density="comfortable"> <Tag size="xs">Design</Tag> <Tag size="sm">Design</Tag> <Tag size="md">Design</Tag> <Tag size="lg">Design</Tag> <Tag size="xl">Design</Tag></div>asChild merges the tag's classes onto your own element instead of wrapping a <span> — for marking up an <abbr> or a list <li> that should read as a tag while keeping its own semantics. It is not a way to make a tag clickable: a tag is read-only by design, and a filterable, removable, or linked pill is a Chip, which is built to be interactive.
import { Tag } from "@/components/ui/tag";
<Tag asChild tone="info"> <abbr title="TypeScript">TS</abbr></Tag>