LogoSEO Jing
  • All Posts
  • SEO Jing
  • KD Team
  • CLab CoreTeam
  • Study

Contact Me

© 2026 SEOJing. All rights reserved.

프론트엔드스터디CSSForm박스모델선택자

프론트엔드 스터디 대면 3주차: 폼(Form), CSS 선택자, 그리고 박스 모델

2026년 4월 3일·22분 읽기

1. 저번 주 복습

지난주에는 사용자로부터 정보를 입력받는 폼(Form)과, CSS를 HTML에 연결하고 원하는 요소를 정확히 선택하는 CSS 선택자를 배웠습니다. 이번에는 단순 나열이 아니라, 각 개념이 왜 중요한지와 실수하기 쉬운 포인트를 중심으로 복습하겠습니다.

폼(Form) 핵심

form 태그와 action, method

form 태그는 입력 요소들을 감싸는 컨테이너입니다. 택배 상자 에 비유하면, action은 배송지 주소이고 method는 배송 방법(일반 택배 vs 등기)입니다. 상자 없이 물건만 던지면 배달이 안 되듯, form 없이 input만 있으면 데이터가 서버로 전송되지 않습니다.

html
<form action="/signup" method="post">
  <input type="text" name="username" />
  <input type="password" name="password" />
  <button type="submit">회원가입</button>
</form>

name 속성 — 없으면 서버로 안 간다

name 속성은 서버로 데이터를 보낼 때 키(key) 역할을 합니다. 택배 송장에 받는 사람 이름이 없는 것과 같습니다. 택배가 도착해도 누구 것인지 모르면 전달할 수 없듯, name이 없는 input의 값은 서버에 전혀 전달되지 않습니다.

html
<!-- name이 없어서 서버로 전송 안 됨 -->
<input type="text" />

<!-- name이 있어서 서버에 username=입력값 형태로 전송 -->
<input type="text" name="username" />

흔한 실수: 폼을 만들고 "서버에 데이터가 안 들어와요"라고 할 때, 가장 먼저 확인할 것이 name 속성 누락입니다. 개발자 도구의 Network 탭에서 Form Data를 보면 바로 확인할 수 있습니다.

label과 for — 클릭 면적을 넓혀주는 연결

label의 for 값과 input의 id 값을 일치시키면, label 텍스트를 클릭해도 input에 포커스가 이동합니다. 데스크톱에서는 편의 기능이지만, 모바일에서는 터치 영역이 넓어지는 실질적인 UX 개선입니다. 작은 체크박스나 라디오 버튼을 손가락으로 정확히 누르기 어려운 상황을 떠올려보세요.

html
<!-- label을 클릭하면 id="email" input에 포커스 -->
<label for="email">이메일 주소</label>
<input type="email" id="email" name="email" />

<!-- 체크박스: label 클릭으로 체크/해제 가능 -->
<input type="checkbox" id="agree" name="agree" />
<label for="agree">이용약관에 동의합니다

접근성 보너스: 스크린 리더가 input을 읽을 때 연결된 label의 텍스트를 함께 읽어줍니다. label이 없으면 시각 장애인 사용자는 이 입력칸이 뭘 위한 것인지 알 수 없습니다.

input 태그의 type 속성

type 하나만 바꿔도 브라우저가 무료로 도와주는 기능이 달라집니다. type="text"로 모든 걸 해결하려는 것은 만능 리모컨 대신 모든 가전에 일일이 손을 대는 것과 같습니다.

type브라우저가 해주는 것모바일 키보드
text없음일반 키보드
email@ 포함 여부 자동 검증@ . 버튼 포함
password입력값 마스킹(●)일반 키보드

select, textarea

select는 드롭다운 선택, textarea는 여러 줄 텍스트 입력을 담당합니다. input은 한 줄 입력에 특화되어 있으므로, 긴 텍스트(자기소개, 문의 내용 등)에는 반드시 textarea를 사용합니다.

textarea를 쓸 때 한 가지 주의할 점이 있습니다. 아래처럼 태그 사이에 줄바꿈이나 들여쓰기를 넣으면, 그 공백이 초기값으로 그대로 들어갑니다.

