Chapter 03

박스와 간격

웹 화면을 아무리 들여다봐도 동그라미나 비스듬한 선은 드물다. 둥근 버튼도, 동그랗게 자른 얼굴 사진도 브라우저에게는 네모난 상자 하나다. 화면은 상자 안에 상자를, 그 안에 또 상자를 넣어 만든다. 그래서 웹 디자이너가 그리는 "간격"은 결국 상자와 상자 사이, 상자의 벽과 내용 사이의 거리다. 이 장은 그 상자를 네 겹으로 벗겨 보고, 상자의 폭이 어디까지인지, 위아래 간격이 왜 가끔 사라지는지 확인한다. 그리고 13, 17, 22 픽셀처럼 제각각이던 간격을 몇 개의 단계로 묶어, 시안을 그린 사람과 코드를 쓰는 사람이 같은 숫자로 말하게 한다.

모든 것은 상자다

브라우저는 HTML 문서를 받으면 글 한 덩어리, 그림 하나, 버튼 하나마다 직사각형 영역을 하나씩 만든다. 이 영역을 상자(Box)라고 부른다. 상자의 크기와 위치를 정하는 일을 레이아웃(Layout)이라고 한다. CompositionBook에서 지면 위의 요소를 "놓는" 일을 레이아웃이라고 했는데(지면과 화면 구성), 웹에서는 그 일을 브라우저가 규칙에 따라 계산한다. 디자이너가 할 일은 그 규칙에 맞는 숫자를 주는 것이다.

상자는 혼자 있지 않다. 페이지 전체가 하나의 큰 상자이고, 그 안에 머리 띠와 본문과 바닥 띠가, 머리 띠 안에 로고와 메뉴 단추가 들어 있다. 상자 안에 상자가 든 이런 구조를 중첩(Nesting)이라고 한다. 아래는 책방 E의 모임 안내 페이지를 휴대폰 폭(360 CSS 픽셀, 2장)으로 그린 가상의 화면이다. "상자 보기"를 켜면 브라우저가 보는 대로 모든 상자의 테두리가 드러난다. 화면을 누르거나 끌면 손끝 아래의 가장 안쪽 상자가 골라진다.

SIMULATOR

화면을 상자로 보기

눌러서 상자를 고른다
고른 상자—
테두리까지의 크기—
안쪽 여백 · 바깥 여백(위 오른 아래 왼)—
해볼 것: ① "화면"과 "상자 보기"를 번갈아 누른다. 둥근 단추도 네모난 상자이고, 글 세 줄도 상자 하나다. ② "상자만"으로 바꾼다. 색과 글자를 지우면 이 페이지는 세 겹으로 포개진 직사각형들일 뿐이다. 회색(페이지), 파랑(머리 띠·본문·바닥 띠), 분홍(그 안의 요소). ③ 제목, 그림, 신청 단추를 차례로 눌러 오른쪽 아래 값을 본다. 본문 상자(main)의 안쪽 여백이 위아래 24, 양옆 16이라서 그 안의 요소들이 모두 왼쪽 16에서 시작한다. (가상의 화면이다. 실제 브라우저는 글줄 하나하나에도 상자를 만들지만 여기서는 요소 단위로만 그렸다.)

"상자만" 화면에서 본 것이 브라우저의 눈이다. 색과 글자를 지워도 페이지의 짜임은 그대로 남는다. 상자는 크게 두 가지로 놓인다. 제목, 문단, 그림처럼 위에서 아래로 한 줄씩 쌓이는 블록 상자(Block box)와, 문장 속 굵은 낱말이나 링크처럼 글줄 안에서 옆으로 흐르는 인라인 상자(Inline box)다. 블록 상자는 따로 정하지 않으면 부모의 폭을 가득 채우고, 높이는 안에 든 내용만큼 자란다. 위 화면의 요소들이 모두 폭 328로 같은 것도 그 때문이다. 폭은 바깥에서 정해지고, 높이는 안에서 정해진다. 이 비대칭이 웹 레이아웃의 첫 번째 성질이다. 종이 지면에서는 디자이너가 높이까지 정하지만, 웹에서는 글이 늘면 상자가 아래로 늘어난다.

실제 사이트의 상자를 보는 법 데스크톱 브라우저(크롬, 엣지, 파이어폭스, 사파리)에는 개발자 도구가 들어 있다. 페이지의 아무 요소에서 오른쪽 단추를 눌러 "검사"를 고르면 그 요소의 상자가 색칠되어 보이고, 옆 창에 내용·안쪽 여백·테두리·바깥 여백의 값이 동심 사각형 그림으로 나온다(사파리는 설정에서 개발자 메뉴를 먼저 켠다). 디자이너에게도 이 그림은 쓸모가 많다. 마음에 드는 사이트의 간격이 왜 편안한지 감으로 짐작하지 말고, 상자를 하나씩 눌러 숫자로 확인해 본다. 위 시뮬레이터의 주황·노랑·초록·파랑도 이 그림의 관례를 따랐다.

박스 모델: 네 겹의 상자

상자 하나를 확대하면 네 겹이 있다. 가운데에 글이나 그림이 들어가는 내용 영역(Content)이 있고, 그것을 안쪽 여백(Padding, 패딩)이 감싼다. 안쪽 여백은 상자의 바탕색이 칠해지는 숨 쉴 자리다. 그 바깥에 테두리(Border, 보더)가 있고, 맨 바깥에 이웃한 상자를 밀어내는 투명한 바깥 여백(Margin, 마진)이 있다. 이 네 겹으로 상자의 크기와 간격을 정하는 규칙을 박스 모델(Box model)이라고 한다. 출처는 W3C의 CSS 명세다(CSS 2.1의 8장, 지금은 CSS Box Model Module Level 3로 이어진다).

