Components
Note Link
A link that runs inside a sentence rather than sitting on its own.
Use it where the link is part of the prose and the surrounding words carry the meaning.
Watch out. If the action is the point of the block rather than part of a sentence, use a Button.
Sub-brandTheme
Liverendered from the shipped recipe
States
default
hover
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.
============================================================================ */
/* ---- note-link ---- */
/* A clickable NOTE, not a CTA — info icon + underlined text + arrow. Sits under
content blocks (policy notes). Underline is the distinguisher, which survives the
brand/deep themes where action == text. */
/* INLINE, not flex. `inline-flex` + `align-items:center` treats the icons as siblings of
the whole text block, so the moment a note wrapped to two lines both glyphs floated to
the vertical middle of the paragraph — the leading icon detached from the first line and
the arrow detached from the last word. Icons inside a sentence are part of the sentence:
inline flow puts the leading glyph on the FIRST line's baseline and lets the arrow
follow the final word wherever it lands, including across a wrap. */
/* Two parts, not one run of text: a SEPARATE leading icon, then the copy with its arrow
riding the last word. Figma models it exactly this way — [info-icon][text][arrow] with
the arrow inside the text run — and it is what makes wrapping behave: the icon hangs in
its own column so wrapped lines indent past it instead of running underneath. */
.post-notelink {
display: inline-flex;
align-items: flex-start;
gap: var(--space-xs); /* Figma itemSpacing = 4 */
color: var(--action);
cursor: pointer;
background: none;
border: none;
font-family: var(--font-post);
font-size: var(--text-xs);
font-weight: 500;
line-height: 1.2;
/* Figma grows the box on hover (0 padding -> 8/4/8/4, h 16 -> 32). Reserving that
padding at rest and cancelling it with a matching negative margin gets the same halo
WITHOUT the reflow — the ghost icon button's mechanism. The vertical value is space/s
against space/xs horizontal: a halo needs more room above and below a line of text
than beside it, or it reads as a highlighter stripe rather than a surface. */
padding: var(--space-s) var(--space-xs);
margin: calc(-1 * var(--space-s)) calc(-1 * var(--space-xs));
text-decoration: none; /* the underline belongs to the copy, not the icon */
}
/* A one-line-tall box, so the circle centres on the FIRST line and stays there when the
copy wraps. The circle is larger than a line box and simply overflows it symmetrically,
which is what keeps it optically centred instead of pinned to the top. */
.post-notelink__icon {
flex: 0 0 auto;
display: inline-flex;
align-items: center;
justify-content: center;
height: calc(var(--text-xs) * 1.2);
}
.post-notelink__icon .post-icon {
width: var(--icon-size-s);
height: var(--icon-size-s);
}
/* BOTH glyphs take the link's ink, never their own. Figma binds all three fills — text and
the two icon vectors — to `action` at rest and `hover-ink` on hover, which in code means
one `color` on the anchor and `fill:currentColor` underneath. Anything that pins a colour
on an icon (the icon-circle sets `color:var(--action)`, for instance) breaks the hover:
the copy moves to hover-ink and the glyph is left behind on the rest colour. */
.post-notelink .post-icon { color: inherit; }
.post-notelink__body {
flex: 1 1 auto;
min-width: 0;
text-decoration: underline;
}
/* The arrow rides the copy: inline, on the baseline, bound to the last word by an
so it can never orphan onto a line of its own. */
.post-notelink__body .post-icon {
display: inline-block;
width: var(--icon-size-s);
height: var(--icon-size-s);
vertical-align: -0.18em;
}
.post-notelink:hover,
.post-notelink.is-hover {
color: var(--hover-ink);
background: var(--hover-surface);
}