문서 읽는 데 38분 · B1

B-1: CSS 입문과 선택자

전체 23강 중 5강 · HTML·CSS·JS
난이도 · 입문

안녕하세요, 홍순구 튜터입니다. 드디어 새로운 막이 올랐어요!

지난 시간까지 우리는 네 번에 걸쳐 인스타그램의 뼈대를 세웠어요. 로그인 페이지, 피드, 프로필, 그리고 폼까지. 사진도 띄우고, 댓글칸도 만들고, 공유 다이얼로그도 붙였죠. 기능은 다 갖춰졌어요.

그런데 지난 시간 마지막에 브라우저로 로그인 페이지를 열어봤을 때, 어땠나요? 솔직히 좀 휑했죠. 입력칸은 밋밋하게 세로로 쌓여 있고, 글자는 다 까만색이고, 버튼도 운영체제 기본 모양 그대로였어요. 우리가 아는 그 파란 인스타그램 로그인 화면과는 거리가 멀었죠.

그게 정상이라고 말씀드렸어요. HTML은 "무엇이 있는가"를 담당하고, 꾸미는 일은 다른 주인공의 몫이라고요. 그 주인공이 바로 오늘부터 만나는 CSS예요.

오늘은 그동안 막아뒀던 CSS의 문을 엽니다. CSS를 페이지에 어떻게 연결하는지 배우고, "이 글자만 파랗게", "저 버튼만 강조" 처럼 꾸미고 싶은 대상을 콕 집어내는 법을 익혀요. 이 "대상을 집어내는 도구"를 선택자(Selector) 라고 부르는데, 오늘의 진짜 주인공이에요. 수업이 끝나면 휑하던 로그인 페이지에 첫 색이 입혀집니다.

💡 오늘 수업의 핵심 — "CSS를 페이지에 연결하고, 선택자로 꾸밀 대상을 정확히 골라낸다" 🎯

🎯 학습 목표

  • CSS가 무엇이고 HTML과 어떻게 역할을 나누는지 이해합니다.
  • CSS를 페이지에 적용하는 세 가지 방법(인라인 / 내부 / 외부)을 알고, 왜 외부 파일이 정답인지 압니다.
  • 태그 · 클래스 · ID 선택자로 꾸밀 대상을 골라내고, 셋의 우선순위 감각을 잡습니다.
  • 자손 · 자식 · 인접 형제 선택자로 "관계"를 이용해 더 정교하게 골라냅니다.
  • :has() 선택자로 "특정 자식을 가진 부모"를 거꾸로 골라내는 최신 기법을 익힙니다.
  • CSS 중첩(Nesting)으로 선택자를 깔끔하게 묶어 정리합니다.

새 개념이 여럿 나오지만 걱정 마세요. CSS는 이 과목의 가장 큰 장점인 "코드를 쓰면 결과가 즉시 눈에 보인다" 가 제대로 빛나는 영역이에요. 한 줄 쓰고 저장하면 화면이 바로 변하거든요. 오늘은 외우려 하지 말고, 한 줄씩 직접 바꿔보면서 눈으로 확인하는 걸 목표로 해요. 자, 첫 색을 입혀볼까요?


Step 1: "CSS 첫걸음 — 뼈대에 옷을 입히다"

CSS가 정확히 뭘까요? 이름부터 풀어볼게요. CSSCascading Style Sheets의 줄임말이에요. 우리말로는 "계단식 스타일 시트" 정도인데, 지금은 "웹 페이지를 꾸미는 규칙을 모아둔 것" 이라고만 알면 충분해요.

HTML과 CSS의 관계는 이렇게 생각하면 쉬워요.

   HTML  =  사람의 뼈대와 장기      → "무엇이 있는가" (제목, 문단, 입력칸)
   CSS   =  옷·머리 모양·화장        → "어떻게 보이는가" (색, 크기, 배치)

뼈대만 있으면 다 똑같이 생긴 사람이에요. 옷을 입혀야 비로소 "그 사람"이 되죠. 우리 인스타그램도 마찬가지예요. HTML 뼈대는 다 만들었으니, 이제 CSS로 옷을 입힐 차례예요.

CSS 규칙은 어떻게 생겼나

CSS 한 덩어리를 규칙(rule) 이라고 불러요. 생김새는 딱 정해져 있어요.

   선택자        선언 블록
   ↓             ↓
   h2  {  color: blue;  }
          ↑      ↑
          속성    값
  • 선택자(selector): 누구를 꾸밀지 고르는 부분. 위에서는 "모든 <h2>".
  • 선언 블록: 중괄호 { } 안에 "어떻게 꾸밀지"를 적어요.
  • 속성: 값;: color(글자색)라는 속성에 blue라는 값을 줬어요. 한 줄 끝엔 세미콜론 ;을 붙여요.

"<h2>를 파란 글자로 만들어라" — 이 한 문장을 코드로 옮긴 게 CSS 규칙이에요. 어렵지 않죠?

CSS를 페이지에 붙이는 세 가지 방법

규칙을 만들었으면, 이걸 HTML 페이지에 연결해야 화면에 반영돼요. 방법이 세 가지 있어요.

첫째, 인라인(inline) 방식. 태그에 style 속성을 직접 붙여요.

<h2 style="color: blue;">친구들의 일상에 오신 것을 환영합니다</h2>

빠르긴 한데, 태그마다 일일이 붙여야 하고 HTML과 꾸미기가 뒤섞여 지저분해져요. 같은 색을 100개 제목에 주려면 100번 적어야 하죠.

둘째, 내부(internal) 방식. <head> 안에 <style> 태그를 두고 규칙을 모아요.

<head>
  <style>
    h2 { color: blue; }
  </style>
</head>

한 페이지 안에서는 깔끔하지만, 페이지가 여러 개면(우리는 index, feed, profile이 있죠) 페이지마다 똑같은 스타일을 복사해야 해요.

