POST Design System Using the system
Components

Input

A single line of text entry.

Use it on its own only inside something that already labels it. Most of the time you want Field, which adds the label and the help text.

Watch out. Never ship an input with no label — a placeholder is not a label; it disappears as soon as someone types.
Sub-brand
Theme
Liverendered from the shipped recipe

States

default
hover
focus
filled
error
disabled

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.
   ============================================================================ */

/* ---- input ---- */
/* Neutral until interaction: the resting border is the `border` token (ink), not the
   action colour — POST borders are a strong tone, not a grey hairline. Error
   MESSAGING lives in Field, not here. */
.post-input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  padding: var(--space-m);
  border: var(--stroke-m) solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-family: var(--font-post);
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: var(--icon-size-s);
}
.post-input::placeholder { color: var(--muted); }
/* HOVER IS A FILL AND AN EDGE, on every control that has an edge. Checkbox and Radio always
   did both; Input and Select changed only their border, and Search Bar and Level Filter did
   nothing at all - so the same gesture produced four different amounts of feedback depending
   on which control you were over. Same pair everywhere now: hover-surface behind,
   hover-ink on the edge.

   Deliberately BEFORE the focus/error/disabled rules: those are more important states and
   must win on a control that is also being hovered. An errored input stays red under the
   pointer, it does not turn blue. */
.post-input:hover,
.post-input.is-hover {
  border-color: var(--hover-ink);
  background: var(--hover-surface);
}
.post-input:focus,
.post-input.is-focus {
  border-color: var(--action);
  outline: 2px solid var(--action);
  outline-offset: 2px;
}
.post-input.is-error { border-color: var(--feedback-error-text); background: var(--feedback-error-surface); }
.post-input:disabled,
.post-input.is-disabled { opacity: 0.4; cursor: not-allowed; }
textarea.post-input { line-height: 1.2; resize: vertical; min-height: 6em; }