html
<!-- 태그 사이의 개행/공백이 초기값으로 들어감 -->
<textarea name="content"> </textarea>

<!-- 이렇게 붙여써야 빈 상태로 시작함 -->
<textarea name="content"></textarea>

이 문제는 input에서는 발생하지 않습니다. input은 자기 닫힘 태그이고 값은 value 속성으로 지정하기 때문입니다. textarea는 태그 사이의 내용이 곧 값이기 때문에 개행 문자가 끼어들 수 있습니다.

나중에 JavaScript를 배우면 이런 상황에서 trim()이라는 메서드를 자주 쓰게 됩니다. trim()은 문자열 앞뒤의 공백과 개행을 제거합니다.

js
const value = document.querySelector("textarea").value;
// 사용자가 입력한 값: "\n안녕하세요\n"
console.log(value.trim()); // "안녕하세요"

왜 제거해야 할까요? 앞뒤 공백이 붙은 채로 서버에 저장되면, 나중에 값을 비교하거나 검색할 때 오작동이 생깁니다. 예를 들어 사용자가 아이디로 "seojin"을 입력했는데 실제로 저장된 값이 " seojin"이라면, 로그인 비교 시 불일치가 발생합니다. 사용자 입장에서는 올바르게 입력했는데 오류가 나는 것처럼 보이죠. trim()은 이런 눈에 보이지 않는 공백 버그를 예방하는 가장 기본적인 습관 입니다.

CSS 선택자 핵심

기본 선택자 정리

css
/* 태그 선택자: 해당 태그 전부 선택 */
p {
  color: black;
}

/* 클래스 선택자: 마침표(.)로 시작, 여러 요소에 재사용 가능 */
.highlight {
  background: yellow;
}

/* 아이디 선택자: 샵(#)으로 시작, 페이지에서 딱 하나 */
#main-title {
  font-size: 2rem;
}

헷갈리는 포인트: 클래스는 한 요소에 여러 개 붙일 수 있지만 (class="btn btn-primary"), 아이디는 페이지에서 단 하나여야 합니다. 아이디가 중복되면 HTML 문법 위반이고, JavaScript의 getElementById도 첫 번째 요소만 반환합니다.

자손 결합자 vs 자식 결합자

html
<nav>
  <ul>
    <li><a href="/">홈</a></li>
    <li>
      <div>
        <a href="/about">소개</a>
      </div>
    </li>
  </ul>
</nav>
css
/* 자손 결합자: nav 안의 모든 a를 선택 (깊이 무관) */
nav a {
  color: white;
}

/* 자식 결합자: nav의 직계 자식 a만 선택 (여기서는 해당 없음) */
nav > a {
  color: white;
}

위 HTML에서 nav a는 "홈"과 "소개" 링크 모두 선택합니다. 하지만 nav > a는 nav의 직계 자식인 a가 없으므로 아무것도 선택하지 않습니다. 자손 결합자(공백)는 "안에 있는 모든 것", 자식 결합자(>)는 "바로 아래 한 단계"라고 기억하세요.

CSS 우선순위 — 점수제로 이해하기

CSS 우선순위는 점수 계산으로 이해하면 직관적입니다. 같은 요소에 여러 규칙이 적용될 때, 점수가 높은 쪽이 이깁니다.

종류점수예시
인라인 스타일1,000점style="color: red"
아이디(#)100점#header
클래스(.) / 속성 / 가상클래스10점.btn, ,
css
/* 점수: 태그(1) + 클래스(10) = 11점 */
p.intro {
  color: blue;
}

/* 점수: 아이디(100) = 100점 → 이쪽이 이김 */
#welcome {
  color: red;
}

왜 id로 스타일링하면 안 좋을까? id 선택자는 100점이라 클래스(10점)로는 절대 이길 수 없습니다. 나중에 스타일을 덮어써야 할 때 더 높은 우선순위(!important 등)를 남발하게 되고, CSS가 점점 관리 불가능해집니다. 그래서 실무에서는 스타일링에는 클래스만 사용하고, id는 JavaScript 선택이나 앵커 링크 용도로만 쓰는 것이 관례입니다.

같은 점수라면? 나중에 작성된(아래에 있는) 스타일이 이깁니다. 이것을 캐스케이드(Cascade)라고 합니다. CSS의 C가 바로 이 Cascade입니다.

