Patterns / intermediate

Accessible form layouts that survive real labels and errors

A form layout is successful when labels, help text, errors, and controls remain connected under real content.

Form styling should protect relationships. A user needs to know which label belongs to which control, what help text applies, and what went wrong after validation.

Start with markup that works before layout.

<label for="email">Email address</label>
<input id="email" name="email" type="email" aria-describedby="email-help" />
<p id="email-help">Use the address where you want replies.</p>

CSS should enhance that structure, not replace it.

Use a field wrapper

.field {
  display: grid;
  gap: 0.35rem;
  max-inline-size: 34rem;
}

.field label {
  font-weight: 700;
}

.field input,
.field textarea,
.field select {
  min-block-size: 2.75rem;
  border: 2px solid var(--field-border, currentColor);
  border-radius: 6px;
  padding-inline: 0.75rem;
}

The wrapper keeps the label, control, hint, and error in one layout context. This makes responsive behavior predictable.

Do not depend on placeholder text

Placeholders disappear when the user types, can have low contrast, and are not a substitute for labels. If a design wants a compact form, use visible labels that can wrap.

Error layout must be tested

Errors are usually longer than designers expect. They may include a correction, a rule, or a server response. Give errors normal text flow.

.field__error {
  color: #a33a25;
  font-weight: 700;
}

Avoid absolutely positioning error text unless the reserved space is explicit. Floating errors often overlap controls or push content unpredictably.

Form grids need fallback behavior

Two-column form grids can be useful, but the control and its label should stay together.

.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
  gap: 1rem;
}

The field wrapper moves as a unit. That matters more than keeping columns perfectly filled.

References