# Page-builders and spacing: kill the custom padding field

Letting editors type arbitrary padding numbers destroys visual consistency. Here is how to give clients real control without wrecking the design system.

![Neon yellow and orange light streaks crossing a dark dotted grid](https://cdn.sanity.io/images/s6kuy1ts/production/57efbed4ccc2c41cd2bb2821848afe69b12edd57-4096x2596.png?w=1600&fm=webp&q=80&auto=format)

Every page-builder eventually gets the same feature request: let editors control spacing. And almost every team that grants it the naive way — a free-form number input for padding — regrets it within a quarter. Once someone can set 32px on the bottom, 72px on the top and 12px on the right, the concept of consistency leaves the building.

## Why an 8px grid matters

Common 16:9 screens divide cleanly by eight, so spacing built on an 8px base lands on whole pixels and avoids blurry half-pixel lines. It also makes rhythm predictable: doubling a gap is just doubling a number. That predictability is what makes a site feel considered rather than assembled.

## Never ship a raw number input

A number field for padding is a loaded gun. It offers infinite wrong answers and no right one, and it guarantees that two editors will space the same block differently. Do not add it.

## If you must offer choice, constrain it

The trick is to expose named options, not numbers, and map those names to a fixed scale in code. In the schema, use a small radio list:

```ts
defineField({
  name: "padding",
  type: "string",
  options: {
    layout: "radio",
    list: [
      { title: "None", value: "none" },
      { title: "Small", value: "small" },
      { title: "Medium", value: "medium" },
      { title: "Large", value: "large" },
    ],
  },
  initialValue: "medium",
})
```

Then resolve the option to a Tailwind class in the component, so the design system — not the editor — owns the actual values:

```tsx
const paddingMap = {
  none: "py-0",
  small: "py-4",
  medium: "py-8",
  large: "py-12",
} as const;

function Section({ padding = "medium", children }) {
  return <section className={paddingMap[padding]}>{children}</section>;
}
```

## Better still: bake a page-builder rhythm token

For most projects you do not even want per-block choice. Define one spacing token and apply it everywhere. In Tailwind v4 that lives in your CSS theme:

```css
@theme {
  --spacing-pagebuilder: 8rem;
}
```

Now every block uses `py-pagebuilder`, and vertical rhythm is identical across the whole site by construction. For blocks with a background colour or image, nest a container and double the spacing so the coloured area breathes without the content drifting.

## "The blocks feel too close together"

When a client says two blocks are cramped, resist the urge to add a padding field. Adjust the token, or introduce one more named step. The problem is almost never that you need infinite control; it is that you need one more well-chosen value.

## Already shipped the number field?

You can walk it back. Write a short migration checklist in Markdown, map each existing numeric value to the nearest named step, and apply it block by block. Working through a written list also keeps an AI assistant on-task across a large refactor instead of losing the thread halfway through.

Consistency is a feature. Protect it by never letting spacing become a free-text field.