학습 자료에서 다룬 추가 개념

2주차 학습 자료에서 강의 외 추가 개념들을 다뤘습니다. 다시 한번 짚어보겠습니다.

GET vs POST — 엽서 vs 편지 봉투

둘 다 데이터를 서버로 보내지만, 데이터를 싣는 위치가 다릅니다. GET은 엽서처럼 내용이 겉면(URL)에 다 보이고, POST는 편지 봉투처럼 내용이 안에 들어 있습니다.

GET 요청:  https://example.com/search?q=프론트엔드&page=1
                                       ↑ 데이터가 URL에 노출

POST 요청: https://example.com/login
           Body: { username: "seojin", password: "1234" }
                  ↑ 데이터가 본문에 숨겨짐

"주소창에 보여도 괜찮으면 GET, 아니면 POST"가 판단 기준입니다. GET은 URL 공유, 북마크, 브라우저 캐싱이 가능하다는 고유한 장점이 있어서 검색과 필터에 적합합니다.

fieldset과 legend

회원가입처럼 입력 필드가 많을 때 "기본 정보", "보안 설정"처럼 관련 항목을 그룹화합니다. 스크린 리더가 legend를 먼저 읽어주기 때문에 접근성에도 도움이 됩니다.

html
<fieldset>
  <legend>기본 정보</legend>
  <label for="name">이름</label>
  <input type="text" id="name" name="name" />
  <label for="email">이메일</label>
  <input type="email" idemail  

CSS 적용 세 가지 방법

인라인 스타일(style=""), 내부 스타일시트(<style>), 외부 스타일 시트(<link>) 중 외부 스타일 시트가 유지보수와 재사용성 면에서 가장 권장됩니다. 인라인 스타일은 우선순위가 1,000점으로 너무 높아 나중에 덮어쓰기가 극도로 어려워지므로 가급적 피합니다.

벤더 프리픽스

-webkit-, -moz- 같은 접두사는 브라우저가 실험적 CSS 기능을 제공할 때 붙입니다. 현재는 Autoprefixer가 빌드 시 자동으로 처리해주므로 직접 작성할 일은 거의 없고, Can I Use 사이트로 브라우저 지원 현황을 확인할 수 있습니다.

CSS 변수 (Custom Properties)

--color-primary: #3b82f6처럼 --로 시작하는 변수를 :root에 정의하고, var(--color-primary)로 참조합니다. 한 곳에서 값을 바꾸면 사용하는 모든 곳에 반영되어 유지보수가 편합니다. 다크 모드 구현에도 활용됩니다.

css
:root {
  --color-primary: #3b82f6;
  --color-text: #1f2937;
}

.btn {
  background: var(--color-primary);
  color: var(--color-text);
}

의사 클래스 (:hover, :focus, :nth-child)

요소의 특정 상태를 선택하는 선택자입니다. 마우스를 올렸을 때(:hover), 키보드 포커스가 갔을 때(:focus), n번째 자식(:nth-child) 등 상태 기반 스타일링에 사용합니다. Tailwind에서는 hover:, focus: 접두사로 적용합니다.

폼 유효성 검증 속성

required(필수 입력), pattern(정규표현식), minlength/maxlength(글자 수 제한) 등 HTML 속성만으로 기본적인 폼 검증이 가능합니다. JavaScript를 한 줄도 쓰지 않고도 "이 칸은 필수입니다"를 구현할 수 있습니다.

html
<!-- 필수 입력 + 최소 8자 + 영문/숫자만 허용 -->
<input
  type="text"
  name="username"
  required
  minlength="8"
  pattern="[a-zA-Z0-9]+"
  title="영문과 숫자만 사용 가능합니다"
/>

2. 저번 주 과제 정답 및 풀이

인증 미션 과제

Q1. 네이버나 구글에서 검색을 해보고, 주소창 URL을 확인해보세요. 어떤 방식(GET/POST)을 사용하고 있나요? 그리고 왜 로그인 폼은 같은 방식을 쓰지 않는다고 생각하나요?

검색은 왜 GET인가?

