본문 바로가기

외부 스타일 시트와 선택자 실습

외부 CSS 파일에 다양한 선택자를 적용해 구조와 표현이 분리된 웹 페이지를 완성합니다.

앞 글에서 CSS의 문법과 선택자를 익혔습니다. 이 글에서는 외부 CSS 파일 하나에 여러 종류의 선택자를 써서 간단한 페이지를 꾸며 보고, 같은 요소에 여러 규칙이 겹칠 때 어느 규칙이 이기는지 명시도로 계산해 봅니다.


실습: 외부 스타일 시트와 선택자

web-dev-practice 폴더에 다음 구조로 파일을 준비합니다. 개발 환경 설정에서 만든 index.html이 이 폴더에 있다면 이 실습의 index.html이 그 파일을 덮어쓰므로, 먼저 그 파일의 이름을 setup.html처럼 바꿔 둡니다. css 폴더를 만들고 그 안에 style.css를 둡니다.

index.html
style.css

index.html에는 다음 코드를 씁니다. 메뉴의 href="#"는 같은 문서를 가리키므로, “외부 링크”라는 글자가 있어도 실제로 다른 사이트로 가지는 않습니다. 자세히 알아보기 버튼과 문의 폼도 모양만 있고 실제 동작이나 저장 기능은 없습니다.

index.html
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CSS 기본 문법과 선택자 실습</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <header id="main-header">
        <h1 class="page-title">내 웹사이트</h1>
        <nav>
            <ul>
                <li class="menu-item"><a href="#">홈</a></li>
                <li class="menu-item"><a href="#">소개</a></li>
                <li class="menu-item"><a href="#" target="_blank">외부 링크</a></li>
                <li class="menu-item"><a href="#">연락처</a></li>
            </ul>
        </nav>
    </header>

    <main>
        <section class="content-section">
            <h2>환영합니다!</h2>
            <p>이곳은 CSS 선택자 연습을 위한 <span class="highlight">샘플 페이지</span>입니다.
                다양한 요소에 CSS가 어떻게 적용되는지 확인해 보세요.</p>
            <button class="call-to-action">자세히 알아보기</button>
        </section>

        <section class="content-section">
            <h3>CSS 학습 리스트</h3>
            <ul>
                <li>기본 문법</li>
                <li>선택자</li>
                <li>속성</li>
                <li>레이아웃</li>
            </ul>
            <p class="small-text">이 목록은 CSS로 스타일링되었습니다.</p>
        </section>

        <section class="form-section">
            <h3>문의하기</h3>
            <form>
                <label for="name">이름:</label>
                <input type="text" id="name" name="name" placeholder="이름을 입력하세요" required><br>

                <label for="email">이메일:</label>
                <input type="email" id="email" name="email" placeholder="[email protected]"><br>

                <button type="submit">문의 제출</button>
            </form>
        </section>
    </main>

    <footer>
        <p>&copy; 2026 웹 개발 학습. 모든 권리 보유.</p>
        <p class="small-text">CSS 기본을 배우는 중입니다.</p>
    </footer>
</body>
</html>

css/style.css에는 다음 CSS를 씁니다. 주석마다 어떤 종류의 선택자인지 적어 두었습니다.

css/style.css
/* 1. 전체 선택자: 기본 여백 제거 */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box; /* padding과 border가 요소의 너비/높이에 포함되도록 설정 */
}

/* 2. 태그 선택자: 기본 폰트 및 배경 색상 */
body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: #f0f2f5;
    color: #333;
    line-height: 1.6;
}

/* ID 선택자: 헤더 스타일링 */
#main-header {
    background-color: #2c3e50;
    color: white;
    padding: 20px 0;
    text-align: center;
}

/* 하위 선택자: 헤더 내부의 h1 */
#main-header .page-title {
    font-size: 2.5em;
    margin-bottom: 10px;
    color: #ecf0f1;
}

