DevInsight

나중에 다시 보려고, AI로 정리해두는 기술 기록

Frontend
조회 07분 읽기

예쁜 코드보다 빨리 살아남는 UI가 필요한 순간

Tailwind를 둘러싼 호불호를 단순한 취향 싸움으로 보지 않고, 왜 많은 팀이 결국 utility-first로 기울어지는지 추적한다. CSS의 장인정신, 팀 생산성, 재사용성, 접근성 사이의 실제 긴장을 현장감 있게 풀어낼 글이다.

#Tailwind CSS#CSS#Frontend#UI Engineering#Utility First#Design Systems#DX#Accessibility

Tailwind를 둘러싼 논쟁이 오래가는 이유는 이 도구가 단순히 CSS 문법을 줄여주는 보조 수단이 아니라, 프론트엔드 팀이 UI를 다루는 방식 자체를 바꿔버리기 때문이다.

한쪽에서는 class가 너무 길어져서 마크업이 지저분해진다고 말한다. 다른 쪽에서는 그 지저분함 덕분에 오히려 살아남는 화면이 많다고 답한다. 이 충돌은 취향의 문제가 아니다. 화면을 예쁘게 만드는 일과, 화면을 망가지지 않게 운영하는 일이 같은 층위의 문제가 아니기 때문이다. 작은 개인 사이트와 여러 팀이 동시에 만지는 서비스는 같은 CSS를 써도 전혀 다른 압력을 받는다. Tailwind는 그 압력의 방향을 아주 노골적으로 드러내는 도구다.

예쁜 구조보다 먼저 깨지는 것은 속도다

UI가 무너지는 순간은 대개 미학이 실패해서가 아니다. 출시 일정이 앞당겨지고, 컴포넌트가 급하게 복제되고, 누군가가 기존 class를 덮어쓰기 시작할 때다. 처음에는 다들 질서 있게 출발한다. semantic한 class name을 만들고, 공통 버튼 스타일을 분리하고, 재사용 가능한 layout 규칙을 설계한다. 문제는 설계가 틀려서가 아니라, 설계가 시간이 지나며 코드와 멀어진다는 데 있다.

CSS는 원래 멀리서 영향을 준다. 지금 손대는 선택자가 어떤 화면까지 닿는지 한눈에 보이지 않는다. 같은 .card라는 이름 아래 전혀 다른 책임이 눌어붙기도 하고, 특정 페이지에서만 덧댄 예외 규칙이 몇 달 뒤 공통 스타일을 오염시키기도 한다. 프론트엔드 팀이 커질수록 CSS의 난점은 문법보다 거리에서 나온다. 선언은 한 파일에 있는데 결과는 열 개의 화면에서 나타난다. 수정은 한 줄인데 회귀는 예측할 수 없다.

Tailwind가 사랑받는 지점은 바로 여기다. 스타일의 영향 반경을 줄인다. CSS를 잘하게 만들어준다기보다, CSS가 멀리 새어 나가지 못하게 만든다. px-4 py-2 rounded-md bg-blue-600 같은 표현은 우아하지 않을 수 있다. 하지만 그 불편할 정도의 직설성이 팀에게는 안전장치가 된다. 무엇이 적용되는지 숨기지 않기 때문이다.

utility-first가 사실상 운영 전략이 되는 순간

많은 팀이 utility-first에 기울어지는 이유를 생산성 한 단어로 설명하면 중요한 맥락이 빠진다. Tailwind의 핵심 가치는 빠르게 만든다는 데만 있지 않다. 빠르게 바꾸고, 바꾼 뒤 무슨 일이 벌어질지 더 좁은 범위에서 생각하게 만든다는 데 있다.

이 차이는 리뷰에서 먼저 보인다. 전통적인 CSS 구조에서는 JSX나 template 변경과 CSS 변경을 함께 읽어야 한다. class name이 바뀌었는데 실제 시각적 의미는 다른 파일을 열어야 드러난다. CSS Modules나 Sass가 아무리 잘 정리돼 있어도, 코드 리뷰어는 결국 머릿속에서 “이 이름이 어떤 스타일이지?”를 한 번 더 해석해야 한다. 반면 utility class는 의미를 숨기지 않는다. padding이 늘었는지, breakpoint가 추가됐는지, 색 대비가 낮아졌는지 마크업 바로 옆에서 읽힌다.