셋째, 외부(external) 방식. 스타일만 따로 .css 파일에 모으고, HTML에서 <link>로 불러와요. 이게 정답이에요.

<!-- instagram-clone-frontend/index.html -->
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Instagram - 로그인</title>
  <link rel="stylesheet" href="css/base.css">
</head>

스타일을 한 파일에 모아두면, 모든 페이지가 같은 <link> 한 줄로 그걸 공유해요. 색을 바꾸고 싶으면 그 파일 한 곳만 고치면 모든 페이지에 반영되고요. 그래서 실무에서는 거의 항상 외부 방식을 써요.

🎯 핵심 멘트: 인라인 < 내부 < 외부. 꾸미기(CSS)와 구조(HTML)는 파일을 나눠 따로 관리하는 게 정답이에요. "HTML은 HTML끼리, CSS는 CSS끼리."

우리 페이지에 첫 스타일 입히기

이제 진짜 첫 CSS 파일을 만들어요. css 폴더를 만들고 그 안에 base.css를 둡니다. 첫 규칙은 페이지 전체의 바탕이에요.

/* instagram-clone-frontend/css/base.css */
body {
  background-color: #fafafa;
  color: #262626;
  font-family: "Apple SD Gothic Neo", "Segoe UI", sans-serif;
}

body는 페이지 전체를 감싸는 태그죠. 거기에 세 가지를 줬어요.

  • background-color: #fafafa; — 배경을 아주 연한 회색으로. (#fafafa는 색을 나타내는 코드인데, 색 표현법은 다음 시간에 제대로 배워요. 지금은 "거의 흰색에 가까운 회색"이라고만.)
  • color: #262626; — 기본 글자색을 진한 회색으로. 새까만 검정보다 부드러워요.
  • font-family: ...; — 글꼴을 지정. 첫 글꼴이 없으면 다음 글꼴을 쓰는 식이에요.

저장하고 브라우저를 보면, 새하얗던 배경이 살짝 톤 다운된 회색으로 바뀌고, 글자도 부드러운 회색이 돼요. CSS 단 네 줄로 페이지 전체 분위기가 한 번에 달라지는 거예요. 이게 외부 CSS의 힘이에요. 한 곳을 고치니 페이지 전체가 따라 움직이죠.

🙋 직접 해보세요 — "값 하나만 바꾸면 화면이 어떻게 변할까?"

background-color#fafafa#000000(검정)으로 바꿔 저장해보세요. 배경이 새까매지죠? 다시 #fafafa로 되돌리고요. 이렇게 값 하나만 바꿔도 화면이 즉시 반응하는 걸 눈으로 확인하는 게 CSS 학습의 핵심이에요.

💡 튜터의 결론: CSS는 "선택자 { 속성: 값; }" 규칙의 모음이에요. 그 규칙들을 외부 .css 파일에 모아 <link>로 연결하면, 한 파일로 사이트 전체를 단장할 수 있어요. 우리 인스타그램의 옷장이 바로 이 base.css예요.


Step 2: "누구를 꾸밀까 — 선택자 기초"

Step 1에서 body라고 적은 부분, 그게 바로 선택자예요. "페이지 전체(body)를 꾸며라"라고 대상을 지목한 거죠. 그런데 우리가 꾸미고 싶은 건 보통 페이지 전체가 아니라 특정 부분이에요. "이 버튼만", "저 제목만" 처럼요.

그래서 선택자를 정확히 다루는 게 CSS의 진짜 실력이에요. 대상을 못 고르면 꾸밀 수가 없거든요. 가장 기본이 되는 세 가지 선택자부터 익혀요.

태그 선택자 — 같은 종류 전부

태그 이름을 그대로 쓰면, 그 태그 전부를 골라요. Step 1의 body가 그랬죠. 링크 <a>를 전부 골라 색을 줘볼게요.

/* instagram-clone-frontend/css/base.css */
a {
  color: #00376b;
  text-decoration: none;
}
  • color: #00376b; — 링크 글자를 인스타그램 특유의 진한 남색으로.
  • text-decoration: none; — 링크에 기본으로 깔리는 밑줄을 없애요.

저장하면 페이지의 모든 링크(네비게이션, "가입하기", 푸터 메뉴까지)가 한꺼번에 남색이 되고 밑줄이 사라져요. 태그 선택자는 이렇게 "같은 종류를 통째로" 다룰 때 좋아요.

클래스 선택자 — 내가 이름 붙인 무리

그런데 같은 <button>이라도 "로그인 버튼"과 "취소 버튼"을 다르게 꾸미고 싶을 때가 있죠. 태그 선택자로는 둘 다 똑같이 잡혀서 구분이 안 돼요. 이럴 때 클래스(class) 를 써요.

클래스는 내가 HTML 요소에 붙이는 이름표예요. 먼저 HTML에서 이름을 붙이고,

<!-- instagram-clone-frontend/index.html -->
<button type="submit" class="btn-login">로그인</button>

CSS에서는 이름 앞에 점 .을 찍어 골라요.

/* instagram-clone-frontend/css/base.css */
.btn-login {
  background-color: #0095f6;
  color: #ffffff;
  font-weight: bold;
}

저장하면 로그인 버튼이 인스타그램의 그 파란 버튼으로 변해요. 배경은 파란색, 글자는 흰색에 굵게. class="btn-login"을 붙인 요소만 골라서요.

클래스의 장점은 여러 요소에 같은 이름을 붙일 수 있다는 거예요. 버튼 10개에 class="btn-login"을 붙이면 10개가 다 같은 스타일을 받아요. 무리를 지어 꾸미는 도구예요.

ID 선택자 — 딱 하나뿐인 대상

가끔은 페이지에 단 하나뿐인 요소를 콕 집고 싶어요. 그럴 땐 ID를 써요. A-4에서 입력칸에 붙였던 id="username" 기억나죠? ID는 CSS에서 우물 정 #을 앞에 붙여 골라요.