검색창에 "리액트"를 입력하면 URL이 ?q=리액트처럼 쿼리스트링에 검색어가 붙습니다. 이것이 GET 방식입니다.

  • 북마크·공유 가능: URL 자체가 검색 결과를 담고 있어서 링크를 복사해 공유하면 상대방도 같은 결과를 볼 수 있습니다.
  • 캐싱 가능: 브라우저와 서버가 결과를 캐시해 두어 반복 검색이 빠릅니다.
  • 멱등성: 몇 번을 요청해도 서버 상태가 변하지 않습니다.

로그인은 왜 POST인가?

  • URL 노출 금지: GET을 쓰면 비밀번호가 주소창에 그대로 나타납니다. 브라우저 히스토리, 서버 로그, 공유 화면에 모두 노출됩니다.
  • 서버 상태 변경: 로그인 성공 시 세션이 생성되는 등 서버 상태가 바뀝니다. 상태를 변경하는 요청은 POST가 맞습니다.
  • HTTPS + POST: POST 바디는 HTTPS 암호화 구간에서 보호되어 안전하게 전달됩니다.

Q2. 아래 CSS 코드에서 .btn 텍스트는 최종적으로 무슨 색이 될까요? 그리고 왜 그 색이 적용되는지, 어떤 규칙 때문인지 설명해보세요.

css
p {
  color: green;
}
.btn {
  color: red;
}
#submit {
  color: blue;
}
html
<p class="btn" id="submit">제출</p>

정답: blue

CSS 명시도(Specificity) 규칙에 따라 가장 높은 점수를 가진 선택자가 이깁니다.

  • p (태그 선택자): 명시도 0-0-1 → green
  • .btn (클래스 선택자): 명시도 0-1-0 → red
  • #submit (ID 선택자): 명시도 1-0-0 → blue

ID 선택자가 가장 높은 명시도를 가지므로 최종 색은 blue입니다. 코드에서 선언 순서와 관계없이 명시도가 높은 규칙이 항상 우선합니다.


3. 면접 질문

이번 주 범위와 관련된 실제 면접 질문들을 함께 생각해봅시다. 정답을 외우기보다는 자신만의 말로 설명하는 연습을 해보세요.

Q1. GET과 POST의 차이를 설명해주세요.

둘 다 HTTP 요청 방식이지만, 데이터를 실어 보내는 위치가 다릅니다.

  • GET: 데이터가 URL의 쿼리 스트링에 붙어서 전송됩니다 (?key=value). URL에 노출되므로 민감하지 않은 데이터(검색, 필터링)에 적합합니다. URL 공유, 북마크, 브라우저 캐싱이 가능한 것이 장점입니다.
  • POST: 데이터가 HTTP 요청 본문(Body)에 담겨 전송됩니다. URL에 노출되지 않아 로그인, 회원가입, 결제 등 민감한 정보에 적합합니다.

면접 팁: "주소창에 보여도 괜찮으면 GET, 아니면 POST"라고 정리하면 간결합니다. 다만 POST도 HTTPS 없이는 안전하지 않다는 점도 언급하면 좋습니다.

Q2. CSS 선택자 우선순위(Specificity)를 설명해주세요.