이것은 단순한 편의성의 문제가 아니다. 팀의 공용 언어가 바뀌는 일이다. “이 버튼은 primary니까 btn-primary를 쓰자”에서 끝나는 것이 아니라, “모바일에서는 w-full, 데스크톱에서는 w-auto가 맞는가”, “focus ring이 충분히 보이는가”, “disabled 상태의 대비가 기준을 넘는가” 같은 더 물리적인 질문을 하게 된다. Tailwind는 CSS의 추상도를 낮추는 대신, 리뷰의 해상도를 높인다.

그래서 utility-first는 자주 DX 이야기로 소비되지만, 실제로는 운영 문제에 가깝다. 많은 화면을 빨리 만들어야 하는 팀일수록 아름다운 class taxonomy보다 명시적인 지역성(locality)을 택한다. 이름을 잘 짓는 능력보다, 영향 범위를 좁히는 구조가 더 높은 우선순위를 갖게 된다.

class가 길다는 불만은 맞다. 그런데 그다음이 중요하다

Tailwind를 싫어하는 이유로 가장 자주 거론되는 것이 “HTML이 너무 더러워진다”는 지점이다. 이 비판은 정당하다. 긴 class 문자열은 처음 보는 사람을 피곤하게 만들고, 화면 구조를 읽는 시선을 가로막기도 한다. 특히 복잡한 상태 조합이 들어간 컴포넌트는 hover, focus-visible, data-*, dark, md, aria-* 변형까지 겹치며 한 줄이 지나치게 길어진다.

하지만 여기서 멈추면 절반만 본 셈이다. 긴 class가 불편한 이유와, 분산된 CSS가 위험한 이유는 서로 다른 문제다. 전자는 읽기 비용이고, 후자는 변경 비용이다. 읽기 비용은 도구와 습관으로 완화할 수 있다. 줄바꿈 규칙을 정하고, variant 정렬 규칙을 맞추고, 컴포넌트 경계를 분명히 하면 꽤 나아진다. 반면 변경 비용은 시간이 쌓일수록 폭발한다. 특정 class name이 어디까지 사용되는지 추적해야 하고, 선택자 우선순위를 다시 계산해야 하며, 의도치 않은 side effect를 테스트로 메워야 한다.

결국 팀은 묻게 된다. 어떤 불편을 감수할 것인가. 파일을 오가며 숨은 의미를 해석하는 불편인가, 아니면 마크업 안에서 다소 시끄러운 스타일 정보를 직접 읽는 불편인가. Tailwind를 선택한 팀은 대개 후자를 더 값싼 비용으로 본다. 깔끔한 마크업을 포기해서가 아니라, 미래의 회귀 비용이 더 비싸기 때문이다.

재사용성은 줄어드는가, 오히려 강제되는가

Tailwind 비판에서 자주 나오는 또 하나의 주장은 재사용성이 무너진다는 것이다. 모든 요소에 utility를 덕지덕지 붙이면 결국 복붙만 늘고, 의미 있는 abstraction이 사라진다는 논리다. 이것도 절반은 맞고 절반은 틀리다.

틀린 부분부터 말하면, Tailwind 자체가 재사용성을 막는 것은 아니다. 같은 utility 묶음을 여러 곳에서 반복하기 시작하면 사람은 금세 피로를 느낀다. 그 시점부터 버튼, 배지, 입력 필드, 카드 헤더 같은 패턴을 컴포넌트로 끌어올리게 된다. 즉 재사용은 class name 레벨이 아니라 컴포넌트 레벨에서 다시 일어난다. 많은 팀이 Button, Input, Dialog 같은 primitives를 두고 variant를 props로 제어하는 이유가 여기에 있다.

맞는 부분은 따로 있다. Tailwind는 재사용의 타이밍을 늦춘다. 처음부터 추상적인 이름을 설계하지 않게 만들고, 반복이 충분히 드러날 때까지 복제를 허용한다. 이것은 장점이기도 하고 위험이기도 하다. 너무 이른 추상화가 낳는 억지 공통화는 피할 수 있지만, 너무 늦은 추상화는 유사한 컴포넌트가 여러 갈래로 분화되는 결과를 부른다. 그러니 utility-first 환경에서는 “재사용을 어떻게 설계할 것인가”보다 “언제 추상화할 것인가”가 더 중요한 질문이 된다.