/* instagram-clone-frontend/css/base.css */
#username {
  background-color: #efefef;
}

id="username"인 입력칸 하나만 골라 배경을 연한 회색으로 줬어요. 저장하면 아이디 입력칸만 회색 배경이 되고, 바로 아래 비밀번호 칸은 그대로예요. ID는 딱 그 하나만 겨냥하니까요.

여기서 중요한 약속이 하나 있어요. ID는 한 페이지에 딱 한 번만 써야 해요. 같은 id를 두 군데 붙이면 안 돼요. 반면 클래스는 몇 번이든 재사용할 수 있고요. 그래서 보통은 클래스를 기본으로 쓰고, "이건 정말 페이지에 하나뿐이다" 싶을 때만 ID를 써요.

셋이 부딪치면? — 우선순위 감각

만약 같은 요소를 태그·클래스·ID로 동시에 꾸미면서 색을 다르게 주면 누가 이길까요? 규칙은 이래요.

   약함  ←────────────────────────────→  강함
   태그 선택자  <  클래스 선택자  <  ID 선택자
   a {}            .btn-login {}        #username {}

더 구체적으로 콕 집은 쪽이 이겨요. "모든 링크"(태그)보다 "btn-login이라는 무리"(클래스)가 더 구체적이고, 그보다 "username 단 하나"(ID)가 더 구체적이죠. 그래서 ID가 가장 셉니다. 지금은 "구체적일수록 세다" 정도의 감각만 잡아두면 충분해요.

⚠️ 자주 하는 실수: 우선순위를 이기려고 ID를 남발하면, 나중에 스타일을 바꾸기가 점점 어려워져요. ID가 너무 세서 다른 규칙으로는 덮어쓰기가 힘들거든요. "기본은 클래스, ID는 꼭 필요할 때만" 을 습관으로 가져가세요.

