Components
Search Bar
Free-text search over a set of content.
The magnifier submits and the page decides what searching means, so the same bar works over venues, events or tenants.
Watch out. Don't use it as a filter for a short list; chips are quicker and show what is available.
Sub-brandTheme
Liverendered from the shipped recipe
States
inactive
active
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.
============================================================================ */
/* ---- search-bar ---- */
/* Section-scale, so stroke/l (rule 2) — this is a big surface, not a component-scale
control. One component across all three breakpoints; the text fills. */
.post-search {
display: flex;
align-items: center;
gap: var(--space-m);
padding: var(--space-m);
border: var(--stroke-l) solid currentColor;
color: var(--action);
background: var(--surface);
/* "Width is instance-resizable; the text fills" — so the bar takes its container and
the input absorbs the slack. Stated rather than left to `display:flex` defaulting to
auto, because the common case is a flex/grid parent where an auto-width child
shrink-wraps instead. box-sizing is declared for the same reason the icon button
needed it: with width + padding + border we cannot inherit the consumer's reset. */
width: 100%;
box-sizing: border-box;
}
.post-search .post-icon { width: var(--icon-size-m); height: var(--icon-size-m); }
.post-search input {
flex: 1;
min-width: 0;
border: none;
background: none;
outline: none;
color: var(--text);
font-family: var(--font-post);
font-size: var(--text-md);
font-weight: 500;
line-height: var(--icon-size-m);
}
.post-search input::placeholder { color: var(--muted); }
.post-search:hover,
.post-search.is-hover {
border-color: var(--hover-ink);
background: var(--hover-surface);
}
/* After the hover rule on purpose: an ACTIVE search bar outranks a hovered one, so it keeps
its action colour while the pointer is over it instead of flickering between the two. */
.post-search.is-active { color: var(--action); }
/* type="search" ships a NATIVE clear affordance — a grey UA-styled x that ignores every
token in this file and differs per browser. Suppress it and use the real ghost icon
button instead, so clear is the same close glyph, colour and hover halo as everywhere
else. Kept on type="search" rather than switching to type="text" because the input
type is what gives us Escape-to-clear and the right mobile keyboard. */
.post-search input::-webkit-search-cancel-button,
.post-search input::-webkit-search-decoration { -webkit-appearance: none; appearance: none; }
/* Both affordances are REAL ghost icon buttons, not decoration: search submits, clear
empties. The ghost's negative margin is what keeps them honest here — it cancels the
bar's own padding, so each glyph lands exactly where a plain 24px icon sat, while the
hover halo expands outward into the padding instead of pushing the layout around. */
/* Clear toggles VISIBILITY, never display. Under display:none the button leaves the flex
flow, the input reclaims its width, and the text reflows the moment you type the first
character — the whole bar twitches. visibility keeps the slot reserved, so nothing
moves; it also drops the button out of the tab order and off the hit-test while empty,
which is the reason not to just fade it with opacity. */
.post-search__clear { visibility: hidden; }
.post-search.is-filled .post-search__clear { visibility: visible; }