본문으로 건너뛰기

37signals의 노빌드(No-Build) CSS 전략: 모던 CSS의 진화와 실용성

Vanilla CSS is all you need

작성자
발행일
2025년 12월 03일

핵심 요약

  • 1 37signals는 Campfire, Writebook, Fizzy에서 Sass, PostCSS, 빌드 도구 없이 바닐라 CSS만으로 정교한 웹 애플리케이션을 구축합니다.
  • 2 현대 CSS는 변수, 중첩, :has(), color-mix(), @layer, @starting-style 등 강력한 내장 기능을 제공하여 복잡한 빌드 단계를 대체할 수 있음을 입증합니다.
  • 3 간결한 파일 구조, OKLCH 색상 시스템, ch 단위 간격, :has() 선택자 활용, CSS Layers 도입 등 진화하는 패턴을 통해 효율적이고 유지보수하기 쉬운 CSS 아키텍처를 제시합니다.

도입

2024년 4월, 37signals의 Jason Zimdars는 Campfire의 모던 CSS 패턴에 대한 게시물을 통해 빌드 도구 없이 바닐라 CSS만으로 정교한 웹 애플리케이션을 구축하는 방식을 설명했습니다. 지난 1년 반 동안 37signals는 동일한 '노빌드(nobuild)' 철학으로 Writebook과 Fizzy를 출시했으며, 본 글은 이러한 CSS 패턴이 어떻게 진화하고 발전했는지 분석합니다. 저자는 Campfire, Writebook, Fizzy의 소스 코드를 분석하며, 일관성을 유지하면서도 점진적으로 현대 CSS 기능을 채택하는 37signals의 접근 방식에 놀라움을 표합니다. 이들은 단순한 취미 프로젝트가 아닌, 총 14,000라인에 달하는 방대한 CSS 코드를 단 하나의 빌드 도구도 사용하지 않고 관리하고 있습니다.

37signals의 CSS 아키텍처는 놀랍도록 단순합니다. app/assets/stylesheets/ 아래에 _reset.css, base.css, colors.css, utilities.css, buttons.css, inputs.css 등 개념별로 하나의 파일이 존재하며, 서브디렉토리나 복잡한 임포트 트리가 없습니다. 이는 제로 설정, 제로 빌드 시간, 제로 대기 시간을 목표로 합니다.

색상 시스템: 일관된 기반, 진화하는 기능

모든 애플리케이션은 지각적으로 균일한 색상 공간인 OKLCH를 사용합니다. 루트 변수로 정의된 LCH 값(--lch-blue, --lch-red, --lch-green)을 기반으로 --color-link, --color-negative와 같은 시맨틱 색상을 정의하며, 다크 모드는 @media (prefers-color-scheme: dark) 내에서 LCH 값을 조정하는 것만으로 쉽게 구현됩니다. Fizzy에서는 color-mix()를 활용하여 --card-color 변수 하나로 --card-bg, --card-text, --card-border와 같은 조화로운 색상 팔레트를 동적으로 생성합니다.

간격 시스템: 픽셀 대신 문자(ch) 단위

수평 간격에 ch 단위를 사용하는 것이 특징입니다. 1ch는 문자 하나의 너비에 해당하여 콘텐츠와 자연스럽게 연관됩니다. 이는 폰트 크기 조정 시 간격도 비례하여 조정되게 하며, 반응형 브레이크포인트도 (min-width: 100ch)와 같이 콘텐츠 기반의 의미론적 접근을 가능하게 합니다.

유틸리티 클래스: 보조적 역할

37signals 애플리케이션도 .flex, .gap, .pad와 같은 유틸리티 클래스를 사용하지만, Tailwind와 달리 이러한 유틸리티는 핵심 스타일링이 아닌 예외적인 상황이나 일회성 조정을 위한 보조적인 역할을 합니다. 핵심 스타일은 시맨틱 컴포넌트 클래스(btn)에 정의되어 HTML 가독성을 높이고, 변경 사항이 쉽게 전파되며, 미디어 쿼리 및 호버 상태가 컴포넌트와 함께 관리됩니다.

:has() 혁명

:has() 선택자는 JavaScript 없이 부모 요소를 자식 요소의 상태에 따라 스타일링할 수 있게 하여, 사이드바 토글, 칸반 컬럼 레이아웃 조정, 아이콘 버튼 스타일링 등 복잡한 상태 관리와 조건부 렌더링을 CSS만으로 구현합니다.

아키텍처의 점진적 진화

  • Campfire: OKLCH 색상, 커스텀 속성, 문자 기반 간격, 플랫 파일 조직, View Transitions API를 확립했습니다.

  • Writebook: 컴포넌트 수준 반응형을 위한 Container Queries, 진입 애니메이션을 위한 @starting-style을 추가했습니다.

  • Fizzy: 특수성 관리를 위한 CSS Layers (@layer), 동적 색상 파생을 위한 color-mix(), JavaScript 상태를 대체하는 복잡한 :has() 체인을 도입하여 현대 CSS 기능을 최대한 활용했습니다.

특별한 기술들

  • 로딩 스피너: 이미지나 SVG, JavaScript 없이 CSS 마스크와 @keyframes 애니메이션으로 세 개의 점이 순차적으로 움직이는 스피너를 구현합니다. currentColor를 사용하여 부모의 텍스트 색상을 자동으로 상속받습니다.

  • 향상된 <mark>: 기본 <mark> 대신 border-radius를 비대칭적으로 사용하여 손으로 그린 듯한 원형 하이라이트를 구현합니다. mix-blend-mode를 통해 배경과의 자연스러운 블렌딩을 제공합니다.

  • Dialog 애니메이션: @starting-styleallow-discrete를 활용하여 HTML <dialog> 요소의 부드러운 진입 및 종료 애니메이션을 순수 CSS로 구현합니다. 이는 display: nonedisplay: block 간의 전환을 애니메이션할 수 있게 합니다.

결론

37signals의 사례는 개발자들이 빌드 도구에 대한 기존의 가정을 재고해야 함을 시사합니다. 현대 CSS는 변수, 중첩, `color-mix()`, `:has()` 등 강력한 내장 기능을 제공하여 Sass나 PostCSS 없이도 충분히 복잡한 요구사항을 충족할 수 있습니다. 모든 프로젝트에 Tailwind가 필수적인 것은 아니며, 특히 CSS에 대한 이해도가 높은 팀이라면 자체 디자인 시스템을 구축하는 것이 더 효율적일 수 있습니다. 업계가 점점 더 복잡한 툴체인으로 향하는 동안, 37signals는 더 단순한 방향으로 나아가며, 많은 프로젝트에서 '더 단순한 것이 더 좋다'는 원칙이 유효함을 보여줍니다. 37signals의 Campfire, Writebook, Fizzy 코드베이스는 현대 CSS 학습을 위한 훌륭한 자료가 될 것입니다.

댓글 0

댓글 작성

댓글 삭제 시 비밀번호가 필요합니다.

이미 계정이 있으신가요? 로그인 후 댓글을 작성하세요.

0/1000
정중하고 건설적인 댓글을 작성해 주세요.