내용 content · width × height 안쪽 여백 padding 테두리 border 바깥 여백 margin (투명, 이웃을 밀어낸다) 바탕색이칠해지는 곳 테두리 안쪽 차지하는 자리:바깥 여백까지
그림 1. 박스 모델의 네 겹. 가운데 내용 영역을 안쪽 여백이 감싸고, 그 바깥에 테두리, 맨 바깥에 바깥 여백이 있다. 상자의 바탕색과 바탕 그림은 테두리 안쪽(내용과 안쪽 여백)에 칠해지고, 바깥 여백은 언제나 투명하다. 눈에 보이는 상자는 테두리까지이지만, 이웃 상자를 밀어내는 범위는 바깥 여백까지다.

CSS로는 네 겹을 각각 따로 적는다. 아래는 모임 안내 카드 하나를 적은 예다. 값을 하나만 쓰면 위·오른쪽·아래·왼쪽 네 변에 같은 값이 들어가고, 네 개를 쓰면 시계 방향(위, 오른쪽, 아래, 왼쪽)으로 들어간다.

.event-card {
  width: 240px;          /* 내용 영역의 폭(기본 방식에서) */
  padding: 16px;         /* 안쪽 여백: 네 변 모두 16 */
  border: 2px solid #22342a;
  margin: 0 0 24px;      /* 바깥 여백: 위 0, 양옆 0, 아래 24 */
}

그렇다면 이 카드는 화면에서 폭을 얼마나 차지할까. 아래 시뮬레이터에서 상자 아래쪽 가장자리의 손잡이 셋을 위아래로 끌면 안쪽 여백(초록), 테두리(노랑), 바깥 여백(주황)의 두께가 바뀐다. 두께는 네 변에 똑같이 들어간다. 슬라이더는 CSS의 width 값이다. 위쪽의 세 치수선이 내용 폭, 눈에 보이는 폭, 차지하는 폭을 보여 준다.

SIMULATOR

네 겹을 끌어 보는 박스 모델

아래 가장자리 손잡이를 끈다
내용 폭—
눈에 보이는 폭(테두리까지)—
차지하는 폭(바깥 여백까지)—
해볼 것: ① 처음 상태에서 숫자를 더해 본다. width 240에 안쪽 여백 16을 양쪽으로, 테두리 2를 양쪽으로 더하면 눈에 보이는 폭은 276이다. 바깥 여백 24를 양쪽에 더하면 차지하는 폭은 324다. ② 초록 손잡이를 끌어 안쪽 여백을 48까지 늘린다. width는 그대로인데 상자가 바깥으로 부푼다. ③ "border-box"로 바꾸고 같은 일을 한다. 이번에는 눈에 보이는 폭이 width에 고정되고, 안쪽 여백이 늘어난 만큼 내용 폭이 줄어든다. ④ border-box에서 width를 80으로 줄이고 안쪽 여백을 늘린다. 내용 폭이 0이 되면 더는 줄지 않고 상자가 넘친다. (손잡이는 안쪽 여백과 바깥 여백을 4 단위로, 테두리를 1 단위로 바꾼다. 높이는 글 두 줄로 고정했다.)

기본 방식에서 width는 상자의 겉 폭이 아니라 내용 영역의 폭이다. 안쪽 여백과 테두리는 그 바깥에 더해진다. 식으로 쓰면 다음과 같다.

$$W_{\text{보이는}} = w + 2p + 2b, \qquad W_{\text{차지하는}} = W_{\text{보이는}} + 2m$$
\(w\): CSS width(기본 방식에서 내용 폭), \(p\): 안쪽 여백, \(b\): 테두리 두께, \(m\): 바깥 여백. 네 변의 값이 같다고 둔 식이다. 왼쪽과 오른쪽 값이 다르면 \(2p\) 대신 \(p_{\text{왼}} + p_{\text{오}}\)처럼 더한다.

바깥 여백은 바탕색이 칠해지지 않으니 눈에 보이지 않는다. 그래서 시안을 보고 "카드 사이가 24"라고 읽은 것이 코드에서는 위 카드의 바깥 여백 24일 수도, 두 카드의 바깥 여백 12씩일 수도, 부모 상자의 간격 속성 24일 수도 있다. 눈에 같은 간격이 코드로는 여러 방법으로 만들어진다는 것, 그리고 방법마다 다르게 행동한다는 것이 이 장 뒷부분의 주제다.

자리를 차지하지 않는 선들 상자 둘레에 그려지지만 박스 모델에 들어가지 않는 것도 있다. outline(키보드로 고른 요소의 초점 표시에 자주 쓰는 바깥선)과 box-shadow(그림자)는 테두리 바깥에 그려질 뿐 자리를 차지하지 않는다. 그래서 그림자를 크게 줘도 옆 상자가 밀려나지 않고, 대신 옆 상자 위로 겹쳐 그려질 수 있다. 초점 표시를 테두리 두께로 만들면 초점이 들어올 때마다 상자가 몇 픽셀씩 커져 주변이 흔들리는데, outline은 그런 일이 없다. 7장(컴포넌트와 상태)에서 초점 표시를 다시 다룬다. 또 하나, 안쪽 여백과 바깥 여백을 퍼센트로 쓰면 위아래 값도 부모의 폭을 기준으로 계산된다. 높이가 아니다. 이 성질을 이용해 폭에 비례하는 높이를 가진 상자를 만들던 시절이 있었고, 지금은 aspect-ratio 속성이 그 일을 대신한다.

