Tailwind에서 벗어나 CSS 구조화를 배우다
안녕하세요! 8년 전에 Tailwind를 발견한 이야기를 신나서 썼었어요.
그때 저는 CSS 코드를 어떻게 구조화해야 할지 정말 감도 못 잡고 있었는데요, 완전한 혼돈 덩어리와 Tailwind 중 하나를 골라야 한다면 당연히 Tailwind를 고르는 게 너무 행복했어요. 덕분에 자그마한 사이트들을 정말 많이 만들 수 있었고요!
지난 일주일 정도 몇몇 사이트를 Tailwind에서 좀 더 시맨틱한 HTML과 순수 CSS로 옮기는 작업을 했는데요, 정말정말 재미있고 흥미로웠어요. 그래서 배운 것들을 여기에 정리해 보려고 해요!
언제나처럼 저는 풀타임 프론트엔드 개발자가 아니라서, CSS 공부는 여러 해에 걸쳐 띄엄띄엄 해왔어요.
알고 보니 Tailwind가 정말 많은 걸 가르쳐 줬더라고요
CSS 구조화에 대해 고민하기 시작했을 때 처음에는 좀 겁이 났어요. CSS 구조를 잘 짜는 편이 아니니까요! 하지만 CSS 구조화에 대한 블로그 글들을 읽기 시작하면서(예를 들면 레이어의 캐스케이드 전체나 2024년에 나는 CSS를 이렇게 작성한다 같은 글들이요) 몇 가지를 깨달았어요.
- 모든 CSS 코드베이스에는 저마다 다양한 요소가 얽혀 있어요(레이아웃! 폰트! 색상! 공용 컴포넌트!)
- 이걸 각각 관리할 시스템이나 가이드라인이 없으면 금방 혼돈에 빠져 버려요
- Tailwind에는 이미 이런 것들을 위한 시스템이 있고, 저는 그 시스템을 이미 알고 있어요! 마음에 드는 시스템은 따라 해 보면 되겠더라고요
예를 들어 Tailwind에는 이런 것들이 있어요.
제가 이야기할 시스템들
앞으로 제 CSS 코드베이스의 몇 가지 측면과, 각각에 대해 어떤 규칙을 적용하고 싶은지에 대한 생각을 나눠 보려고 해요. 어떤 건 Tailwind에서 그대로 가져왔고, 어떤 건 그렇지 않고요.
- 리셋
- 컴포넌트
- 색상
- 폰트 크기
- 유틸리티 클래스
- 베이스
- 간격
- 반응형 디자인
- 빌드 시스템
1. 리셋
저는 그냥 Tailwind의 “preflight 스타일”을 tailwind.css 파일에 들어가서 앞부분 200줄 정도를 그대로 복사해 왔어요.
작업을 하면서 Tailwind의 CSS 리셋과 은근히 친해져 있다는 걸 느꼈어요. 예를 들어 Tailwind는 모든 요소에 box-sizing: border-box를 설정하잖아요(요소의 너비에 패딩이 포함되도록 하는 거죠).
* { box-sizing: border-box; }이런 설정 없이 CSS를 작성하려면 꽤 적응이 필요할 것 같아요. Tailwind 리셋에 있는 다른 것들, 예를 들어 html {line-height: 1.5;} 같은 것도 저는 무의식적으로 익숙해져 있어서 존재 자체를 잊고 있었을 거예요.
2. 컴포넌트
이제부터가 CSS의 대부분을 차지하는 부분이에요!
여기서 아이디어는 CSS를 “컴포넌트” 단위로 정리하는 거예요. Vue나 React 컴포넌트와 정신적으로는 통하는 방식이죠.(사이트에 자바스크립트가 전혀 없을 수도 있지만요)
기본적인 아이디어는 이래요.
- 각 “컴포넌트”는 고유한 클래스를 가진다
- 한 컴포넌트의 CSS가 다른 컴포넌트의 CSS를 절대 덮어쓰지 않는다
- 각 컴포넌트는 자신만의 CSS 파일을 가진다
그러면 한 컴포넌트의 CSS를 수정해도 다른 컴포넌트에서 뭔가 알 수 없이 망가지는 일이 없어요. 그리고 제가 실제로 수정하고 싶어지는 CSS의 80% 정도는 어차피 각 컴포넌트 파일에 들어 있으니까, 100줄짜리 컴포넌트를 수정할 때는 그 100줄만 생각하면 되는 거죠. 훨씬 생각하기 쉬워요.
예를 들어, 이런 HTML이 .zine이라는 “컴포넌트”일 수 있어요.
<figure class="zine horizontal">
<img src="whatever.jpg">
</figure>그리고 CSS는 중첩 선택자를 써서 대략 이런 모습이 돼요.
.zine {
...
&.horizontal {
...
}
&.vertical {
...
}
&:hover {
...
}
}웹 컴포넌트나 @scope처럼 컴포넌트끼리 간섭하지 않도록 프로그래밍적으로 강제하는 장치는 아직 마련하지 않았어요. 그래도 그냥 규칙을 정하고 최대한 지키려고 노력하는 것만으로도 훨씬 나아진 느낌이에요.
다음으로는 사이트 전체의 일관성을 유지하고 컴포넌트들이 서로 잘 어우러지도록 하는 규칙들에 대해 이야기해 볼게요!
3. 색상
colours.css에는 필요할 때마다 쓸 수 있는 변수들이 잔뜩 들어 있어요. 색상은 정말 어려운 문제라 이번 리팩터링에서 색상 사용 방식을 굳이 손대고 싶지 않아서 그대로 두었어요.
여기서 제가 지키려고 하는 유일한 규칙은 사이트에서 쓰는 모든 색상은 이 파일에 정리되어 있어야 한다는 거예요.
:root {
--pink: #fea0c2;
--pink-light: #F9B9B9;
--red: #f91a55;
--orange: rgb(222, 117, 31);
...
}4. 폰트 크기
Tailwind에서 좋았던 점 중 하나는 폰트 크기를 정할 때 “음, 텍스트를 좀 크게 하고 싶네”라고 생각하고 그냥 text-lg라고 쓰면 끝이라는 거였어요! 혹시 충분하지 않으면 xl이나 2xl을 쓰면 되고요. em을 써야 하는지 px인지 rem인지 고민할 필요가 없었죠.
그래서 Tailwind에서 가져온 변수들을 이렇게 정의해 두었어요.
--size-xs: 0.75rem;
--line-height-xs: 1rem;
--size-sm: 0.875rem;
--line-height-sm: 1.25rem;그러면 폰트 크기를 지정하고 싶을 때는 이렇게 할 수 있어요. Tailwind보다는 조금 장황하지만 지금은 만족하고 있어요.
h3 {
font-size: var(--size-lg);
line-height: var(--line-height-lg);
}5. 유틸리티
버튼처럼 여러 컴포넌트에 걸쳐 등장하는 요소들이 있어요. 저는 이런 것들을 “유틸리티”라고 부르고 있어요.
Tailwind에서 몇 가지 유틸리티 클래스를 그대로 가져왔어요(예를 들어 스크린 리더 사용자에게만 보여야 하는 요소를 위한 .sr-only 같은 클래스요).
이 부분은 규모가 아주 작고, 수정을 꽤 신중하게 하려고 해요.
6. 베이스
“베이스” 스타일은 제가 직접 정해서 사이트 전체에 적용하는 스타일이에요. 사이트 전체에 많은 스타일을 강제할 자신이 없어서 이 부분은 정말 작게 유지하려고 해요. 지금은 이 두 가지 정도가 괜찮다고 느껴지는데, <section> 쪽은 바꿀 수도 있어요.
/* put a 950px column in the middle of each <section> */
section {
--inner-width: 950px;
padding: 3rem max(1rem, (100% - var(--inner-width))/2);
}
a {
color: var(--orange);
}베이스 스타일은 상향식(bottom-up)으로 작업하는 게 저에게는 가장 쉬울 것 같아요. 처음에는 베이스 스타일에 거의 아무것도 넣지 않고 시작해서, 공통으로 쓰고 싶은 것들을 발견할 때마다 컴포넌트에서 베이스 스타일로 옮기는 식으로요.
7. 간격
패딩과 마진을 어떻게 관리할지에 대한 방법은 아직 완전히 정하지 못했어요. 다만 Tailwind에서처럼 보이는 대로 될 때까지 여기저기 마구 패딩과 마진을 넣던 방식보다는 좀 더 원칙 있게 해보려고는 하고 있어요.
지금은 바깥쪽 레이아웃 컴포넌트가 가능한 한 간격을 책임지도록 하려고 하고 있어요. 예를 들어 <section> 안에 간격을 두고 싶은 자식 요소들이 여러 개 있다면, 이렇게 해서 자식들 사이를 균등하게 띄울 수 있죠.
section > *+* {
margin-top: 1rem;
}참고한 블로그 글들이에요.
8. 반응형 디자인: 그리드를 더 많이 활용하기!
Tailwind에서 제가 하던 반응형 디자인 방식은 미디어 쿼리를 많이 쓰는 거였어요. Tailwind에는 md:text-xl 같은 문법이 있는데, “md 크기 이상에서는 text-xl 스타일을 적용한다”는 뜻이죠.
지금은 꽤 다른 방식을 시도하고 있어요. 브레이크포인트를 많이 쓰지 않아도 되는, 더 유연한 CSS 그리드 레이아웃을 만드는 거죠. 어렵지만 그리드로 뭘 할 수 있는지 배우는 게 정말 흥미롭고, 이건 Tailwind로는 할 수 없다고 생각하는 것의 좋은 예시이기도 해요.
예를 들어, 이렇게 auto-fit을 사용해서 큰 화면에서는 자동으로 2열, 작은 화면에서는 1열이 되도록 하는 방법을 배우고 있어요.
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 400px), max-content));
justify-content: center;또 grid-template-areas도 많이 썼는데, 정말 놀라운 기능이고 Tailwind에서는 쓸 수 없다고 생각해요.
참고한 자료예요.
- 미디어 쿼리 없는 반응형 그리드 레이아웃 - CSS Tricks
9. 빌드 시스템: esbuild
개발 중에는 빌드 시스템이 필요 없어요. 이제 CSS에는 이런 식으로 내장된 import 문이 있거든요.
@import "reset.css";
@import "typography.css";
@import "colors.css";그리고 이런 식의 내장 중첩 선택자도 있고요.
.page {
h2 { ...}
}원한다면 프로덕션용으로 CSS 파일을 번들링할 때 esbuild를 쓸 수 있어요. 대략 이런 식이죠.
esbuild style.css --bundle --loader:.svg=dataurl --loader:.woff2=file --outfile=/tmp/out.css저는 보통 CSS와 JS 빌드 시스템을 피하는 편이지만, esbuild는 웹 표준에 기반하고 있고 정적 Go 바이너리라서 쓰는 게 괜찮다고 느껴요.(2021년에 여기에 쓴 글에서 다뤘어요)
왜 Tailwind에서 벗어났나요?
몇몇 분이 왜 Tailwind에서 벗어나는지 물어보셨어요. 영향을 준 요인은 몇 가지가 있어요.
- Tailwind는 2018년 이후로 빌드 시스템에 훨씬 더 의존하게 됐어요. 요즘 버전의 Tailwind는 빌드 시스템 없이는 사용이 불가능한(?) 것 같아요. 그래서 저는 수년 동안 Tailwind v2를 써왔어요.(litewind라는 것도 있다고 하더라고요)
- 원래부터 Tailwind는 빌드 시스템과 함께 쓰도록 되어 있었지만, 저는 한 번도 그렇게 하지 않았어요. 그래서 제 프로젝트들에는 2.8MB짜리
tailwind.min.css파일(gzip하면 270K)이 많이 남아 있고, 좀 우스꽝스럽게 느껴졌어요. - Tailwind를 쓰기 시작했을 때보다 지금은 CSS를 훨씬 더 잘 다루게 됐어요
- 결국 Tailwind는 제약이 많아요. CSS로 뭔가 독특한 걸 하고 싶을 때 Tailwind로는 항상 가능한 게 아니거든요. 이런 제약이 엄청 유용할 때도 있지만(사실 이 글의 많은 부분이 Tailwind의 제약을 다시 구현하는 이야기예요!) 이제는 제가 직접 골라서 적용하고 싶어졌어요.
- 한 프로젝트 안에 순수 CSS와 Tailwind가 섞인 사이트들이 생겼는데, 유지보수하기가 전혀 즐겁지 않았어요
- 좀 더 시맨틱한 HTML을 작성하는 게 어떤 느낌일지 궁금해졌어요.
궁금한 CSS 기능들
이번 작업을 하면서 쓰지는 않았지만 언젠가 배워보고 싶은 CSS 기능들을 많이 알게 됐어요.
@layer(레이어의 캐스케이드 전체에서)- @scope (특히 명세서에 나온 “컴포넌트” CSS 디자인에서 @scope를 활용하는 예시!)
- 컨테이너 쿼리
- 서브그리드
Tailwind를 떠난 마지막 한 가지 이유
지금까지 이 글에서 Tailwind를 통해 배운 것들에 대해 많이 이야기했는데, 그건 다 사실이에요.
하지만 3년 전에 Tailwind와 CSS의 여성성이라는 글을 읽었는데 정말 오래 기억에 남았어요. 솔직히 저도 처음에는 그 글에서 묘사하는 것과 비슷한 태도로 CSS를 대했을 거예요.
그들은 CSS가 단순하다고 들었으니 쉽다고 생각한다. 하지만 막상 써 보면 제대로 되지 않는다. 그러면 언어 탓이라고 생각한다. 자신은 똑똑하고, 이건 쉬워야 하는 것이니까.
하지만 지난 10년 동안 저는 CSS라는 기술을 정말 사랑하고 존중하게 됐어요.
그래서 몇 년 전에 “CSS는 어렵다”는 말에 대해 CSS를 깔보는 대신, CSS를 더 잘하고 기술로서 진지하게 대하는 쪽으로 반응하기로 결심했어요. 그렇게 하니 모든 게 달라졌어요. 저를 좌절시켰던 많은 것들(“가운데 정렬은 불가능해” 같은)이 사실 오래전에 CSS에서 이미 해결되었다는 걸 알게 됐고, 또 “가운데 정렬”이 무엇을 의미하는지 자체가 항상 단순하지 않고 그래서 여러 방법이 있는 게 당연하다는 것도 알게 됐죠. CSS가 어려운 이유는 어려운 문제를 풀고 있기 때문이에요!
지난 10~15년 동안 만들어진 새로운 CSS 기능들(이 글에서 이야기한 것들도 일부 포함해서요!)과 그 기능들이 CSS를 얼마나 더 쓰기 쉽게 만드는지에 정말 감탄했고, CSS 실력을 키우는 데 시간을 쓴 것은 정말 멋진 경험이었어요.
그리고 그 글은 Tailwind가 CSS 전문성을 평가절하하는 데 기여한다는 느낌을 주었고, Tailwind가 개인적으로는 유용한 도구였더라도 저는 그런 흐름에 동참하고 싶지 않다고 느끼게 했어요. 특히 LLM 시대에는 인간의 전문성을 소중히 여기는 것이 그 어느 때보다 중요하게 느껴지고요.
저에게 영향을 준, Tailwind를 비판하는 또 다른 블로그 글도 있어요.
일단은 여기까지예요!
원래 wizardzines.com의 디자인과 CSS를 맡아 주신 Melody Starling님께 감사드려요. 그 사이트의 멋지고 재미있는 모든 것은 Melody 덕분이에요.
또 이번 작업을 하면서 CSS Tricks, Smashing Magazine 등에서 정말 놀라운 CSS 블로그 글들을 많이 읽었어요. 글 곳곳에 일부를 링크해 두었고, CSS 커뮤니티 분들이 자신들의 노하우를 이렇게 많이 공유해 주시는 것에 정말 감사하고 있어요.
글을 무작위로 읽기