💡 튜터의 결론: 선택자 세 가지를 정리하면 — 태그(a)는 같은 종류 전부, 클래스(.btn-login)는 내가 이름 붙인 무리, ID(#username)는 딱 하나뿐인 대상이에요. 부딪치면 구체적인 쪽(태그 < 클래스 < ID)이 이기고요. 실무의 90%는 클래스로 해결돼요.


Step 3: "관계로 고르기 — 자손·자식·인접 선택자"

Step 2의 선택자들은 "이 종류", "이 이름" 처럼 대상을 직접 가리켰어요. 그런데 HTML은 태그가 태그를 품는 둥지 구조잖아요. <header> 안에 <nav>가 있고, 그 안에 <a>가 있는 식으로요. 이 품고 품기는 관계를 이용하면 더 정교하게 골라낼 수 있어요.

관계를 표현하는 기호 세 가지를 배워요. 먼저 우리 헤더의 구조를 그림으로 보죠.

   header
     └─ nav
         └─ ul.nav-menu
              ├─ li ─ a   (홈)
              ├─ li ─ a   (탐색)
              ├─ li ─ a   (피드)
              └─ li ─ a   (프로필)

자손 선택자 — 공백으로 "안쪽 어디든"

선택자 사이에 공백을 두면, "앞 요소 안에 있는 뒤 요소"를 골라요. 깊이는 상관없어요. 안쪽 어디에 있든 다 잡혀요.

/* instagram-clone-frontend/css/base.css */
header nav a {
  color: #262626;
  font-weight: bold;
}

header nav a는 "header 안의 nav 안에 있는 모든 a"예요. 헤더 메뉴 링크들이 진한 회색에 굵게 변해요. 푸터(footer)에도 링크가 있지만, 걔네는 header 안이 아니니까 안 잡혀요. 관계로 범위를 좁힌 거죠.

자식 선택자 — >로 "바로 아래만"

화살표 >를 쓰면, "바로 아래 직계 자식"만 골라요. 한 단계만요. 손자나 그 아래는 안 잡혀요.

/* instagram-clone-frontend/css/base.css */
.nav-menu > li {
  background-color: #efefef;
}

.nav-menu > li는 "nav-menu 바로 아래의 li"예요. 저장하면 메뉴 항목마다 연한 회색 배경이 깔려요. 여기서 li 안에 들어 있는 ali의 자식이지 nav-menu의 자식이 아니라서 이 규칙엔 안 걸려요. "딱 한 단계 아래"만 본다는 점이 자손 선택자(공백)와의 차이예요.

   공백 (자손):  header  a     → 안쪽 어디든 (몇 단계든)
   >    (자식):  .nav-menu > li → 바로 한 단계 아래만

인접 형제 선택자 — +로 "바로 다음 짝"

플러스 +는 "바로 뒤에 붙어 있는 형제"를 골라요. 같은 부모 아래에서 바로 다음에 오는 요소죠.

/* instagram-clone-frontend/css/base.css */
h2 + p {
  color: #8e8e8e;
}

우리 로그인 페이지엔 환영 제목 <h2> 바로 다음에 안내 문단 <p>가 있죠. h2 + p는 그 "제목 바로 뒤의 문단" 하나를 골라 연한 회색으로 만들어요. 제목은 진하게, 바로 아래 설명은 흐리게 — 자연스러운 위계가 생겨요. 이때 <h2> 바로 다음 <p> 하나만 잡히고, 더 뒤에 떨어져 있는 다른 문단은 안 잡혀요.

🎯 핵심 멘트: 공백은 "안쪽 어디든(자손)", >는 "바로 아래만(자식)", +는 "바로 다음 형제(인접)". 관계 선택자는 HTML 구조를 그대로 따라가며 대상을 좁히는 도구예요.

💡 튜터의 결론: 클래스를 일일이 안 붙여도, 요소들의 관계(품고 품기는 구조, 나란히 있는 순서)만으로 대상을 골라낼 수 있어요. 다만 관계가 너무 길고 복잡해지면(header nav ul li a span...) 읽기 어려워지니, 적당히 짧게 쓰는 게 좋아요.


Step 4: "부모를 고르는 똑똑한 선택자 — :has()"

지금까지의 선택자는 한 가지 공통점이 있어요. 전부 "바깥에서 안쪽으로", 또는 "앞에서 뒤로" 내려가며 골랐죠. header → 안의 a, h2 → 다음 p 처럼요. 그런데 거꾸로, "안에 무엇을 가진 바깥 요소" 를 고르고 싶을 때가 있어요.

예를 들어볼게요. 우리 로그인 폼엔 "로그인 정보 저장" 체크박스가 있죠. 이 체크박스가 켜진 줄(<p>) 만 골라서 강조하고 싶어요. 그러려면 "체크된 input을 자식으로 가진 p"를 골라야 해요. 안쪽(input)을 보고 바깥(p)을 고르는 거죠. 지금까지의 선택자로는 이게 안 됐어요. 이걸 가능하게 해주는 게 :has() 예요.

/* instagram-clone-frontend/css/base.css */
.login-form p:has(input:checked) {
  background-color: #e7f3ff;
}

읽는 법은 이래요. .login-form p 까지는 "로그인 폼 안의 문단들"이고, 거기에 :has(input:checked)가 붙으면 "그 문단들 중, 체크된 input을 안에 가진 것" 으로 좁혀져요. :has(...) 괄호 안에 "어떤 자식을 가졌는지" 조건을 적는 거예요. 효과는 배경색으로 줬어요. 그 줄(<p>) 전체에 연한 파란 배경이 깔리게요.

저장하고 직접 해보세요. "로그인 정보 저장" 체크박스를 켜면, 그 줄 전체 배경이 즉시 연한 파란색으로 변해요. 체크를 풀면 원래대로 돌아오고요. 클릭 한 번에 화면이 반응하는데, 우리는 자바스크립트를 한 줄도 안 썼어요. :has() 가 "안의 상태"를 보고 "바깥"을 골라준 덕분이에요.

(글자색 대신 배경색을 준 데는 이유가 있어요. 이 줄의 글자는 사실 <label> 안에 들어 있는데, 라벨엔 이미 자기 색 규칙이 따로 걸려 있거든요. 그래서 <p>에 글자색을 줘도 라벨이 그걸 덮어써 버려요. 반면 배경색은 라벨이 건드리지 않으니, <p> 뒤에 깔려서 줄 전체가 또렷하게 강조돼요.)

:has()는 흔히 "부모 선택자" 라고 불려요. 자식의 상태를 보고 부모를 고를 수 있는 첫 선택자거든요. 오랫동안 CSS에 없던 기능이라, 등장했을 때 프론트엔드 개발자들이 정말 반겼어요.

⚠️ 호환성 메모: :has()는 비교적 최근에 모든 브라우저에 들어온 기능이에요. 2026년 현재는 크롬·사파리·파이어폭스 등 주요 브라우저에서 안정적으로 동작하니 안심하고 써도 돼요. 다만 아주 오래된 브라우저를 쓰는 환경이라면 동작하지 않을 수 있다는 점만 기억해두세요.

💡 튜터의 결론: :has()는 "안에 ~를 가진 바깥 요소"를 골라요. 지금까지의 "바깥→안" 방향을 거꾸로 뒤집은 거죠. 덕분에 "에러난 입력칸을 가진 폼", "이미지가 있는 카드" 같은 걸 자바스크립트 없이 CSS만으로 골라 꾸밀 수 있어요.


Step 5: "깔끔하게 묶기 — CSS 중첩(Nesting)"

여기까지 오면서 한 가지 불편함을 눈치챘을 수도 있어요. 같은 .login-form을 꾸미는데, 규칙이 여기저기 흩어져 있죠. .login-form 안의 라벨을 꾸미려면 매번 .login-form 부터 다시 적어야 하고요. 폼이 복잡해질수록 .login-form 이라는 말을 수십 번 반복하게 돼요.

이걸 해결하는 게 중첩(Nesting) 이에요. 관련 있는 규칙을 부모 선택자 안에 둥지처럼 넣는 방식이죠. HTML이 태그를 품듯이, CSS도 규칙을 품을 수 있게 된 거예요.

/* instagram-clone-frontend/css/base.css */
.login-form {
  background-color: #ffffff;

  & label {
    color: #8e8e8e;
    font-weight: bold;
  }
}

.login-form 의 중괄호 안에 또 다른 규칙을 넣었어요. 안쪽의 & label을 보세요. 여기서 &(앰퍼샌드)는 "바깥 선택자 자신", 즉 .login-form을 가리켜요. 그러니까 & label.login-form label(로그인 폼 안의 라벨)과 똑같은 뜻이에요.

중첩 없이 따로 썼다면 이랬을 거예요.

.login-form {
  background-color: #ffffff;
}
.login-form label {
  color: #8e8e8e;
  font-weight: bold;
}

두 방식의 결과는 완전히 똑같아요. 폼 배경은 흰색이 되고, 폼 안 라벨은 회색 굵은 글씨가 돼요. 하지만 중첩으로 쓰면 "이건 다 로그인 폼에 관한 규칙이다" 라는 게 한눈에 보이고, .login-form을 반복해서 안 적어도 돼요. 코드가 HTML 구조처럼 읽혀서 관리하기 훨씬 편해요.

🎯 핵심 멘트: 중첩(Nesting)은 관련 규칙을 부모 선택자 안에 모으는 정리법이에요. &는 "바깥 선택자 자신"을 뜻하고요. 결과는 안 변하지만, 코드를 읽고 고치기가 훨씬 편해져요.

🙋 직접 해보세요 — "중첩이 범위를 폼 안으로 한정할까?"

& labelcolor 값을 #ff0000(빨강)으로 바꿔보세요. 폼 안 라벨만 빨개지죠? .login-form 바깥에 있는 다른 라벨(있다면)은 안 변해요. 중첩이 범위를 폼 안으로 한정한다는 걸 확인할 수 있어요.

💡 튜터의 결론: 중첩은 "코드 정리 도구"예요. 새로운 화면 효과를 만드는 게 아니라, 흩어질 규칙을 부모 안에 모아 읽기 좋게 만드는 거죠. 이건 비교적 최근에 CSS에 정식으로 들어온 기능인데, 2026년 현재 주요 브라우저에서 모두 잘 동작해요.


마무리

첫 CSS 시간, 정말 많은 걸 해냈어요. 휑하던 로그인 페이지에 배경색이 깔리고, 링크 색이 바뀌고, 파란 로그인 버튼이 생기고, 체크박스를 켜면 줄이 강조되기까지 했죠. 그리고 그 모든 변화의 핵심에 선택자가 있었어요.

오늘 배운 것 한눈에 정리

🎯 하나, CSS는 "선택자 { 속성: 값; }" 규칙의 모음이에요. 외부 .css 파일에 모아 <link>로 연결하는 게 정답이고요.

🎯 , 기본 선택자 셋 — 태그(같은 종류 전부), 클래스(.이름, 무리), ID(#이름, 딱 하나). 부딪치면 구체적인 쪽(태그 < 클래스 < ID)이 이겨요.

🎯 , 관계 선택자 셋 — 공백(자손, 안쪽 어디든), >(자식, 바로 아래), +(인접 형제, 바로 다음).

🎯 , :has()는 "안에 ~를 가진 바깥 요소"를 거꾸로 골라요. 자바스크립트 없이 상태에 반응하는 스타일을 만들 수 있어요.

🎯 다섯, 중첩(Nesting)은 관련 규칙을 부모 안에 모으는 정리법이에요. &는 바깥 선택자 자신을 뜻해요.

그런데 아직 "배치"는 손 못 댔죠

오늘 우리가 바꾼 건 주로 이었어요. 글자색, 배경색 정도요. 그런데 진짜 인스타그램 로그인 화면을 떠올려보면, 입력칸 사이 간격도 적당하고, 폼이 화면 가운데 예쁘게 자리 잡고 있죠. 우리 폼은 아직 입력칸이 다닥다닥 붙어 있고 왼쪽으로 쏠려 있을 거예요.

왜냐면 우리는 오늘 "누구를 꾸밀까(선택자)" 만 배웠고, "어떻게 배치하고 띄울까" 는 아직 안 배웠거든요. 요소가 차지하는 공간, 안쪽 여백, 바깥쪽 간격 — 이런 걸 다루는 게 다음 주제예요.

다음 시간 예고

다음 시간엔 모든 화면 요소를 "상자(box)" 로 보는 관점을 배웁니다. 세상의 모든 HTML 요소는 사실 네모난 상자예요. 그 상자의 안쪽 여백(padding), 테두리(border), 바깥쪽 여백(margin) 을 다루는 박스 모델을 익히면, 비로소 입력칸 사이가 시원하게 벌어지고 버튼도 손가락으로 누르기 좋게 커져요.

거기에 크기를 정하는 단위(px·rem·% 같은 것들), 색을 더 자유롭게 다루는 법, 그리고 글자를 다듬는 타이포그래피까지 더해서, 오늘 시작한 이 base.css를 제대로 완성합니다. 오늘이 선택자로 "고르는" 법이었다면, 다음 시간은 그 고른 대상을 "다듬고 배치하는" 법이에요.

오늘 선택자라는 든든한 도구를 손에 넣었으니, 다음 시간엔 그걸로 본격적으로 페이지를 다듬어봅시다!


과제

[구현] 피드 페이지에 첫 스타일 입히기

오늘은 로그인 페이지(index.html)에만 base.css를 연결했어요. 이번엔 피드 페이지(feed.html)에도 같은 스타일을 연결하고, 선택자를 직접 연습해봐요.

요구 사항:

  • feed.html<head>base.css<link>로 연결할 것 (한 줄이면 돼요. 외부 CSS를 여러 페이지가 공유하는 걸 직접 확인)
  • base.css에 아래 선택자 규칙을 직접 추가할 것:
    • 태그 선택자: 페이지의 모든 <h2> 글자색을 원하는 색으로 바꾸기
    • 클래스 선택자: 댓글 입력 버튼 같은 요소에 클래스를 붙이고(class="..."), 그 클래스로 배경색 주기
    • 관계 선택자: article 안의 사진 설명(figcaption)만 골라 색을 주기 (article figcaption { ... } 형태로 — 자손 선택자 연습)
    • :has() 선택자: "동영상을 가진 게시물"(article:has(video))이나 "댓글 입력칸을 가진 게시물"(article:has(textarea))을 골라 배경색으로 강조해보기

각 규칙을 저장할 때마다 브라우저에서 어떻게 변하는지 눈으로 확인하고, 값을 한 번씩 바꿔보며 어떤 요소가 잡히는지 실험해보세요.

[탐구] 즐겨 쓰는 사이트의 선택자 엿보기

자주 쓰는 웹사이트 한 곳을 열고, F12 → Elements 탭에서 아무 요소나 클릭해보세요. 오른쪽 Styles 패널에 그 요소에 적용된 CSS 규칙들이 보여요.

확인 항목:

  1. 적용된 규칙의 선택자는 어떻게 생겼나? 클래스(.)가 많은가, 태그가 많은가, ID(#)도 보이나?
  2. 클래스 이름은 어떤 식으로 지어져 있나? (예: btn-primary, nav-item 처럼 의미가 보이는 이름인가?)
  3. 한 요소에 여러 규칙이 겹칠 때, 줄이 그어져 무효가 된 규칙이 보이나? (우선순위에 밀린 규칙이에요)

관찰한 내용을 3~4줄로 정리하고, "이 사이트는 클래스와 ID 중 무엇을 주로 쓰더라" 한 줄 의견을 덧붙여주세요.


생각해볼 주제

1. 인라인 스타일은 왜 "급할 때만"일까?

태그에 style="..."을 직접 붙이는 인라인 방식은 가장 빠르고 직관적이에요. 한 요소만 잠깐 바꿔보고 싶을 땐 편하죠. 그런데 우리는 외부 CSS 파일을 정답으로 배웠어요. 만약 사이트 전체의 버튼 색을 인라인 스타일로 칠해뒀다면, 나중에 그 색을 한 번에 바꾸려 할 때 어떤 일이 벌어질까요? 빠름과 유지보수 사이의 트레이드오프를 생각해보세요.

2. ID 선택자는 강력한데, 왜 아껴 쓰라고 할까?

ID 선택자는 클래스보다 우선순위가 높아서, 다른 규칙을 확실히 이겨요. "무조건 적용되게 하고 싶으면 ID를 쓰면 되겠네?"라고 생각할 수 있죠. 그런데 실무에서는 ID로 스타일을 주는 걸 오히려 피하는 편이에요. ID를 스타일에 남발하면 나중에 어떤 곤란을 겪게 될까요? "강하다"는 게 항상 좋은 걸까요?

3. :has() 같은 최신 기능, 언제부터 마음 놓고 쓸까?

:has()나 CSS 중첩은 비교적 최근에 모든 브라우저에 들어온 기능이에요. 새 기능은 분명 편리하지만, 모든 사용자의 브라우저가 그걸 지원한다는 보장은 없죠. 여러분이 실무에서 새 CSS 기능을 쓸지 말지 결정해야 한다면, 무엇을 근거로 판단하면 좋을까요? "지원율이 몇 %면 써도 된다" 같은 기준을 세울 수 있을까요?

✅ 예시 답안정답 보기

과제와 생각해볼 주제의 예시답안이에요. 정답이 하나만 있는 건 아니에요. 색 값이나 클래스 이름은 여러분 마음대로 골라도 좋아요. 중요한 건 선택자를 의도대로 골라냈는가 예요.


🎯 [과제 1 예시답안] 피드 페이지에 첫 스타일 입히기

핵심 접근

이 과제의 핵심은 두 가지예요. 첫째, 외부 CSS 파일 하나를 여러 페이지가 공유한다는 걸 직접 확인하는 것. 둘째, 오늘 배운 네 종류의 선택자(태그·클래스·관계·:has())를 실제 요소에 겨눠보는 것이에요.

먼저 feed.htmlbase.css를 연결하는 것부터 시작해요. index.html에 적었던 <link> 한 줄을 그대로 가져오면 돼요.

예시 구현

feed.html<head>에 링크 한 줄을 추가해요.

<!-- instagram-clone-frontend/feed.html -->
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Instagram - 피드</title>
  <link rel="stylesheet" href="css/base.css">
</head>

이 한 줄만 추가해도, index.html에서 만든 body 배경색·링크 색·네비게이션 스타일이 피드 페이지에도 그대로 나타나요. 파일 하나를 두 페이지가 나눠 쓰는 거죠. 이게 외부 CSS의 가장 큰 장점이에요.

이제 base.css 맨 아래에 피드 페이지용 규칙을 추가해요. (게시 버튼에는 먼저 HTML에서 클래스를 붙여줘요.)

<!-- instagram-clone-frontend/feed.html — 첫 게시물의 댓글 폼 -->
<button type="submit" class="btn-post">게시</button>
/* instagram-clone-frontend/css/base.css 맨 아래에 추가 */

/* 태그 선택자 — 모든 h2 제목 */
h2 {
  color: #262626;
}

/* 클래스 선택자 — 댓글 '게시' 버튼 */
.btn-post {
  background-color: #0095f6;
  color: #ffffff;
  font-weight: bold;
}

/* 관계 선택자(자손) — 게시물 안의 사진 설명만 */
article figcaption {
  color: #8e8e8e;
}

/* :has() — 동영상을 가진 게시물 강조 */
article:has(video) {
  background-color: #f0f8ff;
}

각 규칙이 무엇을 겨냥하는지 짚어볼게요.

  • h2 — "피드" 제목을 골라요. 태그 선택자라 페이지의 모든 <h2>가 잡혀요.
  • .btn-post — 댓글 '게시' 버튼만 골라 파란 버튼으로. 클래스를 붙인 요소만 잡혀요.
  • article figcaption — 게시물(article) 안에 있는 사진 설명(figcaption)만 흐린 회색으로. 자손 선택자예요.
  • article:has(video) — 동영상을 품은 게시물('오늘의 릴스')만 골라 연한 파란 배경을 줘요. :has()로 "안에 video를 가진 article"을 골라낸 거죠.

저장하면 동영상 게시물만 배경이 살짝 파래지는 걸 볼 수 있어요. 다른 사진 게시물은 그대로고요.

채점 포인트

항목 확인 내용
외부 CSS 공유 feed.html<link>를 추가해 base.css를 연결했는가
태그 선택자 태그 이름만으로 같은 종류를 통째로 골랐는가 (h2 등)
클래스 선택자 HTML에 class를 붙이고, CSS에서 .으로 골랐는가
관계 선택자 공백(자손)으로 "안쪽 요소"를 좁혀 골랐는가 (article figcaption)
:has() "안에 ~를 가진 바깥 요소"를 골랐는가 (article:has(video))
결과 확인 저장 후 브라우저에서 변화를 눈으로 확인했는가

흔한 실수

  • 클래스 앞에 .을 안 붙임: HTML에는 class="btn-post"(점 없음), CSS에는 .btn-post(점 있음)예요. CSS에서 점을 빼먹으면 btn-post라는 태그를 찾게 돼서 아무것도 안 잡혀요.
  • :has() 괄호 안을 비워둠: article:has()처럼 비우면 안 돼요. 괄호 안에 "어떤 자식을 가졌는지" 조건(video, textarea 등)을 꼭 적어야 해요.
  • <link> 경로 오타: href="css/base.css"인데 base.css만 적거나 폴더명을 틀리면 스타일이 아예 안 먹어요. 화면이 안 변하면 가장 먼저 경로부터 확인하세요.

실무 개선 포인트 (심화)

지금은 피드용 규칙을 base.css 맨 아래에 그냥 이어 붙였어요. 페이지가 늘어나면 한 파일이 점점 길어지겠죠. 실무에서는 보통 역할별로 파일을 나눠요. 페이지 공통 바탕은 base.css, 레이아웃은 따로, 카드·버튼 같은 조각은 또 따로요. 다음 모듈들을 지나면서 우리도 자연스럽게 파일을 나누게 될 거예요. 지금은 "한 파일에 모은다"로 충분하고, "언젠가 나눈다"만 기억해두세요.


🎯 [과제 2 예시답안] 즐겨 쓰는 사이트의 선택자 엿보기

핵심 접근

이 과제는 코드를 짜는 게 아니라 읽는 연습이에요. 잘 만든 사이트가 선택자를 어떻게 쓰는지 관찰하면, "아, 실무에선 이렇게 하는구나" 하는 감각이 생겨요. 정답을 맞히는 과제가 아니라, 패턴을 발견하는 과제예요.

분석 가이드

F12 → Elements 탭에서 요소를 클릭하면, 오른쪽 Styles 패널에 그 요소에 적용된 규칙이 위에서 아래로 쌓여 보여요. 이때 세 가지를 관찰해요.

  1. 선택자 모양 — 클래스(.)가 압도적으로 많을 거예요. 태그 단독 선택자는 기본 스타일(body, a 등)에만 보이고, ID(#)는 의외로 드물어요.
  2. 클래스 이름 짓는 법btn, header, card-title처럼 "무엇인지"가 보이는 이름이 좋은 이름이에요. 의미를 알 수 없는 이름(자동 생성된 무작위 문자열)도 종종 보이는데, 그건 도구가 만든 거예요.
  3. 줄 그어진 규칙 — 한 속성에 줄이 그어져 있다면, 더 우선순위 높은 다른 규칙에 밀린 거예요. 우리가 배운 "구체적인 쪽이 이긴다"가 실제로 일어나는 현장이에요.

모범 정리 예시

네이버 메인의 검색 버튼을 눌러봤어요. 적용된 규칙의 선택자는 거의 다 클래스(.btn_search 같은)였고, 태그 단독 선택자는 body의 기본 글꼴 정도만 보였어요. 클래스 이름은 btn_search, area_search처럼 역할이 드러나는 이름이 많았고요. ID 선택자로 스타일을 준 경우는 거의 못 봤어요. 한 버튼에 색상 규칙이 두 개 겹쳐서, 위쪽(더 구체적인) 규칙이 이기고 아래 규칙엔 줄이 그어져 있었어요. 결론: 이 사이트는 스타일을 거의 클래스로만 주고 있었다.

채점 포인트

항목 확인 내용
선택자 관찰 클래스·태그·ID 중 무엇이 주로 쓰이는지 파악했는가
이름 규칙 클래스 이름이 의미를 담는지 관찰했는가
우선순위 흔적 줄 그어진(밀린) 규칙을 찾아 우선순위 개념과 연결했는가
한 줄 의견 "클래스 위주 / ID 위주" 같은 결론을 냈는가

흔한 실수

  • Elements 탭이 아닌 Console에서 찾으려 함: CSS 규칙은 Elements 탭의 오른쪽 Styles 패널에 있어요. Console은 자바스크립트 출력용이에요.
  • 무작위 클래스 이름을 보고 당황: 일부 사이트는 도구가 자동으로 만든 이상한 클래스 이름을 써요. 그건 사람이 손으로 짠 게 아니니, 의미를 해석하려 애쓸 필요 없어요.

실무 개선 포인트 (심화)

잘 만든 사이트일수록 ID로 스타일을 주는 경우가 드물고, 의미가 담긴 클래스 이름을 일관되게 써요. 이건 우연이 아니에요. 클래스 위주로 가야 스타일을 재사용하고 나중에 바꾸기 쉽거든요. 우리가 "기본은 클래스"라고 배운 게 실무의 표준과 정확히 맞닿아 있다는 걸 직접 확인한 셈이에요.


💡 [생각해볼 주제 1 예시답안] 인라인 스타일은 왜 "급할 때만"일까?

[문제 상황 요약]

인라인 스타일(style="...")은 빠르고 직관적이지만, 우리는 외부 CSS 파일을 정답으로 배웠어요. 사이트 전체 버튼 색을 인라인으로 칠해뒀다면, 나중에 그 색을 한 번에 바꿀 때 어떤 일이 벌어질지 생각해보는 문제예요.

[튜터의 가이드 및 해설]

핵심은 "한 곳을 고치면 전체가 바뀌는가, 아니면 모든 곳을 일일이 고쳐야 하는가" 예요.

버튼 색을 외부 CSS의 .btn { background-color: #0095f6; } 한 곳에 뒀다면, 색을 바꿀 때 그 한 줄만 고치면 모든 버튼이 한꺼번에 바뀌어요. 반면 버튼 100개에 style="background-color: #0095f6;"을 일일이 붙여뒀다면, 색을 바꾸려면 100군데를 다 찾아 고쳐야 해요. 하나라도 빠뜨리면 그 버튼만 옛날 색으로 남고요.

여기에 더해, 인라인 스타일은 우선순위가 아주 높아요. 클래스나 ID로도 덮어쓰기가 어렵죠. 그래서 "이 버튼만 잠깐 다르게" 하려고 인라인을 쓰면, 나중에 전체 규칙으로 통일하려 할 때 그 인라인이 계속 발목을 잡아요.

그래서 인라인 스타일은 "진짜 그 요소 하나에만, 잠깐, 다른 규칙과 안 겹칠 때" 정도로 아껴 써요. 빠름의 대가로 유지보수를 잃는 거래라서, 코드가 오래 살아남아야 할수록 손해가 커져요.

🎯 면접관을 홀리는 핵심 멘트

"인라인 스타일은 빠른 대신 '한 곳을 고치면 전체가 바뀐다'는 CSS의 가장 큰 장점을 포기하는 방식이에요. 그래서 저는 구조(HTML)와 표현(CSS)을 분리해 외부 파일로 관리하고, 인라인은 정말 일회성인 경우에만 씁니다."


💡 [생각해볼 주제 2 예시답안] ID 선택자는 강력한데, 왜 아껴 쓰라고 할까?

[문제 상황 요약]

ID 선택자는 클래스보다 우선순위가 높아 다른 규칙을 확실히 이겨요. "무조건 적용되게 하려면 ID를 쓰면 되겠네"라고 생각하기 쉽죠. 그런데 실무에서는 ID로 스타일 주는 걸 오히려 피해요. 그 이유를 생각해보는 문제예요.

[튜터의 가이드 및 해설]

두 가지 곤란이 있어요. 재사용 문제우선순위 함정이에요.

먼저 재사용. ID는 한 페이지에 딱 한 번만 쓰는 게 약속이에요. 그러니 ID로 스타일을 주면 그 스타일은 그 요소 하나에만 묶여요. 똑같은 버튼이 열 개 있어도 ID 스타일은 재사용이 안 돼요. 반면 클래스는 열 개에 같은 이름을 붙여 한 번에 꾸밀 수 있죠. 스타일은 재사용할수록 이득인데, ID는 그 길을 막아요.

다음은 우선순위 함정. ID가 세다는 건 양날의 검이에요. #username으로 색을 줘버리면, 나중에 .input-error 같은 클래스로 "에러일 땐 빨갛게" 하려 해도 ID가 너무 세서 안 먹어요. 이걸 이기려면 또 다른 ID나 더 복잡한 선택자를 동원해야 하고, 그러다 보면 선택자들이 서로 우선순위 싸움을 벌이는 난장판이 돼요. "강함"이 오히려 유연함을 빼앗는 거죠.

그래서 실무의 기본은 "스타일은 클래스로, ID는 자바스크립트가 요소를 콕 집을 때나 페이지 내 앵커(#) 용도로" 예요. ID 자체가 나쁜 게 아니라, 스타일링에 쓰기엔 너무 세고 재사용이 안 된다는 거예요.

🎯 면접관을 홀리는 핵심 멘트

"ID 선택자는 우선순위가 높아 강력하지만, 그 강력함이 곧 발목을 잡아요. 재사용이 안 되고, 나중에 다른 규칙으로 덮어쓰기가 어렵거든요. 그래서 저는 스타일은 클래스로 일관되게 주고, ID는 스타일이 아니라 식별 용도로 남겨둡니다."


💡 [생각해볼 주제 3 예시답안] :has() 같은 최신 기능, 언제부터 마음 놓고 쓸까?

[문제 상황 요약]

:has()나 CSS 중첩은 비교적 최근에 모든 브라우저에 들어온 기능이에요. 편리하지만 모든 사용자의 브라우저가 지원한다는 보장은 없죠. 실무에서 새 CSS 기능을 쓸지 말지, 무엇을 근거로 판단할지 생각해보는 문제예요.

[튜터의 가이드 및 해설]

판단의 핵심은 "내 사용자들이 쓰는 브라우저가 이 기능을 지원하는가" 예요. "최신이라 멋지니까"가 아니라 "우리 사용자에게 동작하는가"가 기준이에요.

이걸 확인하는 방법이 있어요. 브라우저별 기능 지원 현황을 정리한 사이트(예: caniuse 같은 곳)에서 기능 이름을 검색하면, 전 세계 사용자 기준 지원율이 몇 %인지, 어떤 브라우저의 몇 버전부터 되는지 한눈에 나와요. 또 요즘은 "주요 브라우저에 안정적으로 자리 잡았다"는 걸 뜻하는 기준선(Baseline) 표시가 있어서, 이게 "널리 지원됨"으로 표시되면 안심하고 쓸 수 있어요.

그다음엔 내 사용자층을 봐요. 사내 관리자 도구처럼 최신 브라우저만 쓰는 환경이라면 새 기능을 적극적으로 써도 돼요. 반대로 아주 다양한 기기와 오래된 브라우저가 섞인 불특정 다수 서비스라면 더 보수적으로 가고, 필요하면 "지원 안 될 때를 대비한 대비책(fallback)"을 함께 둬요.

:has()와 CSS 중첩은 2026년 현재 주요 브라우저에서 모두 널리 지원돼요. 그래서 우리는 안심하고 기본으로 배운 거예요. 하지만 "최신 기능이니까 무조건 쓴다"가 아니라 "지원 현황을 확인하고 내 사용자에 맞춰 결정한다"는 습관이 진짜 실력이에요.

🎯 면접관을 홀리는 핵심 멘트

"새 CSS 기능을 쓸지는 '멋짐'이 아니라 '우리 사용자의 브라우저가 지원하는가'로 판단해요. 저는 caniuse나 Baseline 지원 현황을 확인하고, 사용자층이 보수적이면 대비책을 함께 두는 식으로 결정합니다. :has()는 이미 널리 지원돼서 마음 놓고 쓰고 있고요."

더 배우려면

실무 프로젝트까지 가고 싶다면

팀스파르타 백엔드 부트캠프에서 인스타그램 클론을 풀스택으로 완성합니다.