폭은 어디까지인가: box-sizing

앞 시뮬레이터에서 본 두 방식은 CSS의 box-sizing 속성으로 고른다. 기본값 content-box는 width를 내용 폭으로, border-box는 width를 테두리까지의 폭으로 읽는다. 앞의 것은 CSS 초기 명세의 정의이고, 뒤의 것은 디자이너가 "폭"이라고 말할 때 떠올리는 것에 가깝다. 시안에서 "카드 폭 240"이라고 적으면 대개 눈에 보이는 카드의 바깥 테두리까지를 뜻하기 때문이다. 이 차이가 어떤 사고를 내는지 보자. 폭 344의 상자 안에 카드 두 장을 width: 50%로 나란히 놓았다. 카드에 안쪽 여백과 테두리를 준다.

SIMULATOR

50% 두 장이 한 줄에 들어갈까

카드 한 장의 보이는 폭—
두 장의 합 / 부모 폭—
결과—
해볼 것: ① content-box에서 처음 상태를 본다. 카드마다 172에 안쪽 여백 32와 테두리 2가 더해져 206이 되고, 두 장의 합 412가 부모 폭 344를 넘어 둘째 카드가 다음 줄로 밀린다. ② 안쪽 여백과 테두리를 모두 0으로 내린다. 그제야 두 장이 나란히 선다. 하지만 카드 안의 글이 테두리에 붙는다. ③ 안쪽 여백을 16으로 되돌리고 border-box로 바꾼다. 카드의 겉 폭이 172로 고정되고 안쪽 여백만큼 내용이 좁아져, 두 장이 한 줄에 선다. (두 카드를 float로 나란히 놓았을 때의 행동이다. 플렉스박스로 놓으면 밀리는 대신 줄어드는데, 4장에서 다룬다.)

퍼센트 폭과 고정 여백을 섞는 순간 content-box는 계산을 어렵게 만든다. 50%와 32픽셀을 더한 값은 부모 폭이 바뀔 때마다 달라지기 때문이다. 그래서 오늘날 많은 사이트가 스타일시트 맨 앞에 모든 요소를 border-box로 바꾸는 규칙을 둔다. 이 규칙은 널리 퍼진 관례이고, 시안의 숫자와 코드의 숫자를 같은 뜻으로 만든다.

/* 모든 상자의 width·height를 테두리까지의 크기로 읽는다 */
*, *::before, *::after {
  box-sizing: border-box;
}
시안의 "폭"이 무엇을 뜻하는지 먼저 맞춘다 디자인 도구에서 상자를 그리면 그 도구의 폭은 보통 눈에 보이는 상자 전체다. 그런데 테두리 선을 상자 안쪽에 그리는지, 바깥에 그리는지, 가운데에 걸쳐 그리는지는 도구와 설정에 따라 다르다. 바깥에 그린 2픽셀 선은 border-box로 옮기면 상자를 4픽셀 키운다. 시안을 넘기기 전에 "폭은 테두리까지를 잰 값이고, 코드는 border-box로 쓴다"처럼 한 줄로 합의해 두면, 몇 픽셀씩 어긋나는 일을 대부분 막는다. 위 시뮬레이터의 둘째 카드가 밀려난 것도 원인은 단 34픽셀이었다.

사라지는 간격: 마진 상쇄

지후가 서윤의 시안을 코드로 옮기다 처음 부딪히는 것이 이것이다. 제목 아래에 바깥 여백 24를, 그 아래 문단 위에 바깥 여백 16을 주었다. 둘 사이 간격은 40이어야 할 것 같은데, 화면에서 재면 24다. 16은 어디로 갔을까.

일반적인 문서 흐름에서 위아래로 맞닿은 두 블록 상자의 바깥 여백은 더해지지 않고 큰 쪽 하나로 겹친다. 이것을 마진 상쇄(Margin collapsing)라고 한다. CSS 2.1 명세(8.3.1절)의 규칙이고, 원래는 글 문서를 위한 배려였다. 문단마다 위아래에 1줄씩 여백을 주더라도, 문단과 문단 사이가 2줄로 벌어지지 않고 1줄로 유지된다. 책의 단락 간격처럼 동작하게 만든 것이다. 아래에서 두 바깥 여백을 바꾸고, 두 상자가 놓인 방식을 바꿔 본다.

SIMULATOR

더해질까, 겹칠까

실제 간격—
계산—
해볼 것: ① 처음 상태에서 주황(A 24)과 보라(B 16)의 띠가 겹쳐 있는 것을 본다. 간격은 24다. ② B를 32로 올린다. 이제 B가 더 크니 간격은 32다. A는 B 안에 숨는다. ③ "플렉스·그리드 안"으로 바꾼다. 같은 값인데 간격이 A + B로 더해진다. 플렉스 상자와 그리드 상자의 자식들은 바깥 여백이 겹치지 않는다. ④ 블록 흐름으로 돌아와 B를 −8로 내린다. 음수 바깥 여백은 겹치지 않고 큰 양수에서 빠진다(24 − 8 = 16). (명세의 규칙: 둘 다 양수면 큰 값, 둘 다 음수면 더 작은 값, 부호가 다르면 합. 가로 방향의 바깥 여백은 겹치지 않는다.)