같은 요소에 여러 CSS 규칙이 적용될 때, 어떤 스타일이 최종 적용될지 결정하는 규칙입니다.

  • 우선순위 순서: !important > 인라인 스타일 > 아이디(#) > 클래스(.) / 속성 / 가상 클래스 > 태그 / 가상 요소
  • 같은 우선순위라면 나중에 작성된 스타일이 적용됩니다.
  • !important는 우선순위를 강제로 끌어올리지만, 디버깅이 극도로 어려워지므로 실무에서는 가급적 사용하지 않습니다.

Q3. 박스 모델(Box Model)이 무엇인가요?

모든 HTML 요소는 사각형 상자로 이루어져 있습니다. 이 상자는 안쪽부터 바깥쪽으로 네 영역으로 구성됩니다.

  1. Content: 텍스트, 이미지 등 실제 내용이 들어가는 영역
  2. Padding: 콘텐츠와 테두리 사이의 안쪽 여백 (배경색 적용됨)
  3. Border: 요소의 테두리
  4. Margin: 테두리 바깥쪽의 다른 요소와의 간격 (투명)

box-sizing: content-box(기본값)에서는 width가 콘텐츠 영역만을 의미하고, box-sizing: border-box에서는 padding과 border까지 포함합니다. 현대 웹 개발에서는 border-box를 기본으로 설정하는 것이 표준입니다.

css
/* content-box: width(200) + padding×2(40) + border×2(10) = 실제 250px */
.box {
  width: 200px;
  padding: 20px;
  border: 5px solid;
}

/* border-box: 실제 너비 200px 고정, 콘텐츠 = 200 - 40 - 10 = 150px */
.box {
  box-sizing: border-box;
  width: 200px;
  padding: 20px;
  border: 5px solid;
}

4. 관련 포스팅

  • 프론트엔드 스터디 2주차 학습 자료: 사용자와 소통하는 폼 & CSS의 시작

  • 프론트엔드 스터디 3주차 학습 자료: 프론트엔드의 첫 번째 벽, 박스 모델과 스타일링


5. 다음 주 안내

다음 주에는 CSS의 꽃인 레이아웃을 본격적으로 다룹니다. Position으로 요소를 원하는 위치에 배치하고, Flexbox로 유연한 가로·세로 레이아웃을 구성하는 것이 목표입니다.

다음 주 영상 예습

다음 주 영상(4주차 진도: 04:28:10 ~ 05:28:02)에서는 CSS의 꽃인 레이아웃에 들어갑니다. 영상을 보기 전에 아래 내용을 가볍게 훑어두면 이해가 빠릅니다.

  • 배경 이미지: background-image로 요소에 이미지를 넣고, background-size, background-repeat 등으로 크기와 반복을 제어하는 법을 배웁니다.
  • Position: 요소를 문서 흐름에서 꺼내서 원하는 위치에 배치하는 속성입니다. static, relative, absolute, fixed 네 가지가 나오는데, 각각 "기준점이 어디인가"가 다릅니다.
  • Flexbox: 이번 주 가장 중요한 개념입니다. 자식 요소들을 가로 또는 세로로 유연하게 정렬하는 레이아웃 시스템입니다. 내비게이션 바, 카드 배치, 정중앙 정렬 등 거의 모든 레이아웃에 쓰입니다.

사전 과제

아래 영상의 3주차 진도 구간(02:59:19 ~ 04:28:10)을 시청해주세요. 폰트/색상 제어부터 박스 모델까지의 내용입니다.

  • 영상: 제대로 파는 HTML & CSS
  • 구간: 02:59:19 ~ 04:28:10 (약 1시간 29분)
  • 링크:

    제대로 파는 HTML & CSS - 3주차 진도 시청하기

포스트 목록

/study/clab-26-1/in-person
파일 12개, 폴더 0개
프론트엔드 스터디 대면 1주차: 프론트엔드 개발자란? 그리고 우리가 배울 것들프론트엔드 스터디 대면 2주차: HTML 마크업과 폼, 그리고 CSS의 시작프론트엔드 스터디 대면 3주차: 폼(Form), CSS 선택자, 그리고 박스 모델프론트엔드 스터디 대면 4주차: 박스 모델 실전, Position과 Flexbox프론트엔드 스터디 대면 5주차: 반응형 웹, CSS Grid, 그리고 CSS 마무리프론트엔드 스터디 대면 6주차: JavaScript 시작 — 타입, 변수, 호이스팅프론트엔드 스터디 대면 7주차: 객체, 함수, 스코프프론트엔드 스터디 대면 8주차: 불변성, 프로토타입, 타입 체크프론트엔드 스터디 대면 9주차: 클로저, Promise, async/await프론트엔드 스터디 대면 10주차: React 입문 전 브릿지 — ES6+ 문법과 에러 처리프론트엔드 스터디 대면 11주차: React 첫 번째 — 컴포넌트, JSX, props프론트엔드 스터디 대면 12주차: React 심화 — useState와 useEffect
</label
>
number숫자만 입력 가능숫자 키패드
tel없음 (패턴은 직접 지정)전화번호 키패드
[type="text"]
:hover
태그 / 가상요소1점div, p, ::before
=
"
"
name
="email"
/>
</fieldset>