/* 태그 선택자: 내비게이션 ul 초기화 */
nav ul {
    list-style: none; /* 목록 기호 제거 */
}

/* 자식 선택자: nav 바로 아래 ul의 자식 li */
nav > ul > li {
    display: inline-block; /* 메뉴 항목을 가로로 배열 */
    margin: 0 15px;
}

/* 클래스 선택자: 메뉴 아이템 링크 */
.menu-item a {
    color: #f1c40f; /* 노란색 */
    text-decoration: none; /* 밑줄 제거 */
    font-weight: bold;
    transition: color 0.3s ease; /* 호버 효과를 부드럽게 */
}

/* 가상 클래스 선택자: 링크 호버 시 */
.menu-item a:hover {
    color: #f39c12; /* 오렌지색 */
}

/* 속성 선택자 + 가상 요소: target="_blank"인 링크 뒤에 화살표 추가 */
a[target="_blank"]::after {
    content: " ↗";
    font-size: 0.8em;
    vertical-align: top;
    color: #f1c40f;
}

/* 메인 콘텐츠 영역 */
main {
    max-width: 900px;
    margin: 20px auto;
    padding: 20px;
    background-color: white;
    border-radius: 8px;
    box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

/* 클래스 선택자: 섹션 공통 스타일 */
.content-section {
    margin-bottom: 30px;
    padding: 20px;
    border: 1px solid #e0e0e0;
    border-radius: 5px;
    background-color: #ffffff;
}

/* 하위 선택자: .content-section 내부의 h2 */
.content-section h2 {
    color: #2980b9;
    margin-bottom: 15px;
    border-bottom: 2px solid #2980b9;
    padding-bottom: 5px;
}

/* 클래스 선택자: 하이라이트 텍스트 */
.highlight {
    background-color: #ffeaa7; /* 연한 노란색 */
    padding: 2px 5px;
    border-radius: 3px;
    font-weight: bold;
}

/* 태그 + 클래스 선택자: 버튼 스타일 */
button.call-to-action {
    background-color: #27ae60;
    color: white;
    padding: 12px 25px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font-size: 1.1em;
    transition: background-color 0.3s ease;
    margin-top: 20px;
}
button.call-to-action:hover {
    background-color: #229954;
}

/* 하위 선택자: 섹션 안 목록의 li */
.content-section ul li {
    list-style-type: square; /* 사각형 기호 */
    color: #555;
    margin-left: 20px;
}

/* 가상 클래스 선택자: 짝수 번째 li */
.content-section ul li:nth-child(even) {
    background-color: #f9f9f9;
}

/* 인접 형제 선택자: input 바로 뒤의 button */
input + button {
    margin-left: 10px; /* input과 버튼 사이 여백 */
}

/* 폼 섹션 스타일 */
.form-section {
    background-color: #ebf5fb;
}
.form-section label {
    display: block; /* 레이블을 블록 요소로 만들어 줄바꿈 */
    margin-bottom: 5px;
    font-weight: bold;
    color: #444;
}
.form-section input[type="text"],
.form-section input[type="email"] {
    width: calc(100% - 120px); /* 오른쪽에 여유를 둔 너비 */
    padding: 8px;
    margin-bottom: 10px;
    border: 1px solid #a9d9f5;
    border-radius: 4px;
}
.form-section input[type="text"]:focus,
.form-section input[type="email"]:focus {
    border-color: #007bff;
    outline: none; /* 기본 초점 테두리 대신 아래 그림자로 초점을 표시 */
    box-shadow: 0 0 5px rgba(0, 123, 255, 0.3);
}
.form-section button[type="submit"] {
    background-color: #007bff;
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    margin-top: 10px;
}
.form-section button[type="submit"]:hover {
    background-color: #0056b3;
}

/* 푸터 스타일 */
footer {
    background-color: #34495e;
    color: white;
    text-align: center;
    padding: 20px 0;
    margin-top: 30px;
    border-radius: 8px;
}

/* 클래스 선택자: 작은 텍스트 */
.small-text {
    font-size: 0.8em;
    color: #aaa;
}

/* 가상 클래스: input 필드에 포커스 있을 때 */
input:focus {
    border-color: #007bff;
    box-shadow: 0 0 5px rgba(0, 123, 255, 0.3);
}

두 파일을 저장하고 index.html을 엽니다. Live Server로 열었다면 주소가 이 index.html을 가리키는지, 파일을 직접 열었다면 css/style.css가 같은 프로젝트 안에 있는지 확인합니다. HTML에는 style 속성이 하나도 없지만, 외부 CSS의 규칙이 적용되어 짙은 남색 헤더에 노란 메뉴가 가로로 놓이고, 흰 카드 안에 콘텐츠와 폼이 정리되어 보입니다.

CSS 코드와 화면을 비교하며 각 선택자가 어떤 요소에 적용되었는지 확인해 보세요. 메뉴에 마우스를 올리면 링크가 주황색으로 바뀌고(:hover), 입력 필드를 클릭하면 파란 그림자가 생기며(:focus), “외부 링크” 뒤에는 화살표가 붙습니다(::after). 반면 input + button 규칙은 이 페이지에서 아무 요소에도 적용되지 않습니다. 이메일 입력의 바로 다음 형제는 버튼이 아니라 <br>이기 때문입니다.

이름에는 required가 있고 이메일은 type="email"이므로, 제출할 때 이름이 비어 있거나 이메일 형식이 틀리면 브라우저가 막습니다. 하지만 폼에 action이 없어 제출하면 같은 페이지를 다시 열 뿐, 문의를 저장하지는 않습니다.


같은 속성에 겹친 선언 계산하기

화면에 보이는 최종 스타일은 먼저 선택자가 요소에 맞는지 확인한 뒤 cascade 규칙으로 정해집니다. 같은 속성에 여러 선언이 겹치면, 현재 선택자와 미디어 조건에 맞는 후보를 모은 다음 출처와 중요도, cascade 계층(layer)을 먼저 비교하고, 그 안에서 명시도를 비교합니다. @scope를 쓴 동률 선언에서는 scope의 거리도 비교합니다. 여기서는 계층, scope, 인라인 스타일, 애니메이션, 진행 중인 전환이 없는 일반 작성자 CSS만 다루므로, 명시도를 비교하고 같으면 나중에 나온 선언을 고릅니다. 전체 순서는 MDN의 cascade 소개에서 볼 수 있습니다.

명시도(specificity)는 보통 a-b-c 세 숫자로 나타냅니다. a는 ID 선택자의 수, b는 클래스, 속성 선택자, 가상 클래스의 수, c는 태그 선택자와 가상 요소의 수입니다. 예를 들어 p는 0-0-1, .note는 0-1-0, #hero는 1-0-0, .card p:hover는 클래스 하나와 가상 클래스 하나, 태그 하나라서 0-2-1입니다. 비교할 때는 앞자리부터 보며, 앞자리가 크면 뒷자리가 아무리 커도 이깁니다. 세 숫자를 더한 합으로 비교하는 것이 아닙니다.

!important는 명시도를 높이는 기능이 아니라 cascade의 중요도 단계에 끼어들어, 명시도 비교보다 먼저 승부를 냅니다. 그래서 다른 규칙으로 덮어쓰기가 매우 어려워지므로, 일반적인 스타일 조정에는 쓰지 말고 범위가 분명한 예외에만 제한해서 씁니다.

상속은 cascade와 구분해야 합니다. color, font-family, line-height처럼 글자에 관한 속성은 대부분 부모의 값이 자식에게 전해지지만, margin, border, padding 같은 상자에 관한 속성은 기본으로 상속되지 않습니다. 또 자식 요소에 직접 적용된 규칙이 하나라도 있으면, 그 규칙의 명시도가 아무리 낮아도 부모에게서 상속된 값보다 우선합니다.

CSS cascade의 단순화된 평가 흐름과 specificity 비교 — 상태 추적

같은 p#hero의 A 0–2–1/B 1–0–0을 첫 ID 열에서 결정하는 구체 사례다. 후보 비교와 뒤 열/소스 순서 미도달 상태를 보여 주면 specificity를 합산하는 오해를 줄인다.

specificity가 다르면 선언 순서까지 가지 않습니다

CSS CASCADE · PRACTICE

specificity가 다르면 선언 순서까지 가지 않습니다

같은 요소의 같은 color에 경쟁하는 두 일반 선언을 단계별로 비교합니다.

ID 열에서 먼저 결정된 두 선언같은 p#hero에 매칭하는 일반 작성자 A .card p:hover 0–2–1과 B #hero 1–0–0을 비교한다. 앞 cascade 조건은 동률이며 ID 열에서 1이 0보다 커 B red가 선택된다. 뒤 열을 더하거나 소스 순서를 추가 비교하지 않는다.A · .card p:hover0–2–1 · class1 + pseudo-class1B · #hero1–0–0 · ID1같은 cascade 조건일반·layer/scope/inline/움직임 없음첫 열 · ID 비교B: 1 > A: 0B의 color: red 선택뒤 열 합산하지 않음소스 순서 비교 불필요
ID 열에서 먼저 결정된 두 선언같은 p#hero에 매칭하는 일반 작성자 A .card p:hover 0–2–1과 B #hero 1–0–0을 비교한다. 앞 cascade 조건은 동률이며 ID 열에서 1이 0보다 커 B red가 선택된다. 뒤 열을 더하거나 소스 순서를 추가 비교하지 않는다.A · .card p:hover0–2–1class1 + pseudo-class1B · #hero1–0–0ID1같은 cascade 조건둘 다 이 요소에 매칭일반·layer/scope 없음첫 열 · ID 비교B: 1 > A: 0B의 red 선택뒤 열 합산하지 않음소스 순서 비교 불필요

후보 A · .card p:hover

.card p:hover { color: blue; }

specificity 0–2–1 · class 1, pseudo-class 1, type 1

후보 B · #hero

#hero { color: red; }

specificity 1–0–0 · ID 1

  1. 선택자 매칭 · 둘 다 후보

    .card 안의 hover 상태인 p#hero에 두 color 선언이 모두 적용됩니다. 매칭하지 않는 선언은 제외합니다.

  2. 출처·중요도 · 동률

    둘 다 일반 작성자 선언이며 layer·scope·inline style·애니메이션·전환이 없는 예시입니다. !important는 specificity 점수가 아니라 앞선 중요도 단계에서 비교합니다.

  3. specificity · ID 열에서 결정

    1–0–0이 0–2–1보다 큽니다. 첫 열부터 비교하므로 뒤 열의 수를 합산하지 않습니다.

  4. 소스 순서 · 이 예시에서는 비교하지 않음

    앞 조건이 모두 같은 선언끼리만 나중에 나온 선언이 선택됩니다.

결과 · #hero의 color: red

나중에 쓴 규칙보다 앞선 cascade 조건이 먼저입니다. 자식에 직접 지정된 color는 부모로부터의 기본 상속값과 구분합니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
원도표의 전체 관계·조건·설명
구분내용
원도표 설명같은 요소의 같은 color를 두고 경쟁하는 두 일반 선언을 단계별로 비교합니다.
원도표 설명.card p:hover { color: blue; }specificity 0–2–1 · class 1, pseudo-class 1, type 1
원도표 설명#hero { color: red; }specificity 1–0–0 · ID 1
원도표 조건·주석선택자 매칭 · 둘 다 후보: .card 안의 hover 상태인 p#hero에 두 color 선언이 모두 적용됩니다. 매칭하지 않는 선언은 제외합니다.
원도표 조건·주석출처·중요도 · 동률: 둘 다 일반 작성자 선언이며 layer·scope·inline style·애니메이션·전환이 없는 예시입니다. !important는 specificity 점수가 아니라 앞선 중요도 단계에서 비교합니다.
원도표 조건·주석specificity · ID 열에서 결정: 1–0–0이 0–2–1보다 큽니다. 첫 열부터 비교하므로 뒤 열의 수를 합산하지 않습니다.
원도표 조건·주석소스 순서 · 이 예시에서는 비교하지 않음: 앞 조건이 모두 같은 선언끼리만 나중에 나온 선언이 선택됩니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
규칙 후보A: .card p:hover 0–2–1 / B: #hero 1–0–0두 selector가 같은 요소에 매칭된다조건 매칭과 우선순위 판정은 별개다
ID 열 비교ID: A0 < B1 / 다른 열: 더하지 않음ID 수를 먼저 비교하면 B가 이긴다specificity는 전체 숫자 합이 아니라 열 순서 비교다
색 적용승리 규칙: #hero / 색: red다른 상위 cascade 조건이 같다면 B의 red가 선택된다동등한 cascade 위치라는 가정을 유지한다

CSS cascade의 단순화된 평가 흐름과 specificity 비교 — 상태 추적

같은 p#hero의 A 0–2–1/B 1–0–0을 첫 ID 열에서 결정하는 구체 사례다. 후보 비교와 뒤 열/소스 순서 미도달 상태를 보여 주면 specificity를 합산하는 오해를 줄인다.

specificity가 다르면 선언 순서까지 가지 않습니다

CSS CASCADE · PRACTICE

specificity가 다르면 선언 순서까지 가지 않습니다

같은 요소의 같은 color에 경쟁하는 두 일반 선언을 단계별로 비교합니다.

ID 열에서 먼저 결정된 두 선언같은 p#hero에 매칭하는 일반 작성자 A .card p:hover 0–2–1과 B #hero 1–0–0을 비교한다. 앞 cascade 조건은 동률이며 ID 열에서 1이 0보다 커 B red가 선택된다. 뒤 열을 더하거나 소스 순서를 추가 비교하지 않는다.A · .card p:hover0–2–1 · class1 + pseudo-class1B · #hero1–0–0 · ID1같은 cascade 조건일반·layer/scope/inline/움직임 없음첫 열 · ID 비교B: 1 > A: 0B의 color: red 선택뒤 열 합산하지 않음소스 순서 비교 불필요
ID 열에서 먼저 결정된 두 선언같은 p#hero에 매칭하는 일반 작성자 A .card p:hover 0–2–1과 B #hero 1–0–0을 비교한다. 앞 cascade 조건은 동률이며 ID 열에서 1이 0보다 커 B red가 선택된다. 뒤 열을 더하거나 소스 순서를 추가 비교하지 않는다.A · .card p:hover0–2–1class1 + pseudo-class1B · #hero1–0–0ID1같은 cascade 조건둘 다 이 요소에 매칭일반·layer/scope 없음첫 열 · ID 비교B: 1 > A: 0B의 red 선택뒤 열 합산하지 않음소스 순서 비교 불필요

후보 A · .card p:hover

.card p:hover { color: blue; }

specificity 0–2–1 · class 1, pseudo-class 1, type 1

후보 B · #hero

#hero { color: red; }

specificity 1–0–0 · ID 1

  1. 선택자 매칭 · 둘 다 후보

    .card 안의 hover 상태인 p#hero에 두 color 선언이 모두 적용됩니다. 매칭하지 않는 선언은 제외합니다.

  2. 출처·중요도 · 동률

    둘 다 일반 작성자 선언이며 layer·scope·inline style·애니메이션·전환이 없는 예시입니다. !important는 specificity 점수가 아니라 앞선 중요도 단계에서 비교합니다.

  3. specificity · ID 열에서 결정

    1–0–0이 0–2–1보다 큽니다. 첫 열부터 비교하므로 뒤 열의 수를 합산하지 않습니다.

  4. 소스 순서 · 이 예시에서는 비교하지 않음

    앞 조건이 모두 같은 선언끼리만 나중에 나온 선언이 선택됩니다.

결과 · #hero의 color: red

나중에 쓴 규칙보다 앞선 cascade 조건이 먼저입니다. 자식에 직접 지정된 color는 부모로부터의 기본 상속값과 구분합니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.

관계도의 보충 설명

원도표의 전체 관계·조건·설명
구분내용
원도표 설명같은 요소의 같은 color를 두고 경쟁하는 두 일반 선언을 단계별로 비교합니다.
원도표 설명.card p:hover { color: blue; }specificity 0–2–1 · class 1, pseudo-class 1, type 1
원도표 설명#hero { color: red; }specificity 1–0–0 · ID 1
원도표 조건·주석선택자 매칭 · 둘 다 후보: .card 안의 hover 상태인 p#hero에 두 color 선언이 모두 적용됩니다. 매칭하지 않는 선언은 제외합니다.
원도표 조건·주석출처·중요도 · 동률: 둘 다 일반 작성자 선언이며 layer·scope·inline style·애니메이션·전환이 없는 예시입니다. !important는 specificity 점수가 아니라 앞선 중요도 단계에서 비교합니다.
원도표 조건·주석specificity · ID 열에서 결정: 1–0–0이 0–2–1보다 큽니다. 첫 열부터 비교하므로 뒤 열의 수를 합산하지 않습니다.
원도표 조건·주석소스 순서 · 이 예시에서는 비교하지 않음: 앞 조건이 모두 같은 선언끼리만 나중에 나온 선언이 선택됩니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
규칙 후보A: .card p:hover 0–2–1 / B: #hero 1–0–0두 selector가 같은 요소에 매칭된다조건 매칭과 우선순위 판정은 별개다
ID 열 비교ID: A0 < B1 / 다른 열: 더하지 않음ID 수를 먼저 비교하면 B가 이긴다specificity는 전체 숫자 합이 아니라 열 순서 비교다
색 적용승리 규칙: #hero / 색: red다른 상위 cascade 조건이 같다면 B의 red가 선택된다동등한 cascade 위치라는 가정을 유지한다

위 그림은 .card 안에서 마우스가 올라간 p#hero 요소 하나의 color에, .card p:hover { color: blue; }와 #hero { color: red; } 두 선언이 함께 적용되는 경우를 단계별로 보여 줍니다. 두 선택자가 모두 이 요소에 맞으므로 둘 다 후보가 되고, 둘 다 일반 작성자 선언이라 출처와 중요도는 같습니다.

명시도는 A가 0-2-1, B가 1-0-0이므로 첫 자리인 ID의 수에서 B의 1이 A의 0보다 커 B가 이깁니다. 뒷자리의 수를 더하거나 소스 순서를 비교할 필요가 없으며, 결과는 #hero의 빨간색입니다. 그림 아래의 설명처럼 나중에 쓴 규칙인지보다 앞선 cascade 조건이 먼저 비교됩니다.

더 깊이: 괄호를 받는 가상 클래스의 명시도

a-b-c 규칙에는 예외가 몇 가지 있습니다. 괄호 안에 선택자를 받는 :is(), :not(), :has()는 가상 클래스이지만 그 자체로는 b 자리에 1을 더하지 않습니다. 대신 괄호 안의 선택자들 가운데 명시도가 가장 높은 것의 값을 그대로 가져옵니다. 그래서 :is(p, #fakeId)는 실제로 p 요소에 맞았더라도 명시도가 1-0-0이고, p:not(#fakeId)는 1-0-1입니다. 괄호 안에 ID 하나만 섞여도 규칙 전체가 덮어쓰기 어려워진다는 뜻입니다.

:where()는 반대로 괄호 안에 무엇을 적든 명시도가 항상 0-0-0입니다. 전체 선택자 *도 명시도에 아무것도 더하지 않습니다. 그래서 여러 프로젝트에서 함께 쓰는 초기화 스타일이나 라이브러리의 기본 스타일을 :where(.card) p처럼 감싸 두면 명시도가 p와 같은 0-0-1이 되므로, 쓰는 쪽은 라이브러리 CSS보다 뒤에 둔 p 하나짜리 규칙만으로도 덮어쓸 수 있습니다.

각 자리의 수는 다음 자리로 넘어가지 않는다는 점도 기억해 둡니다. 클래스를 11개 이어 붙인 0-11-0도 ID 하나인 1-0-0을 이기지 못하고, 태그 선택자를 아무리 많이 써도 클래스 하나를 넘지 못합니다. 자세한 계산 규칙은 MDN의 명시도 설명에 있습니다.


정리

  • 외부 CSS 파일 하나로 페이지 전체의 스타일을 정하며, HTML에는 style 속성 없이 클래스와 구조만 둡니다.
  • 선택자가 화면에 효과를 내는지는 개발자 도구에서 그 규칙이 실제로 요소에 맞는지 확인합니다. input + button처럼 HTML 구조와 맞지 않는 선택자는 아무것도 고르지 않습니다.
  • 명시도는 ID, 클래스와 속성과 가상 클래스, 태그와 가상 요소의 수를 a-b-c로 나타내며, 앞자리부터 비교합니다.
  • !important는 명시도가 아니라 중요도 단계에서 비교되므로 남용하지 않습니다.
  • 상속은 cascade와 별개이며, 자식에 직접 적용된 규칙은 상속된 값보다 우선합니다.

연습 문제

1. 짝수 번째 항목

“CSS 학습 리스트”의 네 항목 가운데 연한 회색 배경이 적용되는 항목은 무엇일까요?

해설 보기

두 번째인 “선택자”와 네 번째인 “레이아웃”입니다. .content-section ul li:nth-child(even)은 부모 ul의 짝수 번째 자식인 li를 고르기 때문입니다.

nth-child는 1부터 셉니다. 홀수 번째인 “기본 문법”과 “속성”에 적용하려면 odd를 씁니다.

2. 화살표 옮기기

세 번째 메뉴 링크의 target="_blank"를 네 번째 링크로 옮겼습니다. 링크 글자는 그대로입니다. 화살표는 어디에 붙을까요?

해설 보기

“연락처” 뒤에 붙습니다. a[target="_blank"]::after 규칙은 링크의 글자가 아니라 target 속성을 보고 요소를 고르기 때문에, 속성이 옮겨 간 링크에 화살표가 생기고 “외부 링크” 뒤의 화살표는 사라집니다.

3. 버튼 앞의 여백

input + button 규칙이 제출 버튼에 적용되게 하려면 HTML을 어떻게 바꿔야 할까요? 바꾼 뒤 버튼은 어떻게 보일까요?

해설 보기

이메일 입력 바로 뒤의 <br>을 지워, 버튼이 입력의 바로 다음 형제가 되게 합니다. 그러면 input + button이 제출 버튼을 고르므로 버튼 왼쪽에 10px의 여백이 생기고, 버튼이 입력 상자와 같은 줄 오른쪽에 놓입니다. 입력 상자의 너비를 calc(100% - 120px)로 줄여 둔 덕분에 버튼이 들어갈 자리가 있습니다.

이렇게 결합 선택자는 HTML의 실제 구조에 따라 결과가 달라지므로, 개발자 도구에서 규칙이 맞는지 확인하며 고칩니다.

다음 글 색상, 폰트, 텍스트 스타일링에서는 색을 지정하는 여러 방법과 글꼴, 글자 크기, 줄 간격 같은 텍스트 스타일을 다룹니다.