Skip to content
Primitiv home
Framework
Consumption mode

Tag

stableSource Figma

A 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.

Playground

Density

Preview

Design
Size
Tone
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.

Installation

npx primitiv add tag

Import

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.

Props

Tag

Extends HTMLSpanElement — every native attribute of that element is accepted and forwarded.

PropTypeDefaultDescription
asChildboolean—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"neutralSemantic colour. neutral is the plain grey default; the other four are reserved for status.
size"xs" | "sm" | "md" | "lg" | "xl"mdTag size; data-density scales each size further.

Styling contract

--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

Accessibility

  • A tag is read-only. It renders a <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.
  • The tone is not the message. Colour alone carries no meaning for a screen-reader user or anyone who cannot distinguish the hues, so the text has to say it — and because neutral is the default, most tags rely on their words entirely, which is the right instinct for a semantic tone too.
  • A group of tags is your own markup, so its semantics are yours to give: if the set means something as a whole ("topics", "applied filters"), wrap it in a labelled <ul>/<li> or a region with an aria-label rather than leaving a bare run of <span>s.
  • Under 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.
  • Tag or Badge? Tag carries the 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.

Examples

Tones

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.

Density
NeutralSuccessWarningInfoDanger
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>

A group of tags

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.

Density
DesignEngineeringResearchMarketing
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>

Sizes and density

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.

Density
DesignDesignDesignDesignDesign
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>

As another element (asChild)

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.

Density
TS
import { Tag } from "@/components/ui/tag";
<Tag asChild tone="info">  <abbr title="TypeScript">TS</abbr></Tag>