How should the spacing & gap scales handle modern mid-range and large gutters?
The question: UnysonPlus's spacing and gap scales inherit Bootstrap's $spacers. The gap scale caps at slug 5 (3rem/48px), and both scales jump straight from slug 4 (1.5rem/24px) to slug 5 (3rem/48px). Modern (Tailwind-era) layouts lean on the values in between (32px, 40px) and above (64px, 80px). How should the framework — and the Site Converter that maps real sites onto it — represent those?
Context
The trigger was a fidelity bug in the Site Converter. Converting a hero whose grid used gap-10 lg:gap-16 (40px mobile, 64px desktop) produced a 48px gap — because:
- The gap scale stopped at slug 5 (48px), so 64px had nowhere to land and clamped.
- The converter's
gap_slug()snapped to the nearest of{4, 8, 16, 24, 48}, so 40px → 48px too. - Arbitrary
gap-[Npx]tokens harvested from the source were being folded into the spacing scale, but the Section's Gap option reads the gap scale — so they never reached it.
The deeper issue is the 24px → 48px cliff shared by both scales: 2rem (32px) and 2.5rem (40px) — the two steps modern designs use most for card/section padding and grid gaps — simply don't exist as named slugs.
The hard constraint: you cannot renumber existing slugs. Every built page, every converted site, and every theme preset references .pt-11, .g-5, section--gap-5, etc. Changing what a slug means silently breaks live content. So any fix has to be purely additive.
Options considered
- Redefine the mid-range slugs (make slug 5 = 2rem, shift the rest). Cleanest scale, but renumbers every existing reference — breaks all built/converted content. Rejected outright.
- Raise the gap cap only (add 4rem, 5rem to the gap scale). Fixes 64/80px gutters but leaves the 32/40px cliff and the snap-to-48px behaviour. Half a fix.
- Extend the gap scale to mirror the spacing scale, seed the mid-range as bracketed arbitrary steps in both scales, and give the converter a dedicated gap scale with exact
[NNpx]output. Chosen.
Decision
A four-part, entirely additive change:
- The gap scale now mirrors the spacing scale — slugs 0–12 (0…8rem).
g-{slug}≡p-{slug}mentally, and 56/64/72/80/96/112/128px gutters become expressible. Slugs 0–5 are byte-for-byte unchanged. - The
[32px]/[40px]mid-range is seeded into both scale defaults. Bracket-named so they read as exact lengths in the dropdown. The gap generator and the Section view both sanitise[32px]→.section--gap-32px; on the spacing side the bracketed entries render on demand through the per-page arbitrary-spacing handler (.pt-[32px]). - The Site Converter gained a dedicated
build_gap_scale()(parallel tobuild_spacing_scale()) that emits the extended base and appends any genuinely off-scale gutter the source uses — agap-[20px]class or a computedgap:Npx— as an exact[NNpx]entry. Gap tokens no longer pollute the spacing scale. gap_slug()now resolves exactly — an on-scale value returns the clean slug (gap-16= 64px →7), an off-scale value returns a lossless[NNpx]slug thatbuild_gap_scale()has registered a preset for. No more snapping.
The PHP (Stitch/Mapper) and JS (to-theme-settings/to-pages) paths carry identical scales and logic.
Why
The scale's job is to encode a design system's intent as a small, named, editable vocabulary. Bootstrap's $spacers encodes a 2013-era intent; modern component design leans on a denser mid-range and larger section gutters. Because slug numbers are a public contract, the only safe way to modernise is additively: keep 0–12 stable, mirror them across both scales so the mental model is one scale not two, and express everything off-scale as lossless bracketed arbitraries — the same mechanism the converter already used for spacing. The converter change is what makes it faithful: a real site's exact gutter is reproduced, not rounded to the nearest legacy step.
Deliberately not done: redefining the gap cap philosophy as "gaps over 3rem are section spacing." In practice hero and feature grids routinely use 64–80px column gaps; treating those as un-representable was the bug, not a feature.
Status: Accepted.
