POST Design System Using the system
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-brand
Theme

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; }