Design system

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>
StatuscolorBackground
Done, live, healthysuccess-green--color-tag-success-green #EBFFF3
Draft, neutral, not startedcolumn--color-primary-100 #E0EBFE
Needs attention, queuedwarning-yellow--color-tag-warning-yellow #FEF3D5
Failed, disablederror-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 below
colorBackgroundText
column--color-primary-100 #E0EBFEPrimary text
success-green--color-tag-success-green #EBFFF3Primary text
warning-yellow--color-tag-warning-yellow #FEF3D5Primary text
error-red--color-tag-error-red #FFF2F2Primary text
blue--color-tag-blue #E0FDFFPrimary text
green--color-tag-green #E8FFE0Primary text
sea-green--color-tag-blue #E0FDFF--color-misc-green #0D787F
yellow--color-tag-orange #FFEFE0Primary text
orange--color-tag-orange #FFEFE0--color-misc-orange #F87F00
pink--color-tag-pink #FCE9F4Primary text
purple--color-tag-purple #EEE0FFPrimary 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>
SizeHeightTextLine heightPaddingIcon
sm20px12px16px2px 8px10px
md23px12px19px2px 10px12px
lg26px14px22px2px 10px12px

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

PropTypeDefaultNotes
childrenReactNodeThe 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"
prefixIconPhosphor icon componentnull
iconOnlybooleanfalseHides the label. Needs prefixIcon.
suffixIconbooleanfalseShows the remove button.
onRemove() => voidCalled when the remove button is clicked.
classNamestringFor layout only.

Tag does not pass any other prop through. title, aria-*, data-* and onClick are all ignored.

Badge

PropTypeDefaultNotes
variant"success" | "warning" | "danger" | "muted" | "accent""muted"Unknown values fall back to muted.
childrenReactNode
classNamestring

PlannerChip

PropTypeDefaultNotes
childrenReactNodeThe token, such as a table or column name.
size"sm" | "md" | "lg""sm"
prefixIconbooleanfalseShows the Table icon.
suffixIconbooleanfalseShows a remove button.
onRemove() => void
activebooleanfalseGrey 300 border, for the chip that is selected.
classNamestring

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> with aria-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 column for 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 purple or violet.
  • Don't make a tag clickable. If it needs to do something, use a Button or a TabToggle.

On this page