Design
Tag Selection
Mandatory rules for Status Tag, Label Tag, shadcn Badge, and filter chips — read-only display vs interactive pills.
Use this page as the single source of truth when choosing a pill-shaped UI element. There is no “use whatever looks nice” path.
Three Principles
- Read-only display (user cannot click or remove) → only Status Tag or Label Tag.
- Interactive (select, remove, navigate) → only shadcn
Badgeinside controls, or Data Table filter chips — never Label Tag. - Numeric overlay on an icon or avatar (unread count) → future Count Badge; do not fake it with Tag or Badge text.
Decision Tree
Where does this pill appear?
├─ Table cell or detail field showing a stored value?
│ ├─ Good/bad/urgent/processing semantics? → StatusTag (schema kind: "status")
│ └─ Category, role, channel, type? → LabelTag (schema kind: "label")
│
├─ Inside an input (Multi-Select selected items)?
│ └─ shadcn Badge (removable chip) — do not use LabelTag
│
├─ Active filter with remove (×) on the filter bar?
│ └─ DataTableFilterBar chip — do not hand-roll Badge or LabelTag
│
└─ Unread count on Bell / avatar?
└─ Not implemented yet — avoid Tag; use CountBadge when availableScenario Matrix
| Scenario | Component | Notes |
|---|---|---|
| Order status, payment, priority | Status Tag | kind: "status", StatusVariantMap with tone |
| In progress with spinner | Status Tag | icon: "spin" on that variant only |
| Completed / failed with icon | Status Tag | icon: "auto" — opt-in, not table default |
| Category, channel, role, type | Label Tag | kind: "label", LabelVariantMap; default neutral |
| Dense table columns | Status / Label Tag | Omit icon unless the state needs emphasis |
| Multi-Select selected option | shadcn Badge | Full pill, removable — component-internal |
| Filter bar active condition | Filter bar chip | Part of data-table filter surfaces |
| Detail page role (read-only) | Label Tag | Same as users table — not shadcn Badge |
| Marketing / link pill | shadcn Badge | asChild + link OK; not for table enums |
Sidebar unread 3 | Count Badge (future) | Not Tag |
Status Tag vs Label Tag
| Question | Yes → | No → |
|---|---|---|
| Should the user care if the value is good, bad, or urgent? | Status Tag | Label Tag |
Can you use success / warning / destructive? | Status only | Never on labels |
| Spinner while waiting? | icon: "spin" on Status | Labels do not spin |
Schema:
StatusVariantMap:{ label, tone, icon? }—tonerequired.LabelVariantMap:{ label, accent? }— notone; optionalaccent: "auto".
See Data Table — Status Tag and formatters.
Label Tag vs shadcn Badge
Both can look like small capsules. They are not interchangeable.
| Label Tag | shadcn Badge | |
|---|---|---|
| Job | Show a field value | Part of a control (selection, link) |
| Shape | rounded-md + visible border | rounded-4xl pill |
| Interaction | Static | Often removable / clickable |
| Color | Non-semantic accent or neutral | primary, secondary, outline, … |
| Import | @/components/f-ui/label-tag | @/components/ui/badge |
Visual rule of thumb: bordered, squarer, in a table cell → data tag. Full pill inside an input with × → shadcn Badge.
Do not display the same field two ways (e.g. Label Tag in the table and Badge on the detail page for role).
What We Do Not Ship
| Component | Why |
|---|---|
| f-ui Badge for status/labels | Overlaps Status Tag / Label Tag; confuses variant vs tone |
| Label Tag for filters | Use filter bar or Multi-Select |
| shadcn Badge for table status/category columns | Use schema kind: "status" / "label" |
Code Review Checklist
- Table/detail enum uses
kind: "status"orkind: "label", not legacybadge. - No
import { Badge } from "@/components/ui/badge"for read-only status, category, or role. - Label fields do not use semantic status tones.
- Processing states use
icon: "spin"only where the value is truly in-flight. - Same field uses the same tag component on list and detail views.
Related
- Status Tag — API and tone presets
- Label Tag — API and accents
- Data Table — schema-driven rendering