Form Layout

Three layout patterns for arranging form fields: stacked (labels above inputs), two-column (grid), and inline (labels beside inputs). Choose based on the density of the form and the available width.

Stacked

Labels sit directly above their inputs. The default layout for most forms — scannable, accessible, and works at any viewport width. Use for sign-up flows, settings pages, and anywhere the form is narrow or on mobile.

Two-column

A CSS grid with two equal columns. Related fields (first name / last name, city / country) sit on the same row. Wider fields span both columns with col-span-2. Only use at widths ≥ 560 px — switch to stacked on mobile.

Inline

Labels align right in a fixed-width column; inputs fill the remaining space. Most compact option — good for settings panels and admin interfaces where vertical space is at a premium. Avoid on narrow screens or forms longer than ~6 fields.

Pattern comparison

LayoutMin widthBest forAvoid when
StackedAnySign-up, checkout, mobile-first flowsVery wide viewports with only 2–3 short fields
Two-column560 pxProfile edit, address forms, related field pairsMobile, or when field labels have very different lengths
Inline480 pxSettings panels, admin forms, compact data entryLong labels (>20 chars), mobile, more than 8 fields

Responsive guidance

  • Always fall back to stacked below 560 px. Two-column and inline layouts compress too much on narrow screens. Use a media query or Tailwind's sm: prefix to switch to a single-column stacked layout on mobile.
  • Label width in inline layouts. A fixed label column (112–128 px) works for most English labels. For localized UIs, switch to a percentage width or fluid label that can wrap — German and Finnish labels routinely double in length.
  • Grid gaps over margins. Use gap-x/gap-y on the grid container rather than adding margin to individual fields. It's easier to adjust spacing globally and avoids margin-collapse edge cases.
  • Full-width submit buttons on mobile. A full-width button (w-full) on narrow screens gives a larger touch target and feels more native. On desktop, right-aligned buttons at natural width are more appropriate.

Implementation

FormLayouts.tsx
tsx
// Stacked — labels above inputs, full width
function StackedForm() {
  return (
    <form className="space-y-4 max-w-md">
      <div className="space-y-1">
        <label className="block text-[12px] font-medium text-text-secondary">First name</label>
        <Input placeholder="Jordan" />
      </div>
      <div className="space-y-1">
        <label className="block text-[12px] font-medium text-text-secondary">Email</label>
        <Input type="email" placeholder="jordan@example.com" />
      </div>
      <Button type="submit" className="w-full">Save</Button>
    </form>
  );
}

// Two-column — grid with full-width exceptions
function TwoColumnForm() {
  return (
    <form className="grid grid-cols-2 gap-4 max-w-2xl">
      <div className="space-y-1">
        <label className="block text-[12px] font-medium text-text-secondary">First name</label>
        <Input placeholder="Jordan" />
      </div>
      <div className="space-y-1">
        <label className="block text-[12px] font-medium text-text-secondary">Last name</label>
        <Input placeholder="Smith" />
      </div>
      <div className="col-span-2 space-y-1">
        <label className="block text-[12px] font-medium text-text-secondary">Email</label>
        <Input type="email" placeholder="jordan@example.com" />
      </div>
      <div className="col-span-2 flex justify-end">
        <Button type="submit">Save</Button>
      </div>
    </form>
  );
}

// Inline — label on left, input on right
function InlineForm() {
  return (
    <form className="space-y-3 max-w-lg">
      {[
        { label: "Display name", placeholder: "Jordan Smith" },
        { label: "Email",        placeholder: "jordan@example.com" },
      ].map(({ label, placeholder }) => (
        <div key={label} className="flex items-center gap-4">
          <label className="w-28 flex-shrink-0 text-[12px] font-medium text-text-secondary text-right">
            {label}
          </label>
          <div className="flex-1">
            <Input placeholder={placeholder} />
          </div>
        </div>
      ))}
      <div className="flex items-center gap-4">
        <div className="w-28 flex-shrink-0" />
        <Button type="submit">Update</Button>
      </div>
    </form>
  );
}