마진 상쇄는 형제 사이에서만 일어나지 않는다. 부모 상자에 안쪽 여백도 테두리도 없으면, 첫 자식의 위 바깥 여백이 부모 밖으로 빠져나가 부모의 바깥 여백과 겹친다. 카드 안 첫 제목에 위 여백 24를 주었는데 카드 안쪽이 아니라 카드 위쪽이 벌어지는 일이 그렇게 생긴다.

부모에 안쪽 여백 없음 앞 요소 24 제목(margin-top: 24) 여백이 카드 밖으로 샌다 부모에 안쪽 여백 16 앞 요소 제목 40 안쪽 여백이 둑이 된다
그림 2. 부모와 자식의 마진 상쇄. 왼쪽 카드에는 안쪽 여백도 테두리도 없어서 첫 자식인 제목의 위 바깥 여백(주황) 24가 카드 밖으로 빠져나가 카드와 앞 요소 사이를 벌린다. 카드의 바탕색은 제목 바로 위에서 시작한다. 오른쪽 카드는 안쪽 여백(초록) 16이 둑 역할을 해서 제목의 바깥 여백이 카드 안에 머물고, 카드 위 가장자리에서 제목까지 16 + 24 = 40이 된다. 테두리 1픽셀이나 display: flow-root로도 같은 둑을 만들 수 있다.

마진 상쇄는 버그가 아니라 명세에 적힌 동작이다. 하지만 시안을 그리는 사람에게는 직관에 어긋나고, 상자 하나를 옮기면 이웃 간격이 함께 바뀌어 예측하기 어렵다. 그래서 요즘의 레이아웃은 바깥 여백보다 부모 상자의 gap 속성을 즐겨 쓴다. 플렉스박스와 그리드 상자에서 gap: 24px을 주면 자식들 사이에만 24가 들어가고, 맨 앞과 맨 뒤에는 들어가지 않으며, 겹치지도 않는다. 플렉스박스의 gap은 2021년 무렵부터 주요 브라우저가 모두 지원한다.

간격은 한 사람이 준다 위아래 간격을 위 상자의 아래 여백과 아래 상자의 위 여백으로 나눠 주면, 마진 상쇄 때문에 둘 중 큰 값만 남고 어느 쪽이 이겼는지 코드만 봐서는 알기 어렵다. 실무에서는 몇 가지 약속 중 하나를 고른다. 바깥 여백은 아래쪽에만 준다. 또는 형제 사이 간격을 부모가 gap으로 정한다. 또는 "앞에 형제가 있는 요소에만 위 여백을 준다"는 선택자(.stack > * + *)를 쓴다. 마지막 방법은 헤이든 피커링(Heydon Pickering)이 2014년 A List Apart에 쓴 글 「Axiomatic CSS and Lobotomized Owls」로 널리 알려졌다. 어느 쪽이든 핵심은 같다. 두 상자 사이의 간격을 정하는 사람은 한 명이어야 한다.

간격 체계: 4와 8

서윤의 첫 시안에는 간격이 13, 17, 22픽셀처럼 제각각이었다. 서윤에게는 이유가 있었다. 눈으로 보며 조금씩 밀고 당겨 "딱 좋아 보이는" 자리를 찾은 결과다. 하지만 지후에게는 그 숫자들이 재현할 수 없는 소음이었다. 13과 15의 차이가 의도인지 손떨림인지 알 길이 없고, 화면이 하나 늘 때마다 새로운 숫자가 생긴다. 해결책은 미리 정한 몇 개의 값만 쓰는 것이다. 이런 값의 목록을 간격 단계(Spacing scale)라고 한다.

단계를 만드는 방법은 여러 가지다. 아래에서 네 가지 방법을 비교한다. 막대 하나가 한 단계이고, 오른쪽 숫자는 바로 아래 단계와의 비다. 슬라이더는 "옆 단계와 구별되는 최소 비"라는 이 장의 문턱이다. 비가 문턱보다 작은 쌍은 붉게 칠한다.

SIMULATOR

간격 단계 만들기

단계 수—
가장 작은 이웃 비—
구별이 어려운 쌍—
해볼 것: ① "시안의 간격"을 본다. 서윤의 첫 시안에서 잰 열두 값(가상 자료)이다. 9와 10, 20과 22처럼 붙은 쌍이 많아 붉은 칸이 줄줄이 생긴다. ② "4씩 더하기"로 바꾼다. 4, 8, 12까지는 잘 벌어지지만 40, 44, 48, 52처럼 큰 쪽은 비가 1.1 아래로 떨어져 서로 구별되지 않는다. 단계가 16개나 되는데 쓸모 있는 단계는 그보다 적다. ③ "두 배씩"은 모든 비가 2라서 구별은 확실하지만 16과 32 사이에 쓸 값이 없다. ④ "4·8 단계"는 작은 쪽은 4씩, 큰 쪽은 비 1.33~1.5로 벌어진다. 문턱을 1.3, 1.5로 올려 가며 어디서부터 붉은 칸이 생기는지 본다. (구별 문턱은 이 장이 정한 교육용 기준이다. 실제로 구별되는 차이는 거리, 화면, 주변 요소에 따라 다르다.)

