Components
Tag
A small, non-interactive label that says what something is.
Use it for status and category — a cuisine, a level, a state. It describes; it does not respond.
Watch out. If it can be clicked, it is a Chip. A tag that looks clickable is the most common mistake here.
Sub-brandTheme
Liverendered from the shipped recipe
Style
outline default
filled
filled-outline
Code
The HTML for the live example above is on the page - use your browser's inspector, or copy the CSS here and the markup from the preview. Load post-ui.css once per site and this component works anywhere.
/* ============================================================================
POST UI System — components
HAND-WRITTEN. Not generated: this file encodes the RULES the components follow,
which is not the same thing as whatever the Figma canvas currently looks like.
The contract it implements (variant axes + reasoning) is synced in components.json.
Import order: tokens.css -> type.css -> components.css
NAMING. Every class is `post-` prefixed. This ships into Webflow projects where
`.button`, `.field`, `.label` or `.text-body` are exactly the classes a site
already has, and an unprefixed collision would silently restyle real content.
Block `post-x` · modifier `post-x--y` · state `is-*`.
THE FOUR RULES EVERYTHING BELOW OBEYS
1. Tokens are named by scale, never by consumer — space/m, stroke/m, icon-size/m
and text/md all mean "the middle step".
2. Strokes are altitude, not component — s detail · m component · l section.
3. Optical padding — outer padding is uniform, the LABEL carries space/xs left and
right, and the gap is 0. Text ends up inset by outer+xs while an icon sits at
outer, which is correct: a glyph carries internal whitespace and text does not.
One recipe covers icon-left, icon-right, both, or neither.
4. Control height comes from the ICON TIER, never from text. Every control label
carries min-height = its icon-size token. Without it the icon becomes the
tallest child and toggling an optional icon grows the control — a chip jumped
28 -> 32px exactly this way. With it, toggling an icon changes width only.
currentColor is load-bearing. Outline/ghost controls bind their label fill AND
their stroke to the same `action` token, so the border is written as
`border-color: currentColor` and one colour change moves both.
============================================================================ */
/* ---- tag ---- */
/* NON-interactive info marker — category, status, a short piece of text.
RENAMED from Badge 2026-08-19: `Badge` now means the small number/dot/icon marker.
HOW YOU TELL IT IS NOT CLICKABLE: all caps, weight 600, muted info ink. Chip is
sentence case in action blue. Typography AND colour encode interactivity, so a
static marker never reads like a control even at a glance.
THE BORDER IS ALWAYS DECLARED and only its COLOUR changes — the same mechanism
.post-btn uses. There is no global border-box reset in this file, so a style that
simply dropped its border would be 2 x stroke smaller than its siblings. Every
style here is the same box. */
.post-tag {
display: inline-flex;
align-items: center;
gap: 0;
padding: var(--space-xs);
border: var(--stroke-m) solid transparent;
color: var(--feedback-info-text);
background: none;
font-family: var(--font-post);
font-size: var(--text-2xs);
font-weight: 600;
line-height: 1;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.post-tag .post-label { min-height: var(--icon-size-s); }
.post-tag--outline { border-color: var(--feedback-info-text); }
.post-tag--filled {
background: var(--feedback-info-text);
border-color: var(--feedback-info-text);
color: var(--feedback-info-surface);
}
/* ON MEDIA. An outline tag disappears against a photograph; this is the treatment
that survives one — a light plate with a dark edge, readable over any image. */
.post-tag--filled-outline {
background: var(--feedback-info-surface);
border-color: var(--feedback-info-text);
color: var(--feedback-info-text);
}
/* Deprecated alias. .post-badge is the old name and still ships so live pages do not
break; it will be removed once consumers are migrated. Do not use it in new work. */
.post-badge { }