스택 레이아웃

Stack layout

세로로 쌓인 요소 사이의 간격을 딱 한 곳에서만 관리하는 레이아웃 프리미티브. Heydon Pickering·Andy Bell의 Every Layout에서 제안됐습니다.

다른 이름: Every Layout StackOwl selector spacing
···
html
<div class="frame">
  <div class="stack" id="stack" style="--space:8px">
    <div class="item">A</div>
    <div class="item">B</div>
    <div class="item">C</div>
  </div>
  <div class="tag" id="tag">--space: 8px</div>
</div>
css
.frame{display:flex;flex-direction:column;align-items:center;gap:8px}
.stack{display:flex;flex-direction:column;width:min(38vmin,200px)}
.stack > * + *{margin-block-start:var(--space)}
.item{background:var(--surface);border:1px solid var(--line);border-radius:8px;height:32px;
  display:grid;place-items:center;font-weight:800;color:var(--accent);transition:margin-block-start .6s}
.tag{font:700 clamp(9px,1.7vmin,11px)/1 ui-monospace,monospace;color:var(--accent-2);
  background:var(--surface);border:1px solid var(--line);border-radius:6px;padding:4px 8px}
js
const stack = document.getElementById('stack');
const tag = document.getElementById('tag');
const spaces = [8, 18, 30, 14];
let i = 0;
setInterval(() => {
  const s = spaces[i % spaces.length];
  stack.style.setProperty('--space', s + 'px');
  tag.textContent = '--space: ' + s + 'px';
  i++;
}, 1600);

가장 단순한 형태는 `display: flex; flex-direction: column`에 `gap`을 주는 것입니다. Every Layout이 원래 소개한 방식은 `gap` 대신 "다음 형제 결합자"를 씁니다: `.stack > * + * { margin-block-start: var(--space) }`. `* + *`는 "형제 앞에 형제가 있는 요소"만 골라내므로 첫 번째 자식에는 여백이 붙지 않습니다 — Heydon Pickering이 "owl selector"(올빼미의 눈처럼 생겼다고)라고 부른 트릭입니다.

핵심은 간격 값을 CSS 커스텀 프로퍼티 `--space` 하나로 노출해둔다는 점입니다. 페이지 전체에서 쓰는 스택마다 `--space`만 다르게 주면(작은 카드 안 스택은 8px, 섹션 사이 스택은 32px), 마진·패딩을 요소마다 따로 계산할 필요 없이 "이 영역의 리듬"을 숫자 하나로 조절할 수 있습니다.

이제는 `gap`이 flexbox·grid 양쪽에서 잘 지원되므로 owl selector 트릭 자체는 필수가 아니지만, "간격을 부모가 소유하고 자식은 그 존재를 몰라도 된다"는 원칙은 여전히 유효합니다. 자식 컴포넌트에 마진을 직접 넣으면, 그 컴포넌트를 스택 밖의 다른 곳(그리드 셀 안 등)에서 재사용할 때 예상 못한 여백이 함께 따라옵니다.

언제 쓰나

카드 안 요소들, 폼 필드, 본문 문단처럼 "세로로 한 줄씩 쌓이고 간격은 균일해야 하는" 거의 모든 곳의 기본값입니다.