지난주에는 사용자로부터 정보를 입력받는 폼(Form)과, CSS를 HTML에 연결하고 원하는 요소를 정확히 선택하는 CSS 선택자를 배웠습니다. 이번에는 단순 나열이 아니라, 각 개념이 왜 중요한지와 실수하기 쉬운 포인트를 중심으로 복습하겠습니다.
form 태그와 action, methodform 태그는 입력 요소들을 감싸는 컨테이너입니다. 택배 상자
에 비유하면, action은 배송지 주소이고 method는
배송 방법(일반 택배 vs 등기)입니다. 상자 없이 물건만 던지면
배달이 안 되듯, form 없이 input만 있으면 데이터가 서버로 전송되지
않습니다.
<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의 값은
서버에 전혀 전달되지 않습니다.
<!-- name이 없어서 서버로 전송 안 됨 -->
<input type="text" />
<!-- name이 있어서 서버에 username=입력값 형태로 전송 -->
<input type="text" name="username" />
흔한 실수: 폼을 만들고 "서버에 데이터가 안 들어와요"라고 할
때, 가장 먼저 확인할 것이 name 속성 누락입니다. 개발자 도구의 Network 탭에서
Form Data를 보면 바로 확인할 수 있습니다.
label과 for — 클릭 면적을 넓혀주는 연결label의 for 값과 input의 id 값을 일치시키면,
label 텍스트를 클릭해도 input에 포커스가 이동합니다.
데스크톱에서는 편의 기능이지만, 모바일에서는
터치 영역이 넓어지는 실질적인 UX 개선입니다. 작은 체크박스나
라디오 버튼을 손가락으로 정확히 누르기 어려운 상황을 떠올려보세요.
<!-- 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, textareaselect는 드롭다운 선택, textarea는 여러 줄 텍스트 입력을 담당합니다.
input은 한 줄 입력에 특화되어 있으므로, 긴 텍스트(자기소개, 문의 내용
등)에는 반드시 textarea를 사용합니다.
textarea를 쓸 때 한 가지 주의할 점이 있습니다. 아래처럼 태그 사이에
줄바꿈이나 들여쓰기를 넣으면, 그 공백이
초기값으로 그대로 들어갑니다.
<!-- 태그 사이의 개행/공백이 초기값으로 들어감 -->
<textarea name="content"> </textarea>
<!-- 이렇게 붙여써야 빈 상태로 시작함 -->
<textarea name="content"></textarea>
이 문제는 input에서는 발생하지 않습니다. input은 자기 닫힘 태그이고 값은
value 속성으로 지정하기 때문입니다. textarea는 태그 사이의 내용이 곧
값이기 때문에 개행 문자가 끼어들 수 있습니다.
나중에 JavaScript를 배우면 이런 상황에서 trim()이라는
메서드를 자주 쓰게 됩니다. trim()은 문자열 앞뒤의 공백과 개행을 제거합니다.
const value = document.querySelector("textarea").value;
// 사용자가 입력한 값: "\n안녕하세요\n"
console.log(value.trim()); // "안녕하세요"
왜 제거해야 할까요? 앞뒤 공백이 붙은 채로 서버에 저장되면, 나중에 값을
비교하거나 검색할 때 오작동이 생깁니다. 예를 들어 사용자가 아이디로 "seojin"을
입력했는데 실제로 저장된 값이 " seojin"이라면, 로그인 비교 시 불일치가
발생합니다. 사용자 입장에서는 올바르게 입력했는데 오류가 나는 것처럼 보이죠.
trim()은 이런
눈에 보이지 않는 공백 버그를 예방하는 가장 기본적인 습관
입니다.
/* 태그 선택자: 해당 태그 전부 선택 */
p {
color: black;
}
/* 클래스 선택자: 마침표(.)로 시작, 여러 요소에 재사용 가능 */
.highlight {
background: yellow;
}
/* 아이디 선택자: 샵(#)으로 시작, 페이지에서 딱 하나 */
#main-title {
font-size: 2rem;
}
헷갈리는 포인트: 클래스는 한 요소에 여러 개 붙일 수 있지만
(class="btn btn-primary"), 아이디는 페이지에서 단 하나여야
합니다. 아이디가 중복되면 HTML 문법 위반이고, JavaScript의 getElementById도
첫 번째 요소만 반환합니다.
<nav>
<ul>
<li><a href="/">홈</a></li>
<li>
<div>
<a href="/about">소개</a>
</div>
</li>
</ul>
</nav>
/* 자손 결합자: nav 안의 모든 a를 선택 (깊이 무관) */
nav a {
color: white;
}
/* 자식 결합자: nav의 직계 자식 a만 선택 (여기서는 해당 없음) */
nav > a {
color: white;
}
위 HTML에서 nav a는 "홈"과 "소개" 링크 모두 선택합니다.
하지만 nav > a는 nav의 직계 자식인 a가 없으므로 아무것도
선택하지 않습니다. 자손 결합자(공백)는 "안에 있는 모든 것",
자식 결합자(>)는 "바로 아래 한 단계"라고 기억하세요.
CSS 우선순위는 점수 계산으로 이해하면 직관적입니다. 같은 요소에 여러 규칙이 적용될 때, 점수가 높은 쪽이 이깁니다.
| 종류 | 점수 | 예시 |
|---|---|---|
| 인라인 스타일 | 1,000점 | style="color: red" |
| 아이디(#) | 100점 | #header |
| 클래스(.) / 속성 / 가상클래스 | 10점 | .btn, , |
/* 점수: 태그(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은 엽서처럼 내용이 겉면(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를 먼저 읽어주기 때문에 접근성에도 도움이
됩니다.
<fieldset>
<legend>기본 정보</legend>
<label for="name">이름</label>
<input type="text" id="name" name="name" />
<label for="email">이메일</label>
<input type="email" idemail
인라인 스타일(style=""), 내부 스타일시트(<style>), 외부 스타일
시트(<link>) 중 외부 스타일 시트가 유지보수와
재사용성 면에서 가장 권장됩니다. 인라인 스타일은 우선순위가 1,000점으로 너무
높아 나중에 덮어쓰기가 극도로 어려워지므로 가급적 피합니다.
-webkit-, -moz- 같은 접두사는 브라우저가 실험적 CSS 기능을 제공할 때
붙입니다. 현재는 Autoprefixer가 빌드 시 자동으로 처리해주므로 직접 작성할 일은
거의 없고, Can I Use 사이트로 브라우저 지원 현황을 확인할 수 있습니다.
--color-primary: #3b82f6처럼 --로 시작하는 변수를 :root에 정의하고,
var(--color-primary)로 참조합니다. 한 곳에서 값을 바꾸면 사용하는 모든 곳에
반영되어 유지보수가 편합니다. 다크 모드 구현에도 활용됩니다.
: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를 한 줄도 쓰지 않고도 "이 칸은 필수입니다"를 구현할 수 있습니다.
<!-- 필수 입력 + 최소 8자 + 영문/숫자만 허용 -->
<input
type="text"
name="username"
required
minlength="8"
pattern="[a-zA-Z0-9]+"
title="영문과 숫자만 사용 가능합니다"
/>
Q1. 네이버나 구글에서 검색을 해보고, 주소창 URL을 확인해보세요. 어떤 방식(GET/POST)을 사용하고 있나요? 그리고 왜 로그인 폼은 같은 방식을 쓰지 않는다고 생각하나요?
검색창에 "리액트"를 입력하면 URL이
?q=리액트처럼 쿼리스트링에 검색어가 붙습니다. 이것이 GET
방식입니다.
Q2. 아래 CSS 코드에서 .btn 텍스트는 최종적으로 무슨 색이 될까요? 그리고 왜
그 색이 적용되는지, 어떤 규칙 때문인지 설명해보세요.
p {
color: green;
}
.btn {
color: red;
}
#submit {
color: blue;
}
<p class="btn" id="submit">제출</p>
CSS 명시도(Specificity) 규칙에 따라 가장 높은 점수를 가진 선택자가 이깁니다.
p (태그 선택자): 명시도 0-0-1 → green.btn (클래스 선택자): 명시도 0-1-0 → red#submit (ID 선택자): 명시도 1-0-0 → blueID 선택자가 가장 높은 명시도를 가지므로 최종 색은 blue입니다. 코드에서 선언 순서와 관계없이 명시도가 높은 규칙이 항상 우선합니다.
이번 주 범위와 관련된 실제 면접 질문들을 함께 생각해봅시다. 정답을 외우기보다는 자신만의 말로 설명하는 연습을 해보세요.
둘 다 HTTP 요청 방식이지만, 데이터를 실어 보내는 위치가 다릅니다.
?key=value). URL에 노출되므로 민감하지 않은 데이터(검색, 필터링)에
적합합니다. URL 공유, 북마크, 브라우저 캐싱이 가능한 것이 장점입니다.면접 팁: "주소창에 보여도 괜찮으면 GET, 아니면 POST"라고 정리하면 간결합니다. 다만 POST도 HTTPS 없이는 안전하지 않다는 점도 언급하면 좋습니다.
Q2. CSS 선택자 우선순위(Specificity)를 설명해주세요.
같은 요소에 여러 CSS 규칙이 적용될 때, 어떤 스타일이 최종 적용될지 결정하는 규칙입니다.
!important > 인라인 스타일 > 아이디(#) >
클래스(.) / 속성 / 가상 클래스 > 태그 / 가상 요소!important는 우선순위를 강제로 끌어올리지만, 디버깅이 극도로 어려워지므로
실무에서는 가급적 사용하지 않습니다.모든 HTML 요소는 사각형 상자로 이루어져 있습니다. 이 상자는 안쪽부터 바깥쪽으로 네 영역으로 구성됩니다.
box-sizing: content-box(기본값)에서는 width가 콘텐츠 영역만을 의미하고,
box-sizing: border-box에서는 padding과 border까지 포함합니다. 현대 웹
개발에서는 border-box를 기본으로 설정하는 것이 표준입니다.
/* 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;
}
다음 주에는 CSS의 꽃인 레이아웃을 본격적으로 다룹니다. Position으로 요소를 원하는 위치에 배치하고, Flexbox로 유연한 가로·세로 레이아웃을 구성하는 것이 목표입니다.
다음 주 영상(4주차 진도: 04:28:10 ~ 05:28:02)에서는 CSS의 꽃인 레이아웃에 들어갑니다. 영상을 보기 전에 아래 내용을 가볍게 훑어두면 이해가 빠릅니다.
background-image로 요소에 이미지를 넣고,
background-size, background-repeat 등으로 크기와 반복을 제어하는 법을
배웁니다.static, relative, absolute, fixed 네 가지가 나오는데, 각각
"기준점이 어디인가"가 다릅니다.아래 영상의 3주차 진도 구간(02:59:19 ~ 04:28:10)을 시청해주세요. 폰트/색상 제어부터 박스 모델까지의 내용입니다.
제대로 파는 HTML & CSS - 3주차 진도 시청하기
number | 숫자만 입력 가능 | 숫자 키패드 |
tel | 없음 (패턴은 직접 지정) | 전화번호 키패드 |
[type="text"]:hover| 태그 / 가상요소 | 1점 | div, p, ::before |