모든 UI를 위한 유연한 밀도

작성자

카테고리:

← 피드로
DEV Community · Dimon · 2026-06-25 개발(SW)
Cover image for Flexible density for all the UI

Dimon

In our UI system we dont use pixel values to define spacing, we use semantic variables, (t-shirt sizes: gap-sm, p-lg, my-2xl) – so thats like you change one CSS variable, and the whole UI adapts.

We limit user customization to just 5 presets – 0.8 / 0.9 / 1 / 1.1 / 1.2:

  • Symmetric around 1.0 – two steps down, two up;

  • ±20% is deliberately restrained – enough to change the feel (data-dense ↔ spacious) without breaking layouts or distorting proportions;

  • 5 discrete stops = perceptible but decision-light. Most users never open the control, so it’s a coarse preference, not a fine instrument

The biggest reason why do we have it – to support bigger themes differentiation. Brand-overlay density has slightly bigger range: 0.7–1.35; so we can differentiate brands providing customizable UI: >1 reads editorial/airy, <1 terminal/dense. Same components, different feel — no fork. The user can always override.

/* Density cascade: user override → brand default → system default (1) */
--density: var(--user-density, var(--brand-density, 1));

Enter fullscreen mode Exit fullscreen mode

Everything is built on a 4px base unit and a single --density variable, all declared in :root:

--unit: 0.25rem; 
/* Every spacing token is base-step × unit × density */
--space-xs:  calc(2  * var(--unit) * var(--density, 1));  /*  8px @ d=1 */
--space-sm:  calc(4  * var(--unit) * var(--density, 1));  /* 16px */
--space-md:  calc(6  * var(--unit) * var(--density, 1));  /* 24px */
--space-lg:  calc(8  * var(--unit) * var(--density, 1));  /* 32px */
--space-xl:  calc(12 * var(--unit) * var(--density, 1));  /* 48px */
--space-2xl: calc(16 * var(--unit) * var(--density, 1));  /* 64px */
--space-3xl: calc(24 * var(--unit) * var(--density, 1));  /* 96px */
--space-4xl: calc(32 * var(--unit) * var(--density, 1));  /* 128px */
--space-5xl: calc(40 * var(--unit) * var(--density, 1));  /* 160px */

Enter fullscreen mode Exit fullscreen mode

But those are details, the main point was to have flexible spacing underneath so all the UI is adaptable to any vibe.

Also if we talk about accessibility – minimum control height is preserved, different for touch screens:

--size-touch-min: 2.75rem; /* 44px — coarse/touch base (scales with density) */
--control-height-min: 2.25rem; /* 36px — coarse floor (hard min) */

@media (pointer: fine) {
  --size-touch-min: 2.25rem; /* 36px — fine/mouse base */
  --control-height-min: 2rem; /* 32px — fine floor */
}

--control-height: max(var(--control-height-min), calc(var(--size-touch-min) * var(--density)));

Enter fullscreen mode Exit fullscreen mode

  • 44px touch base = the WCAG 2.5.5 / Apple HIG / Material touch-target minimum — a finger needs ~44 CSS px.

  • 36px desktop base = the 2026 SaaS convention (Linear / Vercel / shadcn / Notion); a mouse is precise, so desktop controls can be denser than touch.

  • The floors exist because controls, unlike spacing, cant shrink freely. Spacing tokens have no floor (Compact just scales them down). Controls are clamped by max() so Compact density (0.8×) can’t shrink a button into an un-hittable size: 44×0.8 = 35.2 → 36 on touch, 36×0.8 = 28.8 → 32 on desktop. That asymmetry (whitespace compresses without limit, hit-targets don’t) is the whole point of the floor.

  • The floor is a separate variable from the base so Compact can legally drop below the standard (desktop 36 → 32) while still being bounded. If the floor equalled the base, density could never push a control under its standard size at all.

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다