LabHub
배우기 러닝패스 코스

CSS — 後に書いた規則が勝つ

勝つ規則を計算する

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

CSS 가 예측 불가능해 보이는 것은 규칙이 없어서가 아니라 규칙이 세 단계라서입니다. 중요도 → 특정성 → 순서.

브라우저가 없으니 "보기에 맞는가" 는 채점하지 않습니다. 대신 어떤 규칙이 이기는가 를 실제로 계산합니다 — 그건 브라우저 없이도 답이 하나입니다.

만들 것

/root/work/css/styles.css 하나입니다. HTML 은 필요 없습니다. 채점기가 아래 요소들을 가정하고 캐스케이드를 풉니다.

div#app > div.card
main.lessonLayout > aside.lessonSide
main.lessonLayout.sideHidden > aside.lessonSide
div.toolbar

계산기

cd /root/work/css
python3 /opt/lab/checks/css-lab/cascade.py styles.css

규칙마다 순서 · 미디어 조건 · 특정성을 보여 줍니다. 다루는 범위는 태그·클래스·아이디·의사클래스·자손/자식 결합자·콤마 그룹, 그리고 @media (max-width|min-width: Npx) 입니다. 그 밖의 선택자를 만나면 조용히 넘기지 않고 "해석할 수 없다" 고 말합니다.

단계

  1. :root 토큰 3개 + var() 3곳
  2. 특정성 예측 → 02-spec.txt
  3. !important 없이 이기기
  4. 순서 함정 (기본 규칙을 미디어 쿼리 로)
  5. 3칸 그리드
  6. 접으면 1칸
  7. 플렉스 툴바
  8. 계산기 → 08-cascade.txt
  9. 정리 → 09-notes.md

참고

4단계와 6단계는 이 서비스에서 실제로 났던 버그입니다. 모바일 목차 덮개가 안 열리던 것과, 목차를 접으면 페이지가 33,000px 로 늘어나던 것.

토큰을 :root 에 둔다

/root/work/css/styles.css 를 만들고 :root 에 커스텀 프로퍼티를 세 개 이상 정의한 뒤, var()세 곳 이상에서 쓰세요.

mkdir -p /root/work/css. 이름은 --accent, --gap, --fg 처럼 자유입니다. SCSS 변수와 달리 커스텀 프로퍼티는 런타임에 살아 있고 상속됩니다 — 그래서 하위 트리에서만 덮어쓸 수 있습니다.

특정성을 계산해 본다

아래 다섯 선택자의 특정성을 02-spec.txt번호=a,b,c 형식으로 한 줄씩 적으세요.

1) a
2) .btn.primary
3) #nav a:hover
4) ul > li::marker
5) :where(.card) .title

a=아이디, b=클래스·속성·의사클래스, c=태그·의사요소입니다. 예: 1=0,0,1. 5번이 함정입니다 — :where() 는 항상 0으로 셉니다. 자리끼리 넘어가지 않는다는 것도 기억하세요: 클래스 11개도 아이디 하나를 못 이깁니다.

!important 없이 이긴다

.card { color: ... } 를 먼저 쓰고, #app 안의 .card 만 다른 색이 되게 하세요. !important 를 쓰면 안 됩니다.

채점기가 div#app > div.card 요소에 대해 어떤 규칙이 이기는지 실제로 계산합니다. #app .card1,1,0 이라 .card(0,1,0)를 이깁니다. !important 는 또 다른 !important 로만 이길 수 있어서, 한 번 쓰면 그 속성을 영원히 그렇게 다뤄야 합니다.

순서 함정을 피한다

.lessonSideposition 을 기본은 sticky, max-width: 900px 에서는 fixed 가 되게 하세요. 두 규칙의 특정성은 같으니 순서가 답을 정합니다.

기본 규칙을 @media 블록 에 두세요. 아래에 두면 특정성이 같으므로(둘 다 0,1,0) 나중에 쓴 기본 규칙이 모바일에서도 이깁니다. 채점기가 1280px 와 500px 두 폭에서 각각 계산합니다. 이 서비스에서 모바일 덮개가 안 열리던 버그가 정확히 이것이었습니다.

골격을 그리드로

.lessonLayoutdisplay: grid 로 만들고 세 칸짜리 grid-template-columnsgap 을 주세요.

예: grid-template-columns: 240px 1fr 200px. 그리드는 행과 열을 둘 다 정하고 싶을 때 씁니다. 한 줄 안에서 나누는 것뿐이라면 플렉스가 맞습니다.

접으면 칸도 없어져야 한다

.lessonLayout.sideHidden 일 때 칸이 하나가 되게 하세요. 0 1fr 200px 처럼 0짜리 칸을 남기면 안 됩니다.

자식이 display: none 이면 그리드에서 아예 빠집니다. 칸을 0으로 만들어 두면 본문이 그 0px 칸으로 밀려 들어가 글자마다 줄이 바뀌고, 실제로 페이지가 33,000px 이 됐습니다. 칸의 개수를 줄이세요 — grid-template-columns: 1fr.

툴바는 플렉스로

.toolbardisplay: flex 로 만들고 justify-content, align-items, gap 을 모두 지정하세요.

가장 자주 쓰는 조합은 space-between + center + gap 셋입니다. 하나만 오른쪽으로 밀고 싶으면 그 요소에 margin-left: auto 를 주는 방법도 있습니다.

계산기로 확인한다

/opt/lab/checks/css-lab/cascade.py 로 여러분의 스타일시트를 풀어 결과를 08-cascade.txt 로 남기세요.

python3 /opt/lab/checks/css-lab/cascade.py styles.css | tee 08-cascade.txt. 규칙마다 순서·미디어 조건·특정성이 나옵니다. 개발자 도구에서 취소선이 그어진 규칙을 볼 때 머릿속에서 하는 계산이 이것입니다.

왜 졌는지 설명한다

09-notes.md 에 세 줄 이상. 4단계에서 순서를 뒤집으면 무슨 일이 생기는지, 6단계에서 0짜리 칸을 남기면 무슨 일이 생기는지, 그리고 !important 를 쓰고 싶어질 때 먼저 볼 것.

본문에 특정성, 순서, 그리드 가 들어가야 합니다. 진단은 늘 네 단계입니다 — 선택자가 맞는가, 지고 있는가, 특정성인가 순서인가, 상속을 기대하고 있는가.