웹 개발자는 아니지만 웹 페이지를 꾸며야 하는 사람들을 위한, CSS의 유용한 부분과 피하기 어려운 함정들을 정리한 글.
2026년 6월 4일 웹 페이지에 스타일을 입혀야 하지만 웹 개발자는 아닌 사람들을 위한, 대용품 같은 CSS 튜토리얼이다. 이런 종류의 글을 쓰기에 나는 적임자가 아니다. 시간도 없고 경험도 없다. 차라리 이 주제에 관한 책을 읽고 싶다. 하지만 어쩔 수 없이 MDN을 뒤져 가며 이 모든 것을 배워야 했으니, 지금까지 알게 된 것을 기록해 두는 데는 아마 가치가 있을 것이다.
CSS, HTML, 그리고 Web API는 정말로 방대하며, 전문가가 되려면 커리어 전체가 필요하다. 좋은 소식은 현대 웹에는 비교적 규모가 적당하고 배울 수 있는 부분집합이 있어서, 프로그래밍 블로그나 단순한 GUI 같은 간단한 작업에는 그것만으로 충분하다는 점이다. 이 부분집합만 딱 가르쳐 주는 자료는 아직 보지 못했지만, 그렇게 알아내기 어렵지는 않다. 나쁜 소식은 페이지를 망가뜨릴 불쾌한 함정들의 집합도 함께 있다는 점이다. 존재할 거라고 짐작도 못 한 것들이고, 알아내려면 며칠 동안 디버깅해야 한다. 그래도 그 정도로 나쁘지는 않다. 나는 이 사이트의 스타일링에 꽤 만족하고 있고, 읽기 쉬운 CSS는 고작 200줄 정도다.
좋음: HTML5 시맨틱 태그 이름
MDN의 Elements Reference를 훑어볼 만하다. 요소 수가 그렇게 많지 않고, main, article, nav, kbd 같은 것들은 페이지 구조를 잡기 훨씬 쉽게 해 준다. 덜 자명한 예를 들면:
header > nav 안의 사이트 섹션처럼, 어떤 종류의 목록이든 ul을 쓴다.details를 쓴다(MDN의 소스를 보라).dl/dt를 쓴다.나쁨: 래퍼
“진짜” 웹사이트에서 “페이지 소스 보기”를 해 보면, 모든 것에 래퍼 요소가 겹겹이 둘러져 있다는 것을 알게 된다. 그래서 래퍼가 레이아웃 문제를 푸는 방식이라고 착각하게 될 수 있다. 나는 “프로덕션” CSS를 써 본 적이 없어서 여기에 정말 동의하거나 반대할 수는 없지만, 내 경험으로는 반대로 하는 편이 훨씬 이해하기 쉽다. 즉, 마크업의 의미를 가진 시맨틱 태그만 쓰도록 스스로를 제한하고, 그런 다음 가진 마크업에 맞게 동작하는 CSS를 찾아내는 것이다.
나쁨: 레이아웃
이것은 웹만의 문제는 아니다. 내가 아는 모든 GUI 프레임워크에서 레이아웃은 고통이다. 고정 크기의 래스터 이미지 하나와, 그것을 설명하는 텍스트 문단 하나를 상상해 보라. 화면이라는 직사각형 안에 이 두 요소를 배치하는 방법은 많다. 일반적으로 주어진 너비와 높이마다, 전체 면적만 충분하다면 그럭저럭 괜찮은 배치를 할 수 있다. 전형적인 GUI는 이런 상자들의 계층 구조이며, “레이아웃 자유도”가 많다. 하지만 문제는 각 상자의 레이아웃이 다른 모든 상자의 레이아웃에도 영향을 준다는 점이다. 대개 모든 상자가 빈틈이나 겹침 없이 정확히 맞닿기를 원하기 때문이다. 여기서 중요한 부정적 깨달음은 그 레이아웃 알고리즘이라는 것은 존재하지 않는다는 점이다. GUI 상자를 배치하고 크기를 정하는 완전히 일반적인 해법은 없다. 대신 서로 다른 시스템이 작업을 처리하기 위해 서로 다른 휴리스틱 집합을 사용한다. 단순한 RectCut부터 완전히 일반적인 constraint solvers까지, 그리고 그 사이의 모든 것이 있다. 레이아웃이 일반적으로 어떻게 작동하는지에 대한 정신 모델을 잡기는 어렵다. 그러니 “주어진 시스템에서 내 레이아웃을 어떻게 구현하지?”라고 생각하지 말고, 대신 “이 시스템이 허용하는 가능한 레이아웃은 무엇이지?”라고 생각하라.
나쁨: 브라우저 기본값
CSS 없이, 맨몸이지만 그래도 시맨틱한 블로그 글 HTML 마크업부터 시작해 보자. 브라우저에서 열면 _무언가_가 보일 것이다. 콘텐츠가 무스타일 상태인 것은 아니다. 텍스트에는 특정 색, 글꼴, 크기가 있다. 제목은 본문보다 크고, 링크에는 밑줄이 있다. 이런 것들이 브라우저의 기본 스타일이다. 이것들은 도움이 된다! 문제는 이런 스타일이 브라우저마다 다르다는 점이다. 그래서 여러분이 CSS를 추가해 최종 결과가 여러분 브라우저에서는 괜찮아 보여도, 나는 다른 것을 볼 수 있다. 여러분이 자신도 모르는 사이 브라우저 기본값에 의존하고 있을 수 있기 때문이다. 마지막 부분이 치명적이다. 문제는 여러분이 쓰지 않은 것 안에 있다.
여기서의 일반적인 해법은 CSS reset 또는 normalization이다. 즉, 기본값을 덮어쓰는 명시적인 규칙 집합으로 CSS를 시작하는 것이다. 기본값이 본질적으로 나빠서가 아니라, 일관되지 않기 때문이다. 실제로 어떤 규칙 집합을 덮어써야 하는지는 나도 모르겠다. 여러 기존 CSS reset을 비교해 보는 것이 좋다.
이것은 더 큰 질문과 맞닿아 있다. 과연 웹 페이지에 스타일을 입혀야 할까? 웹 플랫폼에 대해서는 경쟁하는 두 관점이 있다. 어떤 사람들은 그것을 디자인을 표현하기 위한 유연하고 적응적인, 주로 시각적인 매체로 본다. 다른 사람들은 웹이 콘텐츠 전달에 집중하고, 각 사용자가 표현 방식을 직접 커스터마이즈할 수 있기를 바란다. 여기서 내 개인적인 답은 실용적이다. 기본 상태의 무스타일 페이지는 사용성이 나쁘고 보기에도 좋지 않다. CSS 없는 페이지가 그대로 읽기 좋았으면 하는 세상을 나는 더 선호했겠지만, 이 세계에서는 콘텐츠에 스타일을 입히는 것이 도움이 된다고 생각한다. 동시에, 고급 사용자가 자기만의 CSS를 가져올 수 있도록 허용하는 것도 좋은 생각이다. HTML 마크업이 합리적인지, CSS에 맞추느라 HTML을 과도하게 왜곡하지 않았는지(그 반대는 괜찮다), 그리고 페이지가 리더 모드에서도 동작하는지 확인하라.
좋음:클래스 없는 CSS
스타일을 완전히 중립적인 무로 리셋할 수는 없다. 텍스트를 보이지 않게 만들어도(흰색이든 투명이든) 그것 역시 스타일이다. 그러니 차라리 받아들이는 편이 낫다. 리셋 후에는 흔한 HTML 요소를 직접 스타일링하라. 예를 들어, 모든 코드 조각에 좋아하는 글꼴을 설정하려면:
code { font-family: "JetBrains Mono", monospace; }
모든 코드 조각에 대해 main, header, footer, nav 태그를 사용한다면, CSS 셀렉터를 하나도 쓰지 않고도 전체 페이지 레이아웃을 설정할 수 있다. 물론 이렇게 하려면 CSS 안에서 HTML 구조에 대해 가정을 해야 한다. 하지만 이건 여러분의 HTML이고 여러분의 CSS다. 마음대로 해도 된다. 그리고 결과가 마음에 들지 않으면 언제든 바꾸면 된다!
나쁨: CSS 셀렉터
프로그래밍에서는 우리는 집단적으로 상속을 불신하게 되었고 조합을 선호하게 되었다. 기본 CSS는 초강화된 상속과 같아서, 웹 페이지의 각 디자인 요소는 여러 규칙의 영향을 받고, CSS 뒤에 덧붙이는 것만으로 기존 요소를 언제든 “몽키 패치”할 수 있다. 불행하게도 CSS가 제공하는 수단과, 여러분이 실제로 하고 싶은 일 사이에는 간극이 있다. 합리적인 접근은 둘이다:
CSS 셀렉터가 잘못된 축 방향으로 추상화 능력을 추가한다고 결론 내리고, 클래스 없는 CSS와 인라인 스타일에 머문다. 인라인 작성을 좀 더 보기 좋게 하려면 Tailwind 같은 것을 쓰고, HTML에서 반복을 피하려면 JSX 같은 것(혹은 조합을 지원하는 다른 템플릿 엔진)을 쓴다.
CSS nesting을 사용해서 “멀리 뻗는” 셀렉터 작성을 피하고, 컴포넌트별로 스타일링한다:
header { /* 사이트 헤더 */
margin-bottom: 2rem;
& nav {
/* 헤더 안의 nav에만 해당하는 스타일. */
}
}
나쁨:box-sizing
UI는 재귀적인 직사각형들의 집합이고, 레이아웃은 각 직사각형이 어디로 가는지를 알아내는 과정이며, 그것은 직사각형 자체의 크기에 의해 결정된다. 그러므로 _무엇_이 크기인지 이해하는 것은 아주 근본적이다. 슬프게도 HTML에서 기본 크기 정의는 매우 직관에 어긋난다. 요소의 너비와 높이에는 요소의 테두리와 패딩이 포함되지 않아서, 놀라운 결과가 생긴다. 처음에는 모든 것이 완벽해 보이지만, 어딘가의 패딩을 늘리면 전체 레이아웃이 예상치 못하게 밀려난다. 이런 이유로,
* { box-sizing: border-box;
}
는 CSS reset의 첫 줄이 될 자격이 있다. 이렇게 하면 요소가 캡슐화되어, 테두리를 추가하는 일이 국소적인 변경으로만 남는다.
혼돈 선:margin collapsing
요소 둘레에 8px 간격을 두고 싶다고 하자. 아마 여러분은 padding 속성을 설정해야 한다고 생각할 것이다. 하지만 그것은 틀렸다. 그런 요소가 둘 나란히 있으면, 그 사이 간격은 16px가 된다. 패딩이 더해져 의도보다 큰 시각적 간격을 만들기 때문이다. 여러분이 원하는 것은 오히려 사회적 거리두기와 비슷한 것이다. 한 사람이 더 내향적이면, 그 사람의 더 큰 배제 반경이 거리를 결정하는 식이다. 그리고 margin 속성이 바로 그렇게 동작한다. 이웃한 두 margin은 sum이 아니라 max로 결합된다. Margin collapsing은 매우 유용하지만, 사람을 놀라게 할 수 있다. 예를 들면, 나는 자식의 margin이 부모 바깥으로 삐져나갈 수 있다고 생각한다? 솔직히 말해 margin에 대한 좋은 직관적 이해는 없다. 하지만 적어도 문제가 그것이라는 점을 알아챌 정도는 안다.
Margin은 이 글을 쓰게 된 간접적인 계기 중 하나이기도 하다. 다음 글에서
Tailwind에서 멀어지고, 내 CSS를 구조화하는 법을 배우기
Julia Evans는 일반적으로 요소 자체에 margin을 설정하고 싶어 하지 않으며, 대신 소위 owl selector를 사용해 부모가 자식들 사이의 간격을 제어하게 해야 한다고 쓴다:
section > *+* {
margin-top: 1rem;
}
즉, 첫 번째를 제외한 모든 section의 자식에게 margin을 추가하는 것이다. 나는 그걸 몰랐다! 그리고 지금까지 margin이 내게 준 고통을 생각해 보면, 왜 이렇게 하고 싶어지는지, 왜 이것이 좋은 생각인지 실제로 이해가 간다. 하지만 “전문” 웹 개발자가 되거나, 남의 CSS 프레임워크를 역공학하지 않고는 그런 것을 배울 수 없다는 사실이 나를 거슬리게 한다.
나쁨:기본(플로우) 레이아웃
일반적으로 레이아웃은 까다롭다. 보편적인 “레이아웃 알고리즘”이 없고, 그저 특수 사례들의 묶음이기 때문이다. 그런데 HTML은 실제로 무엇을 할까? 기본 레이아웃 알고리즘은, 내 생각에는, 문서를 위한 언어로서 HTML의 기원까지 거슬러 올라가며, 종이를 만들어 내는 용도에 과도하게 맞춰져 있다. 즉, 그림 몇 개가 들어간 주로 텍스트 콘텐츠이고, 텍스트는 그림 주위를 흐를 수 있다. 사실 이것은 블로그 본문 텍스트에는 딱 원하는 동작이지만, 페이지 요소들의 공간적 배치를 실제로 제어하고 싶어지는 순간에는 다른 것이 필요하다. 예를 들면…
좋음:flexbox
이것이야말로 현대 웹 개발을 옛 시절과 가르는 진짜 요소다. 옛날에는 “이건 왼쪽으로, 저건 오른쪽으로”라고 말하려면 CSS 박사 학위나 불투명한 CSS 프레임워크 전체가 필요했다. 이 레이아웃은 가용 공간에 적응하면서 일련의 요소를 세로 또는 가로로 배치할 수 있게 해 준다. 꽤 복잡해서 나는 MDN을 계속 참고하지 않고서는 flexbox를 쓸 수 없지만, 대개는 결국 해낸다.
나쁨: 반응형 디자인
현대 CSS는 화면 크기를 질의하고, 그에 따라 조건부 로직을 구현할 수 있게 해 준다. 즉, 사용자 에이전트의 제약에 “반응하는” 디자인이다. 아마 “진짜” CSS에서는 이것을 써야 할 것이다. 하지만 HTML은 본질적으로 반응형이라는 점에 주목하라. PostScript(PDF)와 달리, 창 크기를 바꾸면 문단이 자동으로 다시 흐른다. 그러니 명시적인 반응형 규칙을 쓰는 것은 피하고, 대신 레이아웃이 알아서 합리적인 일을 하도록 두는 것이 좋다. 예를 들어 이 블로그는 명시적인 @media 쿼리 없이도 모바일, 태블릿, 데스크톱에서 괜찮아 보인다. 본문 주 열에 무조건 max-width를 설정하는 것만으로 충분하다.
질서 악: 픽셀
1px는 여러분이 원하는 일을 하지만, 말하는 그대로의 뜻은 아니다. 화면 위의 물리적 픽셀 하나의 크기가 아니다. 대신 시각 각도의 척도다. 즉, 1px는 어떤 화면에서든 지각적으로 비슷하게 보여야 하고, 화면 크기, 픽셀 밀도, 일반적인 시청 거리에 따라 서로 다른 수의 물리적 픽셀로 변환된다. 그래서 서로 다른 디스플레이의 픽셀 밀도를 신경 쓰지 않고도, 모든 것을 그냥 픽셀 단위로 크기 지정할 수 있다. 더 이상해진다. CSS는 센티미터나 인치 같은 “진짜” 단위도 허용하지만, 그것들 역시 각도다. 왜냐하면 모든 것이 픽셀을 기준으로 정의되기 때문이다.
이중플러스나쁨:font-size
Flexbox는 UI 요소를 배치하는 좋은 방법이다. 플로우 레이아웃은 텍스트 문단을 배치하는 데는 괜찮다. 하지만 개별 줄과 글리프 수준에서 무슨 일이 벌어지는지는, 내 생각에, 완전한 난장판이자 초보자 함정이다. 가장 기본부터 시작해 보자. font-size: 16px라고 쓰면 16px는 무엇의 크기일까? 슬프게도 답은 “특정한 아무것도 아니다”이다. 이것은 글리프 둘레의 가상 상자 크기인데, 그 상자는 딱 맞지 않고, 글리프의 크기는 글꼴에 따라 달라진다. 다행히 font-size-adjust 속성이 이것을 고쳐 주어 font-size를 글꼴 간에 일관되게 만들 수 있다. 자세한 내용은 다음 두 글을 보라:
하지만 현재 시점에서 font-size-adjust는 매우 틈새 기능처럼 보인다. 그래서 개인적으로는
font-size-adjust: ex-height
0.53;
를 box-sizing 바로 옆에 둘 것이지만, 그렇게 하는 페이지는 거의 없다.
font-size의 다음 문제는 기본값이라는 까다로운 질문이다. 좋은 소식은 이것이 브라우저 간에 꽤 일관적인 속성 중 하나라서, 16px가 압도적인 기본값이라는 점이다. 나쁜 소식은 글꼴에 따라 16px가 꽤 작을 수 있다는 점이다. 완전히 읽을 수 없을 정도는 아니지만, 하한선에 아주 가깝다. 더 나쁜 것은 일부 기본 글꼴이 특히 작다는 것이다. 예를 들어 Apple에서는 font-family: serif가 sans-serif보다 훨씬 작아 보이고, 16px에서는 읽기에 거의 불편하다.
그렇다면 선택한 글꼴에 가장 잘 맞는 font-size: 18px 같은 값을 그냥 설정하면 될까? 내 생각에 답은 그렇다. 하지만 염두에 두어야 할 몇 가지 주의점이 있다. 자세한 내용은 Accessibility: px or rem?을 참고하라. 문제는 현대 브라우저가 페이지의 텍스트를 더 크게 만드는 두 가지 방법을 지원한다는 점이다:
CSS에서 font-size를 설정하면 두 번째 접근법은 사용할 수 없게 된다.
모든 것을 종합하면 이렇다. 페이지의 텍스트가 기본 상태에서 읽을 수 있으리라고 가정하지 말고, 여러 설정을 확인하라. 자유도를 줄이고 font-size의 의미를 고정하기 위해 font-size-adjust를 설정하라. 선택한 글꼴(또는 사용자의 기본 글꼴)과 기본 글꼴 크기 16px에서 결과가 괜찮아 보인다면, 거기서 끝이다. 그렇지 않다면 font-size를 더 큰 값으로 설정하라. 그 후에는 리더 모드에서도 페이지가 읽기 좋은지 확인하라.
나쁨:line-height
이름과 달리 line-height는 줄의 높이를 설정하지 않는다. 그것은 같은 글꼴로 설정된 글리프 연속 구간의 높이다. 모든 텍스트가 같은 글꼴일 때는 둘이 일치한다. 하지만 예를 들어 일부 단어를 monospace 글꼴로 설정하면, 놀랄 준비를 해야 한다. font-size-adjust는 상자 안의 글리프 크기는 고정해 주지만, 그 상대적 위치는 여전히 지정하지 않는다. 그래서 서로 다른 글꼴로 된 두 텍스트 구간이 기준선을 공유하도록 세로 정렬되면, 그들의 line-height line-box는 서로에 대해 이동한다. 하나는 아래로 튀어나오고, 다른 하나는 위로 튀어나온다. 전체 줄 높이는 합집합으로 계산되므로 기대보다 더 커진다. 이 효과에 대한 철저한 설명은 다음을 보라.
Deep dive CSS: font metrics, line-height and vertical-align
나쁨: 세로 리듬
이 문제 묶음을 충분히 오래 검색하다 보면, 언젠가는 세로 리듬이라는 아이디어를 만나게 된다. 제목, 이미지 등 무엇이 들어가더라도 서로 다른 문단의 줄들이 같은 상대 위치에 있도록 해야 한다는 주장이다. 마치 웹 페이지 뒤에 보이지 않는 줄 공책이 있는 것처럼 말이다. 내가 보기에는 이것은 순수한 주술이며 유용하지 않다. 만약 두 단 레이아웃을 한다면 양쪽의 줄이 맞아야 하겠지만, 한 단 레이아웃을 위해 그렇게까지 애쓸 이유는 없다(@chrismorgan에게 감사).
나쁨:word-break
플로우 레이아웃의 천재성은 그 동적 성질에 있다. 창을 좁히면 텍스트가 스스로 깔끔하게 줄바꿈되는 기술적 경이를 잠시 생각해 보면 감탄하게 된다. 오래 지속되는 인쇄 텍스트의 세계에서 그것을 처음 실현했을 때의 느낌은 분명 대단했을 것이다. 하지만 그 마법에도 한계는 있다. 줄은 공백이나 하이픈 분할 지점에서만 끊을 수 있다. 그리고 inline code나 URL 같은 긴 구간은 끊을 수 없을 수도 있다. 이것은 모바일 기기에서 넘침 문제를 일으키며, 대개 글을 게시한 후에야 눈치채게 된다. 이것을 고치는 단 하나의 요령은 없지만, 몇 가지 팁은 여기서 볼 수 있다: 자세한 내용은 Against Horizontal Scroll.
그리고 … 지금 기억나는 것은 이게 전부인가? 단순한 블로그를 만들 만큼의 HTML&CSS만 설명하면서도 margin에 짓눌리지 않게 해 주는 짧은 100페이지짜리 책을 누군가 써 달라는 내 요청을 다시 한 번 반복한다!