좋은 팀은 여기서 균형을 잡는다. 페이지 수준에서는 utility의 속도를 누리고, 반복이 쌓이는 경계에서는 component API를 만든다. 나쁜 팀은 둘 중 하나만 한다. 모든 걸 즉시 컴포넌트로 만들다가 과도한 추상화에 빠지거나, 끝까지 utility만 붙이다가 스타일 변형의 조합 폭발을 맞는다.

접근성은 도구가 아니라 태도의 문제지만, 도구는 태도를 유도한다

Tailwind를 두고 접근성이 나빠진다고 말하는 사람도 있다. 엄밀히 말하면 부정확한 비판이다. 어떤 styling 방식도 semantic한 마크업과 keyboard interaction, focus management, 충분한 color contrast를 자동으로 보장해주지 않는다. plain CSS로도 접근성은 얼마든지 망가질 수 있다.

그런데 이 이야기를 여기서 끝내면 현실을 놓친다. 도구는 태도를 유도한다. Tailwind는 시각적 조정을 아주 빠르게 만들어준다. 그래서 잘못 쓰면 “버튼처럼 보이는 div”를 양산하기 쉬워진다. class 몇 개만 붙이면 어떤 요소든 그럴듯한 모양을 낼 수 있으니, 의미를 지키는 습관이 약한 팀에서는 시맨틱이 뒤로 밀린다. 반대로 잘 쓰면 접근성 개선 속도도 빨라진다. focus-visible:ring-2, disabled:opacity-50, aria-[expanded=true]:rotate-180 같은 상태 기반 표현이 마크업 가까이 있기 때문에, 접근성 요구사항을 구현과 함께 검토하기가 수월하다.

결국 중요한 것은 Tailwind가 접근성을 해결하느냐가 아니다. 접근성 요구사항이 컴포넌트 API와 review checklist 안에 들어가 있느냐다. utility-first는 시각 스타일의 마찰을 줄여주기 때문에, 잘만 운용하면 더 많은 에너지를 interaction과 semantics에 쓸 수 있다. 반대로 아무 기준 없이 도입하면 “쉽게 예뻐지는 도구”로만 소비된다. 책임은 언제나 설계와 운영에 있다.

디자인이 비슷해지는 이유는 프레임워크보다 결정의 빈곤에 있다

Tailwind를 쓰면 서비스가 다 비슷해 보인다는 말도 자주 듣는다. 이것 역시 어느 정도 사실이다. 기본 spacing scale, rounded corner, neutral palette, 익숙한 breakpoint 체계 위에서 빠르게 조합하다 보면 현대적인 SaaS 화면들이 서로 닮아가기 쉽다.

하지만 이 현상을 Tailwind 탓으로만 돌리면 편하다. 실제로는 대부분의 제품이 차별화된 시각 실험보다 예측 가능한 사용성을 먼저 선택한다. 많은 팀은 brand expression보다 delivery certainty를 우선한다. Tailwind는 그 선택을 더 빠르게 수행하게 해줄 뿐이다. 비슷한 화면이 나오는 근본 원인은 도구보다 의사결정에 있다. 디자인 토큰이 빈약하고, 타이포그래피 기준이 없고, 상태 설계가 얕고, motion과 density에 대한 팀의 언어가 없다면 무엇으로 짜도 평범해진다.

반대로 Tailwind 환경에서도 충분히 개성 있는 UI는 가능하다. 핵심은 utility를 그대로 쓰느냐가 아니라, design token과 component layer를 어떻게 정의하느냐다. 색상, 여백, radius, 그림자, type scale을 팀의 언어로 조정하고, 의미 있는 primitive를 두면 utility는 오히려 구현의 마찰을 줄이는 기반이 된다. Tailwind는 미학을 대신하지 않는다. 미학이 부재한 조직에서 그 빈 공간을 드러낼 뿐이다.

CSS 장인정신은 사라지는가, 역할이 이동하는가

오랫동안 CSS를 깊게 다뤄온 사람일수록 Tailwind에 대한 거부감이 있는 경우가 많다. 좋은 class name을 짓고, cascade를 세심하게 다루고, selector 설계를 통해 UI를 조직하는 능력은 분명 숙련의 결과다. 이 감각은 가볍지 않다. 문제는 그 장인정신이 오늘의 제품 개발 환경에서 어디에 쓰이느냐다.

