Tag
A small coloured label for a status or category. Badge and PlannerChip are covered here too.
import { Tag, Badge, PlannerChip } from "@/ui";Tag and PlannerChip match the Figma Tag and Planner Tag components.
Status tags
Most tags in the product show where something is in its life: a page, a creative, a sync. Use these four colours for status and nothing else.
<Tag color="success-green">Published</Tag>
<Tag color="column">Draft</Tag>
<Tag color="warning-yellow">Unpublished changes</Tag>
<Tag color="error-red">Sync failed</Tag>| Status | color | Background |
|---|---|---|
| Done, live, healthy | success-green | --color-tag-success-green #EBFFF3 |
| Draft, neutral, not started | column | --color-primary-100 #E0EBFE |
| Needs attention, queued | warning-yellow | --color-tag-warning-yellow #FEF3D5 |
| Failed, disabled | error-red | --color-tag-error-red #FFF2F2 |
The text is --color-text-primary (#232532) on these. Only the background changes, so the label has to say the status on its own.
All colours
Tag has twelve colours. Most are a light tint with dark text; orange, sea-green and violet also colour the text and icons, as in Figma. Outside status, use them to tell categories apart, such as data source or block type in a workflow.
<Tag color="column">column</Tag>
<Tag color="blue">blue</Tag>
<Tag color="orange">orange</Tag>
// …and the rest from the table belowcolor | Background | Text |
|---|---|---|
column | --color-primary-100 #E0EBFE | Primary text |
success-green | --color-tag-success-green #EBFFF3 | Primary text |
warning-yellow | --color-tag-warning-yellow #FEF3D5 | Primary text |
error-red | --color-tag-error-red #FFF2F2 | Primary text |
blue | --color-tag-blue #E0FDFF | Primary text |
green | --color-tag-green #E8FFE0 | Primary text |
sea-green | --color-tag-blue #E0FDFF | --color-misc-green #0D787F |
yellow | --color-tag-orange #FFEFE0 | Primary text |
orange | --color-tag-orange #FFEFE0 | --color-misc-orange #F87F00 |
pink | --color-tag-pink #FCE9F4 | Primary text |
purple | --color-tag-purple #EEE0FF | Primary text |
violet | --color-tag-purple #EEE0FF | --color-misc-purple #9747FF |
These follow Figma, which differs from the --color-tag-* tokens in four places: Column is Primary 100 (the token is grey #EEF0F7), Yellow is #FFEFE0 (the token is #FFFEE0), and Sea Green and Violet reuse the Blue and Purple fills. The tokens are unchanged, since other components use them.
blue is a cyan tint (#E0FDFF), not the primary blue family. purple and violet exist in the CSS but are outside the product palette and no screen uses them; do not start using them. In the app today only column, success-green, warning-yellow, error-red, blue and orange appear.
Sizes
sm is the default and what every screen uses. Use md in filter bars where the tag can be removed, and lg only next to 14px body text.
<Tag size="sm" color="success-green">Published</Tag>
<Tag size="md" color="success-green">Published</Tag>
<Tag size="lg" color="success-green">Published</Tag>| Size | Height | Text | Line height | Padding | Icon |
|---|---|---|---|---|---|
sm | 20px | 12px | 16px | 2px 8px | 10px |
md | 23px | 12px | 19px | 2px 10px | 12px |
lg | 26px | 14px | 22px | 2px 10px | 12px |
All sizes have an 8px radius (--radius-md) and a 6px gap between icon and label. Icons are --color-grey-900. Icon-only tags are 26px wide at sm and 32px at md and lg.
Figma sets sm at 10px. Code uses 12px, because 12px is the smallest text in the product.
With an icon
Pass a Phosphor icon component to prefixIcon, not an element. It is sized to match the tag.
import { FolderPlus, Lightning, Timer } from "@phosphor-icons/react";
<Tag color="blue" prefixIcon={FolderPlus}>New workbook</Tag>
<Tag color="column" prefixIcon={Timer}>Runs every Monday</Tag>
<Tag color="orange" size="md" prefixIcon={Lightning}>Formula</Tag>iconOnly hides the label and shows only the prefix icon. Tag cannot take an aria-label, so an icon-only tag has no accessible name. Avoid it, or put the meaning in nearby text.
Removable
Set suffixIcon to add a close button and handle onRemove. Try removing a filter below.
const [filters, setFilters] = useState(["Paid search", "LinkedIn", "Q3 webinar", "EMEA"]);
{filters.map((f) => (
<Tag
key={f}
color="column"
size="md"
suffixIcon
onRemove={() => setFilters((xs) => xs.filter((x) => x !== f))}
>
{f}
</Tag>
))}suffixIcon is a boolean here, not an icon: the close icon is always an X.
Badge
Badge is a thin wrapper over Tag that maps five semantic names to Tag colours. Its source marks it as retired in favour of Tag, but 9 files outside _archive still import it (schedules, Slack settings, skills, the workflow modal and legacy dashboards). Use Tag in new code.
<Badge variant="success">Connected</Badge>
<Badge variant="warning">Paused</Badge>
<Badge variant="danger">Email failed</Badge>
<Badge variant="muted">Not connected</Badge>
<Badge variant="accent">Shared</Badge>Badge variant | Renders as |
|---|---|
success | <Tag color="success-green"> |
warning | <Tag color="warning-yellow"> |
danger | <Tag color="error-red"> |
muted (default) | <Tag color="column"> |
accent | <Tag color="blue"> |
Badge always renders at sm and passes on only variant, className and children. Any other prop is dropped. ScheduleRunHistory passes title={run.email_error} to a Badge, so that error text never shows.
PlannerChip
PlannerChip is a different idea that shares Tag's shape: a monospace chip for a table or column name inside workbook planner steps. It is only used in WorkbookChat.
Join <PlannerChip size="lg" prefixIcon>hubspot_deals</PlannerChip> to{" "}
<PlannerChip size="lg" prefixIcon>google_ads_campaigns</PlannerChip> on{" "}
<PlannerChip size="lg">utm_campaign</PlannerChip>, then sum{" "}
<PlannerChip size="lg">amount</PlannerChip> by campaign.<PlannerChip size="sm">pipeline_amount</PlannerChip>
<PlannerChip size="md">pipeline_amount</PlannerChip>
<PlannerChip size="lg">pipeline_amount</PlannerChip>
<PlannerChip size="md" prefixIcon>hubspot_deals</PlannerChip>
<PlannerChip size="md" prefixIcon suffixIcon active>hubspot_deals</PlannerChip>It uses JetBrains Mono (loaded from Google Fonts by its own CSS) on --color-grey-50 with a --color-grey-200 border and a 4px radius. Hover darkens the background to --color-grey-100, and active gives it a --color-grey-300 border, matching Figma's Rest, Hover and Active states. Text sizes are 12px, 12px and 14px for sm, md and lg; Figma's sm is 10px.
The text colour is a hard-coded #B85300, not a token. prefixIcon is a boolean that always shows the Phosphor Table icon, unlike Tag where it takes an icon component. At lg the icon stays at 12px.
Props
Tag
| Prop | Type | Default | Notes |
|---|---|---|---|
children | ReactNode | The label. | |
color | "column" | "success-green" | "warning-yellow" | "error-red" | "blue" | "green" | "sea-green" | "yellow" | "orange" | "pink" | "purple" | "violet" | "blue" | Always pass it. The default is rarely the right colour. |
size | "sm" | "md" | "lg" | "sm" | |
prefixIcon | Phosphor icon component | null | |
iconOnly | boolean | false | Hides the label. Needs prefixIcon. |
suffixIcon | boolean | false | Shows the remove button. |
onRemove | () => void | Called when the remove button is clicked. | |
className | string | For layout only. |
Tag does not pass any other prop through. title, aria-*, data-* and onClick are all ignored.
Badge
| Prop | Type | Default | Notes |
|---|---|---|---|
variant | "success" | "warning" | "danger" | "muted" | "accent" | "muted" | Unknown values fall back to muted. |
children | ReactNode | ||
className | string |
PlannerChip
| Prop | Type | Default | Notes |
|---|---|---|---|
children | ReactNode | The token, such as a table or column name. | |
size | "sm" | "md" | "lg" | "sm" | |
prefixIcon | boolean | false | Shows the Table icon. |
suffixIcon | boolean | false | Shows a remove button. |
onRemove | () => void | ||
active | boolean | false | Grey 300 border, for the chip that is selected. |
className | string |
Accessibility
- Tag, Badge and PlannerChip render a
<span>. They are not focusable and have no role, so do not attach behaviour to them. - Colour is not read out. The label must carry the meaning: write "Sync failed", not just a red tag with "Sync".
- The remove button is a real
<button>witharia-label="Remove". Every removable tag gets the same label, so a screen reader hears "Remove, button" with no name. If people will remove tags often, add a visually hidden list of the active filters nearby.
Do and don't
- Do use the four status colours consistently: green for done, grey
columnfor draft, yellow for needs attention, red for failed. - Do keep labels to one to three words: "Published", "Unpublished changes".
- Do import from
@/ui. Some screens import from@/ui/components/Tags/Tag/Tag, which works but bypasses the index. - Don't use Badge in new code.
- Don't use
purpleorviolet. - Don't make a tag clickable. If it needs to do something, use a Button or a TabToggle.