CSS를 배우기 시작하고 얼마 안 됐을 때, 나는 폭을 분명히 300으로 지정했는데 요소가 그보다 커지는 현상에 몹시 당황했다. 여백을 조금 줬을 뿐인데 옆 요소가 아래로 밀려나고, 테두리를 두르자 레이아웃이 통째로 어긋났다. 원인은 하나였다. 화면의 모든 요소가 실은 여러 겹으로 된 상자라는 사실을 몰랐던 것이다.
이번 편은 CSS 레이아웃의 가장 밑바닥에 있는 박스 모델을 다룬다. 내용과 안쪽 여백, 테두리, 바깥 여백이 어떻게 한 상자를 이루는지, 그리고 폭 계산을 직관적으로 바꾸는 설정이 왜 사실상 표준이 되었는지 짚는다. 이 구조를 손에 익히면 레이아웃이 왜 어긋나는지를 눈으로 그릴 수 있게 된다.
1. 모든 요소는 네 겹의 상자다
브라우저는 화면의 모든 요소를 사각형 상자로 취급한다. 이 상자는 안쪽부터 바깥으로 네 겹이다. 실제 글이나 이미지가 들어가는 콘텐츠 영역, 그 둘레를 감싸는 안쪽 여백, 그 바깥의 테두리, 그리고 다른 상자와의 간격을 만드는 바깥 여백이다. 이 네 겹을 머릿속에 그리는 것이 레이아웃 이해의 출발점이다.
콘텐츠 영역은 상자의 심장이다. 텍스트나 이미지가 실제로 자리하는 부분이고, 폭과 높이 속성은 기본적으로 이 콘텐츠 영역의 크기를 가리킨다. 내가 초반에 헷갈린 지점이 바로 여기였다. 폭을 지정한 값이 상자 전체가 아니라 가장 안쪽 콘텐츠에만 적용된다는 걸 몰랐던 것이다.
안쪽 여백은 콘텐츠와 테두리 사이의 숨 쉴 공간이다. 버튼 안 글자가 가장자리에 딱 붙지 않게 하거나, 카드 안 내용이 테두리에서 떨어지게 할 때 쓴다. 이 여백은 상자의 배경색이 칠해지는 영역이기도 하다. 그래서 배경을 준 요소에 안쪽 여백을 주면 색이 있는 여유 공간이 생긴다.
테두리는 안쪽 여백과 바깥 여백 사이에 그어지는 선이다. 두께와 스타일과 색을 지정할 수 있고, 두께가 있으면 그만큼 상자의 전체 크기에 더해진다. 나는 예전에 테두리를 두르면 요소가 커지는 걸 이상하게 여겼지만, 테두리도 엄연히 상자의 한 겹이니 당연한 일이었다.
바깥 여백은 이 상자와 이웃 상자 사이의 거리를 만든다. 배경색이 칠해지지 않는 투명한 간격이며, 요소끼리 서로 밀어내는 역할을 한다. 안쪽 여백이 상자 내부의 숨통이라면, 바깥 여백은 상자들 사이의 거리감이다. 이 둘을 혼동하면 간격이 엉뚱한 곳에 생겨 레이아웃이 뒤틀린다.
이 네 겹을 정확히 구분하는 것만으로도 많은 문제가 풀린다. 요소가 예상보다 크다면 안쪽 여백이나 테두리를 의심하고, 요소끼리 너무 붙었다면 바깥 여백을 살핀다. 나는 개발자 도구로 이 네 겹을 색깔별로 확인하는 습관을 들이고 나서야 레이아웃 디버깅이 훨씬 빨라졌다.
2. 콘텐츠 상자와 테두리 상자
박스 모델에는 폭을 계산하는 두 가지 방식이 있다. 기본값은 콘텐츠 상자 방식인데, 여기서는 폭 속성이 오직 콘텐츠 영역만 가리킨다. 그래서 폭을 300으로 주고 안쪽 여백 20과 테두리 5를 더하면, 실제 상자는 양쪽을 합쳐 훨씬 넓어진다. 이 계산의 어긋남이 초보 시절 나를 가장 괴롭혔다.
다른 방식은 테두리 상자다. 여기서는 폭 속성이 콘텐츠뿐 아니라 안쪽 여백과 테두리까지 포함한 전체를 가리킨다. 폭을 300으로 주면 안쪽 여백과 테두리가 그 300 안에서 자리를 나눠 갖고, 콘텐츠 영역이 그만큼 줄어든다. 상자의 겉 크기가 내가 적은 숫자와 정확히 일치하는 것이다.
이 차이는 실무에서 하늘과 땅이다. 콘텐츠 상자 방식에서는 여백을 조금만 바꿔도 전체 크기가 흔들려 옆 요소와 어긋난다. 테두리 상자 방식에서는 겉 크기가 고정이라 안쪽을 어떻게 채우든 레이아웃이 안정적이다. 그래서 나는 예외 없이 테두리 상자 방식을 쓴다.
많은 사람들이 프로젝트 맨 앞에 전역 규칙 하나를 둔다. 모든 요소와 가상 요소에 테두리 상자 방식을 적용하는 규칙이다. 이 한 줄이 사실상 현대 CSS의 기본 초기화로 자리 잡았다. 나도 새 작업을 시작하면 습관처럼 이 규칙을 가장 먼저 적는다.
테두리 상자 방식이 좋은 이유는 계산이 사람의 직관과 맞아떨어지기 때문이다. 폭 300짜리 칸을 절반씩 나눠 둘을 놓고 싶으면 각각 150을 주면 되고, 안쪽 여백이 있어도 겉은 150 그대로다. 콘텐츠 상자 방식이었다면 매번 여백과 테두리를 빼는 산수를 해야 했을 것이다.
다만 폭 값이 안쪽 여백보다 작으면 콘텐츠가 밀려 이상해질 수 있으니, 극단적인 경우엔 여전히 주의가 필요하다. 그래도 대부분의 실무에서는 테두리 상자 방식이 사고를 크게 줄인다. 방식 하나를 바꾸는 것만으로 레이아웃이 예측 가능해진다는 건, 내가 CSS에서 배운 가장 값진 지름길 중 하나였다.
3. 여백 상쇄라는 함정
박스 모델을 안다고 자신했을 때 나를 다시 무너뜨린 것이 여백 상쇄였다. 위 요소에 아래 여백 20을 주고 아래 요소에 위 여백 30을 주면, 둘 사이 간격은 50이 아니라 30이 된다. 두 여백이 더해지지 않고 큰 값 하나로 합쳐지는 것이다. 이 현상을 모르면 간격 계산이 계속 어긋난다.
여백 상쇄는 수직 방향에서, 그리고 일반적인 블록 흐름 안에서만 일어난다. 좌우 방향의 여백은 상쇄되지 않고 그대로 더해진다. 그래서 가로로 나열된 요소들 사이 간격은 예측대로 계산되지만, 세로로 쌓인 요소들 사이에서는 이 상쇄를 늘 염두에 둬야 한다.
부모와 자식 사이에서도 상쇄가 일어난다. 부모에 위 여백이 없고 자식에 위 여백을 주면, 그 여백이 부모 밖으로 삐져나와 부모 전체가 밀리는 황당한 일이 생긴다. 나는 카드 안 제목에 여백을 줬는데 카드째로 아래로 밀리는 걸 보고 한참을 헤맸다. 원인은 이 부모 자식 간 상쇄였다.
상쇄를 끊는 방법은 여러 가지다. 부모에 안쪽 여백이나 테두리를 아주 조금이라도 주면 경계가 생겨 상쇄가 멈춘다. 부모를 새로운 서식 문맥으로 만들어도 상쇄가 차단된다. 나는 보통 여백 대신 안쪽 여백으로 간격을 설계해 상쇄 자체가 일어날 여지를 줄인다.
더 근본적인 해법은 한 방향으로만 여백을 주는 습관이다. 요소들 사이 간격을 위 여백과 아래 여백을 섞어 주지 않고, 예컨대 아래 여백으로만 통일하면 상쇄가 끼어들 틈이 줄어든다. 최근에는 부모에 간격 속성을 줘서 자식들 사이 거리를 한 번에 지정하는 방식이 여백 상쇄 고민을 상당히 덜어준다.
여백 상쇄는 CSS의 오래된 규칙이라 사라지지 않는다. 그러니 없애려 애쓰기보다 언제 일어나는지를 정확히 아는 편이 낫다. 수직 방향, 블록 흐름, 인접하거나 감싸는 관계, 이 세 조건이 겹칠 때 의심하면 대부분 원인이 잡힌다. 나는 이 조건을 외워두고 간격이 이상할 때 가장 먼저 떠올린다.
4. 테두리와 아웃라인, 그리고 정렬
테두리와 비슷해 보이지만 성격이 다른 것이 아웃라인이다. 아웃라인은 요소 바깥에 그려지는 선인데, 상자의 크기에는 전혀 영향을 주지 않는다. 즉 아웃라인을 둘러도 레이아웃이 밀리지 않는다. 나는 이 차이를 안 뒤로 임시 디버깅용 선은 아웃라인으로, 실제 디자인 선은 테두리로 나눠 쓴다.
아웃라인의 진짜 쓸모는 접근성에 있다. 키보드로 이동할 때 지금 어디에 포커스가 있는지 알려주는 표시가 대개 아웃라인이다. 그런데 이게 보기 싫다고 무작정 없애면 키보드 사용자는 자기가 어디 있는지 알 수 없게 된다. 나는 아웃라인을 지우는 대신, 더 예쁜 포커스 표시로 대체하는 방향을 택한다.
둥근 모서리를 만드는 속성은 테두리가 없어도 동작한다. 배경만 있는 상자의 모서리를 부드럽게 깎을 수 있고, 값을 크게 주면 완전한 원이나 알약 모양도 만든다. 나는 프로필 이미지나 버튼의 인상을 결정할 때 이 속성을 즐겨 조절한다. 작은 반경 하나가 전체 분위기를 바꾼다.
상자를 가로로 중앙에 놓는 고전적인 방법은 바깥 여백을 자동으로 두는 것이다. 폭이 정해진 블록 요소에 좌우 여백을 자동으로 주면, 남은 공간을 양쪽이 똑같이 나눠 가져 가운데로 온다. 콘텐츠 폭을 제한한 본문 영역을 화면 중앙에 두는 흔한 방식이 바로 이것이다.
음수 여백이라는 기술도 있다. 여백에 음수를 주면 요소를 이웃 쪽으로 끌어당기거나 겹치게 할 수 있다. 나는 겹쳐 보이는 카드나 살짝 튀어나온 배지를 만들 때 이걸 쓰지만, 남용하면 레이아웃이 뒤죽박죽되니 조심한다. 강력하지만 다루기 까다로운 도구다.
정렬과 관련해 최근에는 논리적 속성이 널리 쓰인다. 좌우를 뜻하는 인라인 방향 여백을 자동으로 주는 식인데, 글쓰기 방향이 바뀌어도 자동으로 대응한다는 장점이 있다. 나는 이제 좌우 대칭 여백이 필요할 때 물리적인 왼쪽 오른쪽 대신 인라인 방향 속성을 먼저 떠올린다. 국제화까지 한 번에 챙기는 습관이다.
5. 박스 모델을 실무에서 다루는 법
내가 새 프로젝트에서 가장 먼저 하는 일은 앞서 말한 테두리 상자 전역 규칙을 까는 것이다. 이 한 줄로 이후 모든 폭 계산이 직관과 맞아떨어진다. 그다음엔 브라우저 기본 여백을 정리해 예상치 못한 간격을 없앤다. 이 두 가지가 박스 모델을 다스리는 나만의 기본 준비다.
간격 설계는 되도록 한 방향으로 통일한다. 요소들 사이 간격을 아래 여백으로만 주거나, 아예 부모의 간격 속성으로 몰아준다. 이렇게 하면 여백 상쇄의 함정을 피하고, 나중에 간격을 조절할 때도 한 곳만 손대면 된다. 여백을 사방에 흩뿌리던 예전 방식과 비교하면 유지보수가 훨씬 편하다.
요소가 예상보다 크거나 작을 때는 네 겹을 순서대로 점검한다. 콘텐츠 폭이 맞는지, 안쪽 여백이 더해졌는지, 테두리 두께가 있는지, 바깥 여백이 간섭하는지를 차례로 확인한다. 개발자 도구가 이 네 겹을 색으로 보여주므로, 나는 감으로 추측하지 않고 눈으로 확인한 뒤에 손을 댄다.
간격의 기준값은 미리 정해두면 좋다. 예를 들어 여백과 안쪽 여백을 일정한 배수로만 쓰기로 정하면 화면 전체의 리듬이 통일된다. 나는 간격 값을 변수로 정의해두고 그 배수만 쓰는데, 이렇게 하면 나중에 전체 간격을 한 번에 넓히거나 좁히기도 쉽다. 박스 모델을 시스템으로 관리하는 셈이다.
이미지나 미디어 같은 치환 요소는 박스 모델이 조금 다르게 동작한다는 것도 알아둬야 한다. 이런 요소는 인라인이면서도 크기 지정이 되고, 종횡비를 가진다. 나는 이미지에 폭과 높이를 명시해 레이아웃이 흔들리지 않게 하고, 필요하면 종횡비 속성으로 비율을 고정한다. 미디어의 상자를 다스리는 요령이다.
정리하면 모든 요소는 콘텐츠, 안쪽 여백, 테두리, 바깥 여백의 네 겹으로 된 상자이고, 폭 계산 방식을 테두리 상자로 바꾸면 레이아웃이 직관과 맞아떨어지며, 여백 상쇄 같은 함정은 조건을 알면 피할 수 있다. 이 상자 하나하나가 모여 화면을 이룬다. 다음 편에서는 이 상자들이 어떤 방식으로 배치되는지를 결정하는 표시 속성을 다룬다.