Form-spacing skill for AI coding agents: an 8 / 20 / 24 / 48px spacing system for UI forms, based on Zander Whitehurst's "Stop guessing your UI form spacing". A developer published a form-spacing skill for AI coding agents that encodes a fixed 4px-grid spacing system for UI forms, drawn from Zander Whitehurst's "Stop guessing your UI form spacing." The skill assigns specific gaps by relationship — 8px for label-to-input and title-to-caption, 20px between fields, 24px between content groups and around dividers, and 48px/40px container padding — and instructs agents to set gaps once on the parent container rather than scattering margins. It covers HTML/CSS, Tailwind, React, Vue, Svelte, SwiftUI, Flutter and Figma specs, with identical spacing in dark mode. | name | form-spacing | |---|---| | description | Use when building, styling, or reviewing any UI form or auth card — sign-up, login, checkout, settings, contact, onboarding, waitlist — in HTML/CSS, Tailwind, React, Vue, Svelte, SwiftUI, Flutter, or a Figma/design spec. Also use when a form looks cramped, unbalanced, or "off", or its spacing is eyeballed or inconsistent, even if the user never says the word "spacing". | Don't eyeball form spacing. Use a fixed set of gaps, where proximity carries meaning : small gaps tie things together a label to its input, a helper to its input , and larger gaps separate one group from the next. When every gap comes from the table below, the form reads correctly at a glance and looks the same in every theme. Source: Zander Whitehurst @zander supafast, Memorisely , "Stop guessing your UI form spacing". | Relationship | Gap | Tailwind | Why | |---|---|---|---| | Container padding, top/bottom | 48px | py-12 | The form needs room to breathe. Never let it touch the card or screen edge. | | Container padding, left/right | 40px | px-10 | Same reason | | Title → caption/subtitle | 8px | gap-2 | They are one heading unit | | Between content groups | 24px | gap-6 | Separates header / fields / actions / alt-actions | | Label → its input | 8px | gap-2 / mb-2 | The label belongs to the input below it | | Field → next field | 20px | gap-5 | Clearly more than 8px, so no label floats toward the previous input. Leaves room for a helper. | | Input → helper or error text | 4px | mt-1 | The helper is part of the input it describes | | Last field → primary button | 24px | gap-6 | Close enough to belong, without being squished against the input | | Divider "Or continue with" | 24px above and below | gap-6 | A divider is a group boundary | | Button → button stacked or side by side | 8px | gap-2 | Related alternatives are one cluster | Every value is on a 4px grid. In rem 16px base : 0.25 / 0.5 / 1.25 / 1.5 / 2.5 / 3. Use the same numbers as pt/dp in SwiftUI and Flutter. Gaps are measured between element boxes, the way CSS gap works, not between visible glyphs. Container padding sits inside any card border. Dark mode uses identical spacing. Only color tokens change between themes. Never put spacing inside theme overrides. Scope: the table governs the space between form elements and the container padding. The insides of components are not covered. That includes input/button inner padding and height, the icon → text gap inside a button, and the line → text gap inside a divider. Keep the project's existing component styles for those. Divide the form into groups first, then apply the table: ┌─ card: 48px top/bottom, 40px left/right ─┐ │ Header title ─8─ caption │ │ 24 │ │ Fields label ─8─ input ─4─ helper │ │ ──20── next field │ │ 24 │ │ Primary submit button │ │ 24 │ │ Divider ── Or continue with ── │ │ 24 │ │ Alt button ─8─ button │ └──────────────────────────────────────────┘ Set each gap once, on the parent container flex / grid + gap , rather than scattering mt- / mb- on individual elements. Stray margins are how forms drift out of the system. Inside a field, the label and helper are the only places where margins make sense.