지금 많은 팀이 필요로 하는 것은 “얼마나 우아하게 CSS를 구성했는가”보다 “얼마나 쉽게 바꾸고 되돌릴 수 있는가”다. 이때 장인정신은 사라지는 것이 아니라 이동한다. 긴 utility를 보고도 semantic structure를 무너지지 않게 컴포넌트를 나누는 감각, 토큰을 엉성하게 늘리지 않고 scale을 유지하는 감각, 상태가 많아져도 variant 설계를 단순하게 유지하는 감각, 그리고 무엇보다 접근성과 responsive behavior를 스타일 수준에서 함께 다루는 감각이 새 숙련으로 등장한다.

즉 CSS를 잘 안다는 것은 더 이상 예쁜 stylesheet를 만든다는 뜻에 머물지 않는다. styling abstraction을 어디까지 올리고 어디서 멈출지 판단하는 능력, 시각 규칙과 제품 속도를 함께 관리하는 능력으로 옮겨간다. Tailwind는 이 이동을 불편할 정도로 선명하게 드러낸다.

운영에서 먼저 보이는 신호들

Tailwind 도입 여부를 추상적 신념으로 결정하면 대개 오래 간다. 실제로는 운영 신호를 보는 편이 훨씬 정확하다. 새 화면 추가 속도가 점점 늦어지는가. CSS 변경 리뷰가 불안해서 QA 기간이 길어지는가. 공통 컴포넌트가 있는데도 팀마다 미세하게 다른 버튼과 폼이 생기는가. 반응형 수정 하나가 예상 밖 회귀를 자주 만드는가. 디자이너가 낸 작은 spacing 조정이 생각보다 큰 작업으로 번지는가.

이런 신호가 뚜렷하다면 Tailwind는 충분히 현실적인 해법이 된다. 반대로 제품이 적고 화면 수명이 길며, 강한 브랜드 아이덴티티와 세밀한 모션, 독창적 layout이 핵심 경쟁력이라면 utility-first가 오히려 창작의 흐름을 방해할 수 있다. 복잡한 styling logic를 계속 마크업에 붙들어두는 일이 부담으로 돌아오기도 한다.

중요한 것은 옳고 그름이 아니라 비용 구조다. Tailwind는 CSS 비용을 없애지 않는다. 비용의 위치를 바꾼다. selector 설계와 전역 영향 관리에 쓰이던 에너지를 component composition, token governance, variant discipline으로 옮긴다. 이 이동이 팀에 맞으면 강력하고, 맞지 않으면 금세 피로해진다.

빨리 살아남는 UI라는 관점

프론트엔드에서 살아남는 UI는 처음부터 가장 세련된 UI와 다르다. 요구사항이 몇 번 바뀌어도 금이 덜 가고, 다른 사람이 건드려도 사고가 적고, 상태가 늘어나도 설명 가능한 구조를 유지하는 UI에 가깝다. Tailwind가 매력적인 이유는 화면을 완성품처럼 다루게 하기보다, 계속 수선될 물건처럼 다루게 만들기 때문이다.

이 관점은 다소 냉정하다. 낭만이 부족해 보일 수도 있다. 하지만 서비스 환경에서는 이 냉정함이 자주 필요하다. 제품은 정적인 결과물이 아니라 지속적으로 교체되는 계약의 집합이다. 디자인도, 기능도, 팀도 바뀐다. 그 안에서 utility-first가 주는 장점은 “더 아름다운 CSS”가 아니라 “덜 위험한 변경”이다.

그래서 Tailwind에 대한 호불호는 앞으로도 쉽게 끝나지 않을 것이다. 어떤 사람에게 CSS는 표현의 언어이고, 어떤 사람에게는 복잡도를 관리하는 장치다. 다만 한 가지는 분명하다. 많은 팀이 결국 utility-first로 기울어지는 이유는 게으름이 아니라 생존 본능에 가깝다. 예쁜 코드가 아니라 오래 버티는 화면이 필요할 때, 그 선택은 꽤 자주 합리적이다.

마지막에 남는 질문은 이것이다. 지금 필요한 것은 더 순수한 CSS인가, 아니면 더 적은 회귀와 더 빠른 합의인가. Tailwind를 좋아하든 싫어하든, 이 질문 앞에서는 누구나 취향보다 운영을 먼저 생각하게 된다. 그리고 바로 그 순간부터 논쟁은 미감이 아니라 엔지니어링의 문제가 된다.

댓글

댓글을 읽어오는 중입니다.

같이 읽으면 좋은 글

방금 읽은 주제와 이어지는 글을 골랐습니다.

Frontend 전체 보기

이전 글

유틸리티 클래스가 프론트엔드 팀을 구하는 순간