결과에서 원리 하나를 읽을 수 있다. 사람은 길이의 차이를 절대량보다 비율로 느끼는 경향이 있다. 4와 8의 차이는 분명하지만, 44와 48의 차이는 같은 4픽셀인데도 거의 보이지 않는다. 지각 심리학에서 베버의 법칙(Weber's law)이라고 부르는 오래된 근사와 같은 방향이다. 그래서 좋은 간격 단계는 작은 쪽은 촘촘하고 큰 쪽은 성기다. 이 책이 정한 단계는 다음과 같다.

단계값(px)아래 단계와의 비주로 쓰는 곳(서윤의 첫 안)
14—아이콘과 글자 사이, 날짜 칩과 제목처럼 붙어 있어야 하는 것
282제목과 그 아래 한 줄 설명, 입력 칸과 그 라벨
3121.5목록 항목 사이, 작은 단추 안쪽 여백
4161.33카드 안쪽 여백, 화면 양옆 여백, 카드 안 무리 사이
5241.5카드와 카드 사이
6321.33넓은 화면의 바깥 여백, 큰 카드 안쪽 여백
7481.5구획(행사, 신간)과 구획 사이
8641.33넓은 화면의 큰 구획 사이, 첫 화면 위아래

왜 하필 4의 배수일까. 여러 이유가 겹친다. 첫째, 나누기 쉽다. 4의 배수는 반으로, 다시 반으로 나눠도 정수가 남는 경우가 많아 가운데 맞추기나 반쪽 여백을 계산할 때 소수점이 덜 생긴다. 둘째, 2장에서 본 기기 픽셀 비율이다. CSS 1픽셀은 기기 픽셀 비율(DPR)이 1.5인 화면에서 1.5기기 픽셀이 된다. 13픽셀 간격은 19.5기기 픽셀이라 경계가 픽셀 사이에 걸려 살짝 흐려질 수 있지만, 짝수 값은 DPR 1.5, 2, 3에서 모두 정수가 된다. 셋째, 관례다. 구글의 머티리얼 디자인 지침을 비롯한 여러 디자인 체계가 8 단위 격자(작은 요소에는 4 단위)를 쓰고, 그 관례를 아는 개발자와 디자이너가 많다. 숫자 자체에 마법이 있는 것은 아니다. 5의 배수나 6의 배수로 만든 단계도 같은 일을 할 수 있다. 중요한 것은 목록이 짧고, 모두가 같은 목록을 쓰는 것이다.

단계를 정했으면 시안을 그 단계에 맞춘다. 그런데 기계적으로 가장 가까운 값으로 반올림하면 어떻게 될까. 아래는 서윤의 행사 목록 시안이다. 분홍 띠는 개발자에게 넘기는 시안에 그리는 간격 표시(흔히 "레드라인"이라고 부른다)다.

SIMULATOR

시안의 간격을 단계에 맞추기

간격 값의 종류—
DPR 1.5에서 소수가 되는 값—
묶음 규칙 위반—
해볼 것: ① "첫 시안"의 간격 표시를 본다. 일곱 가지가 넘는 값이 섞여 있고, 대부분 홀수라 DPR 1.5 화면에서 소수 기기 픽셀이 된다. 날짜·제목·장소와 설명 사이(11)가 제목과 장소 사이(9)와 거의 같아, 카드 안이 한 덩어리로 읽힌다. ② "가장 가까운 단계로"를 누른다. 값은 넷으로 줄었지만, 카드 사이(16)가 카드 안쪽 여백(16)과 같아지고 행사 제목과 첫 카드 사이(24)가 구획 사이(24)와 같아졌다. 반올림이 관계를 망가뜨린다. ③ "역할로 다시 정하기"를 누른다. 값마다 역할(무리 안, 무리 사이, 카드 안쪽, 카드 사이, 구획 사이)을 먼저 정하고 단계를 골랐다. 위반이 0이 된다. (가상의 시안이다. 묶음 규칙은 다음 절의 "안쪽 간격 < 바깥 간격"을 다섯 가지 확인으로 나눈 것이다.)

간격 단계는 숫자를 줄이는 도구이지 판단을 대신하는 도구가 아니다. 시안의 13을 12로 바꿀지 16으로 바꿀지는 반올림이 아니라 그 간격이 하는 일로 정한다. 같은 일을 하는 간격은 같은 값, 다른 일을 하는 간격은 확실히 다른 값. 그러면 값의 종류가 줄면서 관계는 오히려 분명해진다.

단계 밖의 값이 필요할 때 실무에서 단계 밖의 값이 꼭 필요할 때가 있다. 테두리 1픽셀 때문에 정렬이 1픽셀 어긋나거나, 글자의 위아래 빈 공간(행간) 때문에 단추 안쪽 여백이 눈으로는 고르지 않아 보일 때다. 이럴 때 15나 7을 몰래 넣지 말고, 왜 필요한지 시안 옆에 적는다. "글자 상자의 위쪽 공간을 빼서 눈으로 16에 맞춘 값"처럼. 이유가 적힌 예외는 체계의 일부가 되고, 이유 없는 예외는 다음 사람에게 13과 17의 수수께끼로 남는다. 6장(색과 글자의 체계)에서 간격 단계에 이름을 붙여 토큰으로 만든다.

간격으로 묶고 떼기

CompositionBook 4장에서 가까이 있는 것은 한 무리로 보인다는 근접의 원리를 봤고, 12장에서 그것을 "무리 안 간격은 좁게, 무리 사이 간격은 확실히 넓게"라는 지면의 규칙으로 옮겼다. 웹의 상자는 이 규칙을 한 겹 더 깊게 만든다. 상자 안에 상자가 있으니, 간격에도 층이 있다. 글자와 글자 사이, 줄과 줄 사이, 무리와 무리 사이, 카드의 테두리와 내용 사이, 카드와 카드 사이, 구획과 구획 사이. 이 층들이 안쪽에서 바깥쪽으로 갈수록 넓어지면 화면은 저절로 읽힌다. 이 장에서는 이것을 "안쪽 간격 < 바깥 간격" 규칙이라고 부른다.

안에서 바깥으로 갈수록 넓게 무리 안4 · 8 무리 사이16 카드 안쪽 여백(무리 사이 이상)16 카드 사이24 구획 사이48
그림 3. 간격의 층. 서윤이 이 장에서 정한 첫 안을 막대로 그렸다(막대 길이는 값에 비례). 무리 안 간격보다 무리 사이가, 무리 사이보다 카드 안쪽 여백이(같아도 된다), 카드 안쪽 여백보다 카드 사이가, 카드 사이보다 구획 사이가 넓다. 층이 하나 바깥으로 나갈 때마다 단계가 한두 칸씩 올라간다.

카드 하나로 확인해 보자. 신간 카드 두 장이 나란히 있고(카드 사이 24로 고정), 카드 안에는 표지, [제목·저자], [가격·재고], 단추가 있다. 대괄호로 묶은 것이 한 무리다. 세 슬라이더로 무리 안 간격, 무리 사이 간격, 카드 안쪽 여백을 바꾼다. 점선 상자는 간격만 보고 눈이 묶을 법한 무리를 CompositionBook 12장과 같은 단순한 규칙으로 찾은 것이다.

SIMULATOR

안쪽 간격 < 바깥 간격

간격의 사슬(카드 사이 24)—
눈이 묶는 무리—
판정—
해볼 것: ① 처음 상태(4 < 16 ≤ 16 < 24)에서 점선 상자가 표지, 제목·저자, 가격·재고, 단추의 네 무리로 나뉘는지 본다. ② 무리 안 간격을 무리 사이와 같은 16으로 올린다. 점선 상자가 하나로 합쳐진다. 모든 줄이 같은 간격이라 제목과 저자가 짝이라는 정보가 사라진다. ③ 무리 안을 24로, 무리 사이를 8로 뒤집는다. 저자와 가격이 한 무리로 묶이는, 거짓 무리가 생긴다. ④ 처음 상태로 돌아와 카드 안쪽 여백을 4로 줄인다. 글이 테두리에 붙어 카드 안 무리 사이(16)보다 테두리와 더 가까워진다. 이번엔 안쪽 여백을 40으로 늘린다. 카드 사이(24)보다 넓어져, 카드 두 장이 한 덩어리의 띠처럼 보이기 시작한다. (무리는 이웃한 간격 중 가장 큰 것과 가장 작은 것의 중간보다 넓은 간격에서 끊는다. 가장 큰 간격이 가장 작은 것의 1.3배보다 작으면 나누지 않는다. CompositionBook 12장과 같은 교육용 규칙이다.)

셋째 실험의 거짓 무리는 반올림 실수에서 흔히 생긴다. 시안에서는 분명 저자 아래가 넓었는데, 값을 옮기다 위아래가 바뀌면 화면이 엉뚱한 이야기를 한다. 넷째 실험의 결과는 조금 미묘하다. 카드 안쪽 여백은 카드 안의 어떤 무리 사이 간격보다 좁으면 안 된다. 그러면 내용이 카드 안의 이웃보다 카드의 벽에 더 가까워져 갇힌 느낌이 든다. 또 카드 안쪽 여백이 카드 사이보다 넓으면, 카드의 경계보다 카드 안의 빈자리가 더 크게 보여 카드들이 한 판으로 붙어 보인다. 바탕색이나 테두리가 있으면 덜하지만 방향은 같다.

이 규칙은 카드에만 쓰는 것이 아니다. 입력 칸과 그 라벨은 4~8로 붙이고, 라벨·입력 칸 묶음과 다음 묶음 사이는 16~24로 뗀다(9장 폼). 단추 안의 아이콘과 글자는 4~8로 붙이고, 단추와 단추 사이는 그보다 넓게 뗀다. 화면 어디서든 "이 둘은 한 편인가?"를 묻고, 한 편이면 가깝게, 아니면 확실히 멀게.

종이 위의 레드라인 자주 쓰는 앱이나 웹사이트의 화면 하나를 갈무리해 A4에 크게 인쇄한다. 자를 대고 간격 열 곳 정도를 잰다. 제목과 부제 사이, 목록 항목 사이, 카드 안쪽 여백, 카드 사이, 화면 가장자리. 잰 값(mm)을 화면 폭으로 나눠 비율로 적고 작은 것부터 큰 것까지 한 줄로 늘어놓는다. 몇 가지 값으로 모이는가? 안쪽 간격이 바깥 간격보다 늘 좁은가? 이번엔 같은 화면을 연필로 따라 그리되, 간격을 모두 똑같이 그려 본다. 무엇이 무엇과 한 편인지 알아보기 어려워지는 순간이 이 절의 규칙이 일하던 자리다.

간격은 누구의 것인가

시안 속 간격 하나를 코드로 옮길 때, 그 간격을 어느 상자의 어느 속성으로 줄지 정해야 한다. 같은 24를 위 카드의 아래 바깥 여백으로 줄 수도, 아래 카드의 위 바깥 여백으로 줄 수도, 두 카드를 담은 부모의 gap으로 줄 수도 있다. 화면은 같아 보여도, 카드를 다른 페이지로 옮기거나 순서를 바꾸면 결과가 달라진다. 위 카드에 아래 여백을 박아 두면 그 카드는 어디에 놓이든 아래에 24를 끌고 다닌다. 목록의 마지막에 놓여도, 혼자 놓여도.

그래서 많은 팀이 이런 약속을 한다. 부품(컴포넌트)은 바깥 여백을 갖지 않는다. 부품 사이의 간격은 부품을 놓는 부모가 정한다. 카드는 자기 안쪽 여백만 알고, 카드끼리 얼마나 떨어질지는 목록이 gap으로 정한다. 그러면 같은 카드를 행사 목록에서는 24 간격으로, 첫 화면 배너 옆에서는 16 간격으로 다시 쓸 수 있다.

시안에서 본 간격누가 정하나CSS마진 상쇄
카드 테두리와 내용 사이카드 자신padding없음
카드 안 무리와 무리 사이카드 안의 배치 상자gap(또는 아래 여백 한쪽만)gap이면 없음
카드와 카드 사이목록(부모)gap없음
구획과 구획 사이페이지(부모)gap 또는 구획의 padding-block바깥 여백으로 주면 일어날 수 있다
화면 가장자리와 내용 사이페이지 바탕 상자padding-inline없음

시안도 이 약속을 따르면 넘기기 쉽다. 간격마다 숫자만 적지 말고 그 간격의 역할을 적는다. "카드 사이 24", "구획 사이 48"처럼. 개발자는 역할을 보고 어느 상자에 줄지 정하고, 다음에 새 화면을 그릴 때 디자이너는 같은 역할에 같은 값을 다시 꺼내 쓴다. 간격이 체계가 되는 것은 숫자가 깔끔해질 때가 아니라 역할에 이름이 붙을 때다.

사용자의 글자 크기를 따라 늘어나는 간격 도경처럼 휴대폰 글자 크기를 크게 쓰는 사람에게는 글자만 커지고 간격은 그대로이면 화면이 빽빽해진다. 간격을 rem(뿌리 글자 크기의 배수, 기본 16px이면 1rem = 16px) 단위로 적으면 사용자가 글자 크기를 키울 때 간격도 함께 커진다. 4·8 단계는 rem으로 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4가 된다. 어느 간격을 글자와 함께 키우고 어느 간격을 고정할지는 글자 단계와 함께 정하는 것이 좋아서, 6장에서 토큰을 만들 때 다시 다룬다. 이 장의 값은 기본 글자 크기 16px 기준의 픽셀로 적었다.

서윤의 웹 개편 노트

CASE 서윤웹 개편 노트 · 3장

열아홉 가지 간격을 여덟 단계로

2028년 1월 둘째 주. 서윤이 2장에서 정한 대로 첫 화면을 "다음 모임" 하나만 크게 두고 다시 그렸고, 행사 일정 시안도 휴대폰 폭으로 함께 넘겼다. 이틀 뒤 지후의 답장이 왔다. 시안 두 장에서 간격을 재 보니 서로 다른 값이 열아홉 가지(가상 자료)였다. 13, 15, 17, 22, 27… "13이랑 15가 다른 의도인가요? 아니면 같은 건데 손이 미끄러진 건가요?" 서윤은 대답하지 못했다. 눈으로 보며 하나씩 맞춘 값이라 어느 것이 의도인지 서윤 자신도 기억하지 못했다.

지후가 먼저 코드로 옮긴 행사 카드에는 문제가 더 있었다. width: 50%로 두 장을 나란히 놓았더니 둘째 카드가 다음 줄로 떨어졌다. 안쪽 여백 16과 테두리 1이 50% 바깥에 더해졌기 때문이다. 카드 제목의 위 바깥 여백은 카드 밖으로 새어 나가 카드 위쪽을 벌렸다. 두 사람은 이 장의 시뮬레이터로 원인을 하나씩 확인했고, 지후는 스타일시트 맨 앞에 모든 상자를 border-box로 바꾸는 규칙을 넣었다. 시안의 폭은 테두리까지를 잰 값으로 읽기로 했다.

간격은 서윤이 단계부터 정했다. 처음에는 4씩 더하는 열여섯 단계를 만들었지만, 40과 44, 44와 48을 나란히 놓고 보니 구별되지 않았다. 작은 쪽은 4씩, 큰 쪽은 비 1.33~1.5로 벌어지는 여덟 단계로 줄였다. 그다음 시안의 간격을 가장 가까운 단계로 반올림해 봤는데, 카드 사이와 카드 안쪽 여백이 둘 다 16이 되고 행사 제목과 첫 카드 사이가 구획 사이와 같은 24가 되어 버렸다. 숫자는 깔끔해졌는데 화면은 더 어수선해졌다. 서윤은 반올림을 버리고 간격마다 역할을 먼저 적은 뒤 값을 골랐다. CompositionBook 4장에서 첫 화면에 정했던 "무리 안 12, 무리 사이 40"도 다시 봤다. 40은 단계에 없다. "무리 사이는 무리 안의 3배 이상"이라는 당시의 기준(12 × 3 = 36)을 지키는 가장 가까운 단계는 48이라, 첫 화면 무리 사이를 48로 올렸다.

간격의 역할첫 시안(가상)서윤의 선택
날짜 칩 → 제목, 제목 → 장소(무리 안)7, 94, 8
카드 안 무리 사이(장소 → 설명 → 단추)11, 2216
카드 안쪽 여백1516
카드 사이1724
구획 제목 → 첫 카드2216
구획 사이(행사 → 신간)2748
화면 양옆 여백(휴대폰)1316

지후는 표를 받자마자 카드에서 바깥 여백을 모두 지우고, 목록과 페이지에 gap을 넣었다. "이제 카드를 어디에 옮겨도 간격이 따라오지 않아요." 서윤은 시안의 레드라인 옆에 숫자 대신 역할 이름을 적기 시작했다.

간격 단계4·8·12·16·24·32·48·64 카드 안쪽 · 사이16 / 24 구획 사이48 상자 폭border-box
이 장에서 정한 것간격 단계는 4의 배수 여덟 개: 4·8·12·16·24·32·48·64(px, 기본 글자 16px 기준). 규칙은 "안쪽 간격 < 바깥 간격": 무리 안 4·8 < 카드 안 무리 사이 16 ≤ 카드 안쪽 여백 16 < 카드 사이 24 < 구획 사이 48. 구획 제목은 자기 목록에 붙인다(16). 휴대폰 화면 양옆 여백 16. 첫 화면의 무리 사이 40은 48로 옮긴다. 모든 상자는 box-sizing: border-box, 시안의 폭은 테두리까지. 부품은 바깥 여백을 갖지 않고 간격은 부모의 gap으로 준다. 단계 밖의 값은 이유를 적은 예외로만.
아직 남은 문제주인은 신간을 한눈에 많이 보여 주고 싶어 한다. 신간 카드를 넓은 화면에서 한 줄에 몇 권씩, 얼마나 넓게 놓을지 아직 정하지 않았다. 카드 사이 24를 그대로 둘지도 카드 배치와 함께 다시 봐야 한다.
다음 단계4장: 열 그리드 위에 신간 카드를 놓고 단, 거터, 최대 폭을 정한다.

핵심 정리

  1. 브라우저는 모든 요소를 직사각형 상자로 만들고, 페이지는 상자 안에 상자가 든 중첩 구조다. 블록 상자의 폭은 바깥(부모)에서, 높이는 안(내용)에서 정해진다.
  2. 박스 모델은 내용, 안쪽 여백(padding), 테두리(border), 바깥 여백(margin)의 네 겹이다. 바탕색은 테두리 안쪽에 칠해지고 바깥 여백은 투명하다. outline과 그림자는 자리를 차지하지 않는다.
  3. 기본 content-box에서 width는 내용 폭이라 안쪽 여백과 테두리가 바깥에 더해진다. border-box는 width를 테두리까지의 폭으로 읽어 시안의 "폭"과 같은 뜻이 된다.
  4. 일반 블록 흐름에서 위아래로 맞닿은 바깥 여백은 큰 쪽 하나로 겹친다(마진 상쇄). 부모와 첫 자식 사이에서도 일어나고, 플렉스·그리드 안에서는 일어나지 않는다. 간격은 한 사람(대개 부모의 gap)이 준다.
  5. 간격 단계는 짧은 목록이다. 작은 쪽은 촘촘하게, 큰 쪽은 비율로 벌어지게(4·8·12·16·24·32·48·64). 시안을 맞출 때는 반올림이 아니라 간격의 역할로 값을 고른다.
  6. "안쪽 간격 < 바깥 간격": 무리 안 < 무리 사이 ≤ 카드 안쪽 여백 < 카드 사이 < 구획 사이. 간격이 뒤집히면 거짓 무리가 생긴다.

확인 퀴즈

1. box-sizing을 바꾸지 않은 상자에 width: 300px; padding: 20px; border: 2px solid; margin: 20px;를 주었다. 이 상자의 눈에 보이는 폭(테두리까지)은?

기본값 content-box에서 width는 내용 폭이다. 안쪽 여백 20과 테두리 2가 양쪽에 더해져 300 + 40 + 4 = 344가 된다. 384는 바깥 여백까지 더한 "차지하는 폭"이고, 322는 한쪽 변만 더한 값이다. 300은 border-box였다면 맞는 답이다.

2. 일반 블록 흐름에서 위 문단에 margin-bottom: 32px, 바로 아래 문단에 margin-top: 24px을 주었다. 두 문단 사이의 간격은?

위아래로 맞닿은 두 블록 상자의 양수 바깥 여백은 더해지지 않고 큰 쪽 하나로 겹친다(마진 상쇄). 그래서 32다. 56은 두 문단이 플렉스나 그리드 상자의 자식이어서 겹치지 않을 때의 값이고, 8은 차이를 잘못 계산한 값이다.

3. 신간 카드의 간격을 정했다. "안쪽 간격 < 바깥 간격" 규칙에 맞는 것은? (무리 안 / 카드 안 무리 사이 / 카드 안쪽 여백 / 카드 사이 순)

안에서 바깥으로 갈수록 같거나 넓어져야 한다. 8 < 16 ≤ 16 < 24가 맞다. 모두 16이면 무리가 구별되지 않는다. 8 / 24 / 12 / 32는 카드 안쪽 여백(12)이 카드 안 무리 사이(24)보다 좁아 내용이 테두리에 갇힌다. 8 / 16 / 32 / 24는 카드 안쪽 여백(32)이 카드 사이(24)보다 넓어 카드들이 한 판으로 붙어 보인다.

4. 간격 단계를 4의 배수로 만드는 까닭으로 이 장의 설명과 맞지 않는 것은?

브라우저는 13픽셀 간격도 13픽셀로 그린다(DPR에 따라 소수 기기 픽셀이 되어 살짝 흐려질 수는 있다). 4의 배수에 마법이 있는 것이 아니라 계산, 기기 픽셀, 관례라는 실용적 이유가 있고, 무엇보다 목록이 짧고 모두가 같은 목록을 쓰는 것이 중요하다.

출처