문서 읽는 데 54분 · A4

A-4: 폼과 입력

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

안녕하세요, 홍순구 튜터입니다. 벌써 네 번째 시간이에요!

지난 시간까지 우리가 만든 페이지들을 떠올려볼까요? 사진을 띄우고, 동영상을 재생하고, 표를 그렸어요. 그런데 이 모든 건 화면을 읽고 보는 일이었죠. 사용자는 그냥 구경만 할 수 있었어요.

진짜 인스타그램은 어떤가요? 로그인하려면 아이디와 비밀번호를 입력하고, 마음에 드는 사진에는 댓글을 달고, 좋은 게시물은 친구에게 공유하잖아요. 사용자가 직접 무언가를 적고, 고르고, 누르는 거예요.

오늘은 바로 그 "사용자가 직접 입력하는 페이지"를 만듭니다. 주인공은 폼(Form) 이에요.

지난 시간 index.html에 남겨둔 "비밀번호를 잊으셨나요?" 링크 기억하시죠? 그 옆에 드디어 진짜 로그인 폼이 들어옵니다. feed.html의 게시물 아래에는 댓글 입력칸이 생기고, 공유 버튼을 누르면 떠오르는 공유 다이얼로그도 만들어요. 그리고 profile.html에는 이름·이메일·생일·테마 색상까지 고르는 프로필 편집 폼을 추가합니다.

오늘 수업이 끝나면, 여러분의 인스타그램은 처음으로 사용자와 대화하기 시작해요.

💡 오늘 수업의 핵심 — "폼으로 사용자의 입력을 받아내고, 브라우저가 알아서 입력을 검사하게 만든다" 🎯

🎯 학습 목표

  • <form> 으로 여러 입력칸을 하나로 묶어, 사용자가 적은 내용을 서버로 보낼 준비를 합니다.
  • <input><label> 을 연결해서, 누구나 무엇을 적는 칸인지 알 수 있는 입력칸을 만듭니다.
  • type 속성 하나만 바꿔서 이메일·비밀번호·날짜·색상·범위 등 다양한 입력 모드를 꺼내 씁니다.
  • required · minlength · pattern 같은 유효성 검사 속성으로, 잘못된 입력을 브라우저가 직접 막게 합니다.
  • <textarea> · <select> · <fieldset> 으로 댓글·선택지·그룹 입력을 구성합니다.
  • <dialog> 와 최신 버튼 속성으로 자바스크립트 없이 공유 모달 창을 띄웁니다.
  • Popover API로 "더보기" 같은 가벼운 떠있는 메뉴를 만듭니다.
  • 라벨 · autocomplete · 키보드 탐색으로 누구나 편하게 쓸 수 있는 접근성 좋은 폼을 완성합니다.

오늘은 새로 배울 태그가 많아요. 하지만 걱정 마세요. 폼은 "입력칸을 만들고 → 의미를 붙이고 → 검사를 건다"는 한 가지 흐름의 반복이거든요. 이 흐름만 손에 익으면 나머지는 응용일 뿐이에요. 그럼 첫 입력칸부터 만들어볼까요?


Step 1: "<form>의 등장 — 입력을 하나로 묶는 봉투"

자, 폼이 정확히 뭘까요? 어렵게 생각할 필요 없어요. 폼은 사용자가 적은 내용을 한 덩어리로 묶어서 어딘가로 보내는 봉투예요.

은행에서 계좌를 만들 때를 떠올려보세요. 이름 칸, 주민번호 칸, 주소 칸이 있는 신청서 한 장을 받죠. 칸을 다 채우고 직원에게 제출하면, 그 신청서가 통째로 전산실로 넘어가요. 웹의 폼이 딱 이 신청서예요.

        ┌─────────── <form> (신청서 한 장) ───────────┐
        │                                              │
        │   사용자 이름:  [ jaehoon          ]         │
        │   비밀번호:     [ ********         ]         │
        │                                              │
        │              [  로그인  ] ← 제출 버튼        │
        └──────────────────────────────────────────────┘
                              │
                              │ 제출하면 묶음째로
                              ▼
                        서버로 전송 ✉️

입력칸 하나하나가 따로 노는 게 아니라, <form>이라는 봉투가 이들을 감싸서 한 번에 보낸다는 점이 핵심이에요.

우리 로그인 폼의 뼈대

지난 시간 index.html에 "로그인 폼은 다음에 추가합니다"라고 주석만 남겨뒀던 곳, 기억나시죠? 거기에 들어갈 폼의 뼈대를 먼저 봅시다.

<!-- instagram-clone-frontend/index.html -->
<form action="/login" method="post">
  <!-- 입력칸들은 다음 Step에서 채웁니다 -->
  <button type="submit">로그인</button>
</form>

<form>은 여는 태그와 닫는 태그가 있는 평범한 태그예요. 이 안에 들어가는 모든 입력칸이 "하나의 봉투"로 묶입니다. 그런데 처음 보는 속성이 둘 보이네요. actionmethod예요.

action — 어디로 보낼까

action봉투를 보낼 주소예요. 사용자가 로그인 버튼을 누르면, 입력한 내용이 action에 적힌 주소로 날아갑니다. 우리는 /login이라고 적었어요.

아직 서버를 배우지 않았으니 지금은 "입력을 받아줄 목적지 주소" 정도로만 이해해도 충분해요. 실제로 이 주소에서 로그인을 처리하는 건 한참 뒤, 백엔드를 배울 때 만나게 됩니다.

method — 어떻게 보낼까

method보내는 방식이에요. 크게 두 가지가 있어요.

method 비유 특징
get 엽서 입력 내용이 주소창에 그대로 보임. 검색어처럼 숨길 필요 없는 것에
post 밀봉 봉투 입력 내용이 주소창에 안 보임. 비밀번호처럼 민감한 것에

로그인은 비밀번호를 다루니까 당연히 밀봉 봉투, 즉 method="post"를 써요. 검색창처럼 "뭘 찾는지 주소에 드러나도 괜찮은" 경우엔 get을 씁니다.

type="submit" — 봉투를 부치는 버튼

폼 안의 <button type="submit">봉투를 부치는 버튼이에요. 이 버튼을 누르는 순간 폼이 action 주소로 제출됩니다. 버튼 글자("로그인")는 마음대로 바꿀 수 있어요.

🙋 학생 질문 — "튜터님, `action`을 비워두면 어떻게 되나요?"

좋은 질문이에요! action을 적지 않거나 비워두면, 폼은 지금 보고 있는 바로 그 페이지로 제출돼요. 즉 index.html에서 제출하면 index.html로 다시 보내지는 거죠.

지금 우리는 받아줄 서버가 없으니, 로그인 버튼을 눌러도 페이지가 새로고침되면서 입력한 내용이 사라질 거예요. 이건 정상이에요. "입력을 받아서 진짜로 처리하는" 일은 자바스크립트(뒤에 나올 C 모듈)와 백엔드의 몫이거든요. 지금은 폼의 모양과 구조를 만드는 데 집중해요.

💡 튜터의 결론: <form>은 입력칸들을 묶는 봉투예요. action은 보낼 주소, method는 보내는 방식(post는 밀봉, get은 엽서). 그리고 type="submit" 버튼이 봉투를 부치는 역할을 해요. 봉투는 준비됐으니, 다음 Step에서 그 안에 진짜 입력칸을 채워볼게요.


Step 2: "<input><label> — 입력칸과 이름표"

봉투를 만들었으니 이제 입력칸을 채워봅시다. 사용자가 글자를 적는 칸을 만드는 태그가 바로 <input>이에요.

<input>은 빈 요소예요

<input>은 지난 시간에 배운 <img>처럼 닫는 태그가 없는 빈 요소예요. 한 줄로 끝나죠. 우리 로그인 폼의 입력칸을 봅시다.

<!-- instagram-clone-frontend/index.html -->
<p>
  <label for="username">사용자 이름</label>
  <input type="text" id="username" name="username"
         placeholder="아이디 또는 이메일"
         autocomplete="username"
         required minlength="3" maxlength="20">
</p>

라벨과 입력칸을 <p>로 감싼 게 보이죠? 이렇게 묶으면 입력칸이 한 줄씩 차곡차곡 쌓여요. 아래에서 입력칸을 더 추가할 때도 같은 방식으로 <p>로 하나씩 감싸면 됩니다.

속성이 많아 보이지만, 하나씩 보면 어렵지 않아요.

속성
type="text" 어떤 종류의 입력칸인지 (글자 입력) — 다음 Step의 주인공
id="username" 이 칸의 고유 이름표 (라벨이 찾아올 주소)
name="username" 서버로 보낼 때 붙는 꼬리표 (이 칸의 값이 무슨 데이터인지)
placeholder="..." 칸이 비었을 때 흐리게 보이는 안내 문구

autocomplete, required, minlength는 뒤에서 따로 다룰게요. 지금은 idname에 집중해요. 둘 다 "이름"인데 역할이 달라요. idHTML 안에서 이 칸을 가리키는 이름이고, name서버로 보낼 때 붙는 이름이에요.

<label> — 입력칸에 붙이는 이름표

<input>만 덜렁 있으면 사용자는 이 칸이 뭘 적는 곳인지 알 수 없어요. 그래서 <label>로 "이름표"를 붙여줍니다.

핵심은 <label>for<input>id똑같은 값으로 맞추는 것이에요.

   <label for="username">          <input id="username">
            │                                │
            └────── for == id 로 연결 ────────┘
         "이 이름표는 username 칸의 것이다"

for="username"id="username"이 같은 값이라서, 브라우저는 이 이름표가 어느 칸의 것인지 정확히 알아요. 이렇게 연결하면 두 가지 좋은 일이 생겨요.

  • 라벨 글자를 클릭해도 입력칸이 활성화돼요. "사용자 이름"이라는 글자를 눌러도 커서가 칸으로 쏙 들어가죠. 모바일에서 손가락으로 누를 영역이 넓어지는 거예요.
  • 화면을 못 보는 사용자에게 의미가 전달돼요. 지난 시간 alt로 이미지에 의미를 담았던 것처럼, <label>은 입력칸에 의미를 담아요.

라벨로 감싸는 방법도 있어요

forid로 연결하는 대신, <label><input>통째로 감싸는 방법도 있어요. 로그인 정보 저장 체크박스가 이 방식이에요.

<label>
  <input type="checkbox" name="remember" value="yes">
  로그인 정보 저장
</label>

감싸는 방식은 for/id를 안 적어도 자동으로 연결돼요. 체크박스나 라디오 버튼처럼 글자가 짧을 때 편해요.

🌟 추천: 둘 중 뭘 써도 되지만, 입력칸이 많은 폼에서는 for/id 방식이 더 명확해요. 라벨과 입력칸이 화면에서 떨어져 있어도 연결이 유지되거든요.

💡 튜터의 결론: <input>은 입력칸, <label>은 그 칸의 이름표예요. forid를 같은 값으로 맞춰 연결하거나, <label>로 감싸서 연결해요. 라벨은 "있으면 좋은" 게 아니라 "반드시 있어야 하는" 거예요. 라벨 없는 입력칸은 이름표 없는 서랍과 같아요.


Step 3: "type 하나로 변신하는 입력칸"

방금 본 type="text"type을 바꾸면, 같은 <input> 태그가 완전히 다른 입력칸으로 변신해요. 이게 폼의 가장 재밌는 부분이에요.

프로필 편집 폼을 만들면서 여러 타입을 직접 써봅시다.

<!-- instagram-clone-frontend/profile.html -->
<p>
  <label for="email">이메일</label>
  <input type="email" id="email" name="email"
         placeholder="you@example.com"
         autocomplete="email" required>
</p>
<p>
  <label for="phone">휴대폰</label>
  <input type="tel" id="phone" name="phone"
         placeholder="010-1234-5678"
         pattern="010-[0-9]{4}-[0-9]{4}">
</p>
<p>
  <label for="website">웹사이트</label>
  <input type="url" id="website" name="website"
         placeholder="https://my-blog.com">
</p>
<p>
  <label for="birthday">생일</label>
  <input type="date" id="birthday" name="birthday">
</p>

타입마다 입력칸의 생김새와 동작이 달라져요.

type 입력칸 모습 특별한 점
text 평범한 글자 칸 기본값
password 입력한 글자가 ●●● 로 가려짐 비밀번호용
email 글자 칸 @가 없으면 제출 때 막아줌
tel 글자 칸 모바일에서 숫자 키패드가 뜸
url 글자 칸 http로 시작 안 하면 막아줌
date 달력 선택기 클릭하면 달력이 펼쳐짐
color 색상 팔레트 클릭하면 색 고르는 창
range 슬라이더 막대 드래그로 숫자 선택
number 숫자 칸 위아래 화살표로 증감

특히 모바일에서 차이가 확 느껴져요. type="email"이면 키보드에 @가 바로 보이고, type="tel"이면 숫자 키패드가 떠요. 사용자가 입력하기 훨씬 편해지죠.

색상과 범위도 입력칸이에요

글자만 입력칸이 아니에요. 색을 고르고 숫자를 슬라이더로 정하는 것도 <input>이에요.

<label for="theme-color">테마 색상</label>
<input type="color" id="theme-color" name="themeColor" value="#e1306c">

<label for="post-goal">하루 게시물 목표 (1~10장)</label>
<input type="range" id="post-goal" name="postGoal"
       min="1" max="10" value="3">

type="color"를 클릭하면 색 고르는 창이 뜨고, type="range"는 드래그하는 막대가 나와요. value는 처음 보여줄 기본값이에요.

🙋 학생 질문 — "튜터님, 직접 바꿔보고 싶어요. 뭘 실험하면 좋을까요?"

프론트엔드의 최고 장점은 바로 보인다는 거예요! 이런 실험을 해보세요.

  1. type="date"type="datetime-local"로 바꿔보세요. 날짜만 고르던 게 시간까지 고르게 변해요.
  2. type="range"min0, max100으로 바꿔보세요. 슬라이더가 움직이는 범위가 달라져요.
  3. type="email" 칸에 @ 없이 아무 글자나 넣고 제출 버튼을 눌러보세요. 브라우저가 "@를 포함해 주세요" 하고 막아줄 거예요. (이건 다음 Step의 예고편이에요!)

값을 바꾸고 저장한 뒤 화면이 어떻게 달라지는지 눈으로 확인하는 게 폼과 친해지는 가장 빠른 길이에요.

💡 튜터의 결론: <input> 하나가 type 값에 따라 글자칸·비밀번호칸·달력·색상 팔레트·슬라이더로 변신해요. 적절한 타입을 골라주면 사용자가 입력하기 편해지고, 브라우저가 입력을 검사해주기까지 해요. 그 "검사" 이야기를 다음 Step에서 본격적으로 해봅시다.


Step 4: "유효성 검사 — 브라우저가 대신 막아준다"

폼을 만들다 보면 이런 걱정이 들어요. "사용자가 비밀번호를 한 글자만 적으면? 이메일 칸에 엉뚱한 글자를 넣으면?" 옛날에는 이런 검사를 전부 직접 코드로 짜야 했어요. 그런데 지금은 <input>속성 몇 개만 붙이면 브라우저가 알아서 막아줘요.

우리 로그인 폼으로 돌아가 봅시다.

<!-- instagram-clone-frontend/index.html -->
<input type="password" id="password" name="password"
       placeholder="비밀번호 (8자 이상)"
       autocomplete="current-password"
       required minlength="8">

여기 requiredminlength="8"이 보이죠? 이게 유효성 검사 속성이에요.

속성 어기면?
required 반드시 채워야 함 비어 있으면 제출 막힘
minlength="8" 최소 8글자 짧으면 제출 막힘
maxlength="20" 최대 20글자 더 못 적게 막음
pattern="..." 정해진 형식 형식이 다르면 제출 막힘
min / max 숫자의 최소/최대 범위 밖이면 막힘

직접 어겨보세요

required가 붙은 비밀번호 칸을 비워둔 채로 로그인 버튼을 눌러보세요. 그러면 브라우저가 빨간 말풍선으로 "이 입력란을 작성하세요" 하고 막아줘요. 코드를 한 줄도 더 안 짰는데 말이죠.

  ┌──────────────────────────────┐
  │ 비밀번호 [            ]       │
  └──────────────────────────────┘
        ▲
        └── ⚠️ "이 입력란을 작성하세요"
            (required 인데 비어 있어서 브라우저가 자동으로 띄움)

pattern — 정해진 형식 강제하기

휴대폰 번호처럼 "형식이 정해진" 입력은 pattern으로 검사해요. 프로필 편집 폼의 휴대폰 칸을 다시 봅시다.

<input type="tel" id="phone" name="phone"
       placeholder="010-1234-5678"
       pattern="010-[0-9]{4}-[0-9]{4}">

pattern="010-[0-9]{4}-[0-9]{4}"는 "010-숫자4개-숫자4개" 형식이라는 뜻이에요. [0-9]는 숫자 한 개, {4}는 네 번 반복이라는 약속이에요. 이 형식을 안 지키면 제출이 막혀요. 지금은 깊이 파고들 필요 없어요. "정해진 글자 형식을 강제할 수 있다"는 것만 기억하면 돼요.

placeholder는 라벨이 아니에요

여기서 자주 하는 실수 하나를 짚고 갈게요. placeholder(흐린 안내 문구)를 라벨 대신 쓰면 안 돼요.

⚠️ 주의: placeholder는 입력을 시작하면 사라져요. 그래서 사용자가 "내가 지금 뭘 적는 칸이었지?" 하고 헷갈리기 쉬워요. 또 화면을 못 보는 사용자에게는 라벨만큼 확실하게 전달되지 않아요. placeholder는 어디까지나 예시 힌트, 진짜 이름표는 <label>이에요.

💡 튜터의 결론: required · minlength · maxlength · pattern · min · max 속성만 붙이면, 잘못된 입력을 브라우저가 제출 전에 막아줘요. 자바스크립트 없이도 기본 검사가 되는 거예요. 단, placeholder는 라벨의 대체품이 아니라는 것을 꼭 기억하세요.


Step 5: "댓글·선택·그룹 — 폼을 구성하는 나머지 태그들"

<input> 말고도 폼에는 자주 쓰는 태그가 더 있어요. 댓글처럼 긴 글, 카테고리처럼 고르는 입력, 그리고 입력칸을 묶는 그룹이에요.

<textarea> — 여러 줄 입력

<input type="text">는 한 줄짜리예요. 댓글이나 자기소개처럼 여러 줄을 적으려면 <textarea>를 써요. 지난 시간 예고했던 댓글 입력칸을 feed.html에 만들어봅시다.

<!-- instagram-clone-frontend/feed.html -->
<form action="/comments" method="post">
  <label for="comment-1">댓글 달기</label>
  <textarea id="comment-1" name="comment" rows="2"
            placeholder="댓글을 입력하세요..."
            maxlength="200" required></textarea>
  <button type="submit">게시</button>
</form>

<textarea><input>과 달리 여는 태그와 닫는 태그가 있어요. rows="2"는 처음 보일 줄 수예요. maxlength="200"으로 댓글을 200자까지만 받게 막았어요. Step 4에서 배운 유효성 속성이 여기서도 그대로 쓰이죠.

<select><option> — 정해진 보기 중 고르기

카테고리처럼 "정해진 보기 중 하나를 고르는" 입력은 <select>를 써요. 프로필 편집 폼의 카테고리 칸이에요.

<!-- instagram-clone-frontend/profile.html -->
<label for="category">카테고리</label>
<select id="category" name="category">
  <option value="">선택하세요</option>
  <option value="dev" selected>개발</option>
  <option value="travel">여행</option>
  <option value="food">맛집</option>
  <option value="daily">일상</option>
</select>

<select>가 드롭다운 메뉴고, 그 안의 <option> 하나하나가 보기예요. selected를 붙인 보기가 처음부터 골라져 있어요. 여기서는 "개발"이 기본 선택이죠.

<fieldset><legend> — 입력칸을 그룹으로 묶기

프로필 편집 폼처럼 입력칸이 많아지면, 관련 있는 것끼리 묶어주면 보기 좋아요. 그 묶음이 <fieldset>, 묶음의 제목이 <legend>예요.

<fieldset>
  <legend>기본 정보</legend>
  <!-- 이름, 이메일, 휴대폰 등 -->
</fieldset>

<fieldset>
  <legend>꾸미기</legend>
  <!-- 카테고리, 테마 색상, 소개 등 -->
</fieldset>

<fieldset>은 입력칸 묶음 둘레에 테두리를 그려주고, <legend>는 그 테두리에 제목을 박아줘요. "기본 정보"와 "꾸미기"가 시각적으로 나뉘니 사용자가 폼을 이해하기 쉬워져요.

버튼의 세 가지 type

폼 안의 버튼에는 세 종류가 있어요. 프로필 편집 폼 끝을 보면 두 개가 보이죠.

<button type="submit">저장하기</button>
<button type="reset">되돌리기</button>
type 하는 일
submit 폼을 제출함 (봉투를 부침)
reset 폼의 모든 입력을 처음 상태로 되돌림
button 아무것도 안 함 (다른 동작을 붙일 때 — 뒤에서 등장)

⚠️ 주의: 폼 안의 버튼은 type을 안 적으면 자동으로 submit이 돼요. 그래서 제출이 아닌 버튼(예: 다음 Step의 "더보기")에는 type="button"을 꼭 붙여줘야 엉뚱하게 폼이 제출되는 사고를 막아요.

💡 튜터의 결론: 여러 줄은 <textarea>, 정해진 보기 중 고르기는 <select>+<option>, 입력칸 묶기는 <fieldset>+<legend>. 버튼은 submit·reset·button 세 종류고, 제출용이 아니면 type="button"을 꼭 붙이세요. 이제 폼의 기본 부품은 다 모았어요. 다음 두 Step에서는 인스타그램에서 자주 보는 떠오르는 창을 만들어봅니다.


Step 6: "<dialog> — 공유 모달 창 띄우기"

인스타그램에서 공유 버튼을 누르면, 화면 가운데에 작은 창이 떠오르면서 뒤 배경이 살짝 어두워지죠. 이런 창을 모달(modal) 이라고 불러요. "이 창을 닫기 전까진 뒤를 못 만지게" 막는 창이에요.

예전에는 이런 모달을 만들려면 복잡한 코드가 필요했어요. 그런데 지금은 HTML에 <dialog>라는 전용 태그가 있어요.

공유 다이얼로그 만들기

feed.html에 공유 다이얼로그를 만들어봅시다.

<!-- instagram-clone-frontend/feed.html -->
<dialog id="shareDialog">
  <h2>공유하기</h2>
  <p>이 게시물을 친구에게 보내보세요.</p>

  <form action="/share" method="post">
    <label for="share-to">받는 사람</label>
    <input type="text" id="share-to" name="shareTo"
           placeholder="친구 아이디" required>
    <button type="submit">보내기</button>
  </form>

  <form method="dialog">
    <button type="submit">닫기</button>
  </form>
</dialog>

<dialog> 안에는 평범한 HTML을 뭐든 넣을 수 있어요. 여기서는 제목, 안내 문구, 그리고 받는 사람을 입력하는 폼을 넣었죠. 다이얼로그는 평소엔 화면에 숨어 있다가, 버튼을 누를 때 떠올라요.

버튼으로 여는 법 — JavaScript 없이!

그럼 이 숨어 있는 다이얼로그를 어떻게 띄울까요? 게시물의 공유 버튼을 봅시다.

<button type="button" command="show-modal" commandfor="shareDialog">공유</button>

여기 commandcommandfor가 핵심이에요. 2026년부터 모든 주요 브라우저에서 쓸 수 있게 된 최신 기능이에요.

  • commandfor="shareDialog" — "어떤 요소를 조작할까?" → idshareDialog인 다이얼로그
  • command="show-modal" — "무슨 동작을 할까?" → 모달로 띄워라

즉 이 버튼은 "shareDialog를 모달로 띄워라"라고 선언하는 거예요. 자바스크립트 한 줄 없이 버튼만으로 모달이 떠요. 예전에는 이 동작에 꼭 자바스크립트가 필요했는데, 이제 HTML만으로 되는 거예요.

닫기 — <form method="dialog">

닫는 것도 코드 없이 돼요. 다이얼로그 안의 닫기 버튼을 보세요.

<form method="dialog">
  <button type="submit">닫기</button>
</form>

<form method="dialog"> 안의 버튼을 누르면, 폼을 어디로 보내는 게 아니라 다이얼로그를 닫아요. Step 1에서 배운 methoddialog라는 특별한 값이 하나 더 있는 셈이죠.

🙋 학생 질문 — "튜터님, 뒤 배경이 어두워지는 효과는 어디서 나오나요?"

좋은 관찰이에요! 모달을 띄우면 뒤 배경이 어둑하게 깔리는 부분, 그걸 backdrop(배경막) 이라고 해요. <dialog>show-modal로 띄우면 이 배경막이 자동으로 생겨요.

다만 배경막의 색이나 투명도를 꾸미는 건 CSS의 일이에요. 우리는 아직 CSS를 안 배웠으니, 지금은 브라우저 기본 모습 그대로 보일 거예요. 다음 시간부터 CSS를 배우면 이 모달을 인스타그램처럼 예쁘게 단장할 수 있어요.

💡 튜터의 결론: <dialog>는 모달 창 전용 태그예요. command="show-modal"commandfor를 버튼에 붙이면 자바스크립트 없이 모달을 띄우고, <form method="dialog">로 닫아요. "떠오르는 창"이 HTML만으로 완성되는 거예요.


Step 7: "Popover API — 가벼운 더보기 메뉴"

다이얼로그가 "이 창을 닫기 전엔 뒤를 못 만지는" 무거운 창이라면, 인스타그램의 "⋯ 더보기" 메뉴처럼 가볍게 떴다가 바깥을 누르면 사라지는 창도 있어요. 이런 가벼운 떠있는 메뉴는 Popover(팝오버) 로 만들어요.

더보기 메뉴 만들기

feed.html에 게시물 더보기 메뉴를 만들어봅시다.

<!-- instagram-clone-frontend/feed.html -->
<div popover id="postMenu">
  <p>게시물 메뉴</p>
  <ul>
    <li><a href="#report">신고하기</a></li>
    <li><a href="#unfollow">팔로우 취소</a></li>
    <li><a href="#copy-link">링크 복사</a></li>
  </ul>
  <button type="button" popovertarget="postMenu" popovertargetaction="hide">닫기</button>
</div>

평범한 <div>popover라는 속성 하나만 붙이면, 이 요소는 평소엔 숨어 있다가 필요할 때 떠오르는 팝오버가 돼요.

버튼으로 열기 — popovertarget

팝오버를 여는 버튼은 게시물에 있어요.

<button type="button" popovertarget="postMenu">⋯ 더보기</button>

popovertarget="postMenu"는 "이 버튼을 누르면 idpostMenu인 팝오버를 토글해라"라는 뜻이에요. 누르면 뜨고, 다시 누르면 사라지죠. 이것도 자바스크립트 없이 돼요.

팝오버의 가장 편한 점은 바깥을 클릭하면 저절로 닫힌다는 거예요. 메뉴를 띄워두고 다른 곳을 누르면 알아서 사라져요. 닫기 버튼의 popovertargetaction="hide"는 "이 버튼은 팝오버를 닫는 용도"라고 콕 집어준 거고요.

다이얼로그 vs 팝오버 — 언제 뭘 쓸까

둘 다 "떠오르는 창"인데 성격이 달라요. 이제 둘 다 만들어봤으니 비교해볼게요.

<dialog> (모달) Popover
무게 무거움 가벼움
뒤 배경 어두워지고 못 만짐 그대로 만질 수 있음
바깥 클릭 안 닫힘 (의도적) 저절로 닫힘
어울리는 곳 공유·로그인·결제처럼 집중이 필요한 작업 더보기·툴팁·드롭다운처럼 가벼운 메뉴

공유처럼 "사용자가 끝까지 마쳐야 하는" 작업은 모달, 더보기처럼 "슬쩍 보고 마는" 메뉴는 팝오버. 이렇게 성격에 맞게 골라 쓰면 돼요.

💡 튜터의 결론: popover 속성 + popovertarget 버튼으로 가벼운 떠있는 메뉴를 만들어요. 바깥을 누르면 저절로 닫히는 게 특징이에요. 집중이 필요한 작업은 <dialog> 모달, 슬쩍 보는 메뉴는 Popover — 이렇게 구분하면 됩니다.


Step 8: "폼 접근성 — 누구나 쓸 수 있는 폼"

마지막으로, 오늘 만든 폼들을 다시 한번 "누구나 쓸 수 있는가?"라는 눈으로 점검해봐요. 잘 만든 폼은 마우스를 못 쓰는 사람, 화면을 못 보는 사람도 똑같이 쓸 수 있어야 해요. 다행히 오늘 배운 것들을 제대로 쓰면 대부분 자동으로 챙겨져요.

첫째, 모든 입력칸에 라벨

Step 2에서 강조했죠. 라벨 없는 입력칸은 화면을 못 보는 사용자에겐 "정체불명의 칸"이에요. 우리 폼은 모든 입력칸에 <label>을 붙였어요. 이게 접근성의 기본 중의 기본이에요.

둘째, autocomplete로 자동완성 돕기

로그인 폼의 입력칸에 붙어 있던 autocomplete 기억나요?

<!-- instagram-clone-frontend/index.html -->
<input type="text" id="username" name="username"
       autocomplete="username" ...>

<input type="password" id="password" name="password"
       autocomplete="current-password" ...>

autocomplete="username", autocomplete="current-password"는 브라우저에게 "이 칸은 아이디 칸이야", "이 칸은 비밀번호 칸이야"라고 알려줘요. 그러면 브라우저가 저장해둔 정보로 자동완성을 도와줘요. 매번 타이핑하는 수고를 덜어주는 거죠. 특히 손을 자유롭게 쓰기 어려운 사용자에게 큰 도움이 돼요.

셋째, 키보드만으로 폼 채우기

마우스 없이 키보드만으로도 폼을 다 채울 수 있어야 해요. 직접 해보세요. 폼에서 Tab 키를 눌러보면 입력칸 사이를 차례로 이동하고, Enter로 제출돼요. HTML 표준 태그(<input>, <button>, <select>)를 쓰면 이 키보드 이동이 자동으로 됩니다. 우리가 따로 코드를 짤 필요가 없어요.

넷째, <fieldset>으로 그룹의 의미 전달

Step 5에서 만든 <fieldset>+<legend>도 접근성에 도움이 돼요. 화면을 못 보는 사용자가 "지금 읽는 칸이 '기본 정보' 그룹에 속한다"는 걸 알 수 있거든요. 시각적인 테두리일 뿐 아니라 의미의 묶음이기도 한 거예요.

🎯 핵심 멘트: 접근성은 "장애가 있는 일부 사용자를 위한 특별 배려"가 아니에요. 라벨을 붙이고, 표준 태그를 쓰고, 입력칸을 그룹으로 묶는 것 — 이건 모든 사용자가 더 편하게 쓰는 폼을 만드는 일이에요. 그리고 놀랍게도, 오늘 배운 HTML을 제대로만 쓰면 대부분 공짜로 따라와요.

💡 튜터의 결론: 접근성 좋은 폼의 비결은 거창하지 않아요. 모든 입력칸에 <label>, 적절한 autocomplete, 표준 태그로 키보드 이동 보장, <fieldset>으로 의미 있는 그룹. 오늘 배운 것을 제대로 쓴 폼은 이미 접근성이 좋은 폼이에요.


마무리

오늘 정말 많은 걸 만들었어요. 우리 인스타그램이 처음으로 사용자와 대화하기 시작한 날이에요.

오늘 배운 것 한눈에 정리

🎯 하나, <form>은 입력을 묶는 봉투예요. action(보낼 주소) + method(get/post)로 어디로 어떻게 보낼지 정해요.

🎯 , <input>은 입력칸, <label>은 이름표. forid를 맞춰 연결하거나 <label>로 감싸요. 라벨은 필수예요.

🎯 , type 하나로 <input>이 글자칸·비밀번호·이메일·날짜·색상·슬라이더로 변신해요.

🎯 , required · minlength · pattern 같은 속성으로 잘못된 입력을 브라우저가 제출 전에 막아줘요.

🎯 다섯, 여러 줄은 <textarea>, 보기 선택은 <select>, 그룹 묶기는 <fieldset>+<legend>. 버튼은 submit·reset·button 세 종류.

🎯 여섯, <dialog>로 모달을, popover 속성으로 가벼운 메뉴를 만들어요. command/commandfor/popovertarget로 자바스크립트 없이 여닫아요.

🎯 일곱, 라벨 · autocomplete · 표준 태그 · <fieldset>만 잘 써도 접근성 좋은 폼이 돼요.

그런데 폼이 좀 투박하죠?

지금 우리 폼을 브라우저로 열어보면, 입력칸들이 밋밋하게 세로로 쌓여 있을 거예요. 인스타그램의 그 예쁜 로그인 화면과는 거리가 멀죠. 왜냐면 우리는 아직 HTML로 뼈대만 만들었거든요. 색도, 간격도, 둥근 모서리도 아직 없어요.

이건 정상이에요! HTML은 "무엇이 있는가"를 담당하고, 꾸미는 일은 다음 주인공인 CSS의 몫이거든요.

다음 시간 예고

다음 시간부터 우리는 드디어 CSS의 세계로 들어갑니다. 지금까지 만든 투박한 페이지들이 색과 모양을 입기 시작해요.

  • 색상 · 글꼴 · 간격을 입히는 법
  • 최신 CSS의 강력한 기능들 (:has() 같은 똑똑한 선택자, 깔끔하게 중첩해서 쓰는 법)
  • 오늘 만든 이 로그인 폼과 다이얼로그를 인스타그램처럼 단장하기

A 카테고리(HTML)를 무사히 마쳤어요. 골격이 튼튼하게 완성됐으니, 다음 시간엔 여기에 옷을 입혀봅시다!


과제

[구현] 회원가입 폼 만들기

index.html의 로그인 폼에는 "계정이 없으신가요? 가입하기" 링크가 있죠. 이번엔 그 가입 페이지(signup.html)의 폼을 직접 만들어봐요.

feed.html이나 profile.html을 복사해서 골격을 잡고, <main> 안에 회원가입 폼을 채워보세요.

요구 사항:

  • <form>으로 감싸고 method="post"를 쓸 것
  • 다음 입력칸을 모두 포함하고, 각 칸에 <label>for/id로 연결할 것
    • 이름 (type="text", required)
    • 이메일 (type="email", required)
    • 비밀번호 (type="password", required, minlength="8")
    • 생일 (type="date")
    • 관심사 카테고리 (<select> + <option> 3개 이상)
    • 자기소개 (<textarea>, maxlength="150")
  • 관련 있는 칸끼리 <fieldset> + <legend>로 묶을 것 (예: "계정 정보" / "프로필 정보")
  • "약관에 동의합니다" 체크박스를 <label>로 감싸서 추가하고 required를 걸 것
  • 제출 버튼(type="submit")과 되돌리기 버튼(type="reset")을 둘 것

완성한 뒤 브라우저에서 칸을 비운 채 제출 버튼을 눌러, 브라우저가 어떤 칸에서 막아주는지 직접 확인해보세요.

[탐구] 즐겨 쓰는 사이트의 폼 뜯어보기

자주 쓰는 웹사이트 2곳의 로그인 폼(또는 검색창)을 열고, F12 → Elements 탭에서 <input><label>을 찾아보세요.

각 사이트마다 확인 항목:

  1. 각 입력칸의 type은 무엇인가? (text/email/password/search 등)
  2. <label>이 제대로 붙어 있는가, 아니면 placeholder만으로 때우고 있는가?
  3. autocomplete 속성이 있는가? 있다면 어떤 값인가?
  4. requiredpattern 같은 유효성 속성이 보이는가?

두 사이트의 결과를 간단한 표로 정리하고, "라벨을 더 잘 챙긴 사이트는 어디였나" 한 줄 의견을 덧붙여주세요.


생각해볼 주제

1. placeholder만으로 입력칸을 안내하면 안 될까?

요즘 많은 사이트가 라벨 없이 placeholder만으로 입력칸을 안내해요. 화면이 깔끔해 보이거든요. 하지만 우리는 수업에서 "라벨은 필수"라고 배웠죠. placeholder만 쓰면 어떤 사용자가, 어떤 순간에 불편을 겪을까요? 깔끔함과 사용성 사이에서 어떤 선택이 옳을지 생각해보세요.

2. 공유 기능은 모달(<dialog>)이 맞을까, 팝오버가 맞을까?

우리는 공유 기능을 모달 다이얼로그로 만들었어요. 그런데 인스타그램을 떠올려보면, 공유 방식이 앱마다 조금씩 달라요. 어떤 곳은 화면을 꽉 채운 모달로, 어떤 곳은 아래에서 슬쩍 올라오는 가벼운 창으로 띄우죠. 공유 같은 기능에는 모달과 팝오버 중 무엇이 더 어울릴까요? 그 판단의 기준은 무엇일까요?

3. 브라우저의 기본 유효성 검사만 믿어도 될까?

requiredminlength만 붙이면 브라우저가 잘못된 입력을 막아주는 걸 봤어요. 정말 편리하죠. 그런데 이 검사는 "사용자의 브라우저 안에서" 일어나요. 만약 누군가 일부러 이 검사를 우회하거나, 개발자 도구로 속성을 지워버린다면 어떻게 될까요? 우리가 만든 폼의 검사를 어디까지 믿어도 되는지 생각해보세요.

✅ 예시 답안정답 보기

🎯 [과제 1 예시답안] 회원가입 폼 만들기 (signup.html)

핵심 접근

이 과제의 핵심은 오늘 배운 폼 부품을 하나의 폼 안에 빠짐없이 조립하는 것이에요. 입력칸마다 라벨을 for/id로 연결하고, 타입을 의미에 맞게 고르고, 검사 속성(required·minlength·maxlength)을 적절히 거는 게 전부예요. 관련 있는 칸끼리 <fieldset>으로 묶어 "계정 정보"와 "프로필 정보"로 나누면 사용자가 폼을 한눈에 이해해요. 정답은 하나가 아니에요. 라벨 연결 방식, fieldset 묶음 기준 등은 여러 모범 사례 중 하나로 보시면 돼요.

예시 구현

feed.html을 복사해서 골격을 잡고, <main> 안을 회원가입 폼으로 채운 모습이에요. 오늘 배운 HTML 태그만으로 만들었어요 (CSS·자바스크립트 없음).

<!-- instagram-clone-frontend/signup.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Instagram - 회원가입</title>
</head>
<body>
  <header>
    <h1><a href="index.html">Instagram</a></h1>
  </header>

  <main>
    <section>
      <h2>새 계정 만들기</h2>
      <p>친구들의 사진과 동영상을 보려면 가입하세요.</p>

      <form action="/signup" method="post">

        <fieldset>
          <legend>계정 정보</legend>

          <p>
            <label for="name">이름</label>
            <input type="text" id="name" name="name"
                   placeholder="홍길동"
                   autocomplete="name" required>
          </p>

          <p>
            <label for="email">이메일</label>
            <input type="email" id="email" name="email"
                   placeholder="you@example.com"
                   autocomplete="email" required>
          </p>

          <p>
            <label for="password">비밀번호</label>
            <input type="password" id="password" name="password"
                   placeholder="비밀번호 (8자 이상)"
                   autocomplete="new-password"
                   required minlength="8">
          </p>
        </fieldset>

        <fieldset>
          <legend>프로필 정보</legend>

          <p>
            <label for="birthday">생일</label>
            <input type="date" id="birthday" name="birthday">
          </p>

          <p>
            <label for="category">관심사 카테고리</label>
            <select id="category" name="category">
              <option value="">선택하세요</option>
              <option value="dev">개발</option>
              <option value="travel">여행</option>
              <option value="food">맛집</option>
              <option value="daily">일상</option>
            </select>
          </p>

          <p>
            <label for="bio">자기소개</label>
            <textarea id="bio" name="bio" rows="3"
                      placeholder="나를 한 줄로 소개해보세요"
                      maxlength="150"></textarea>
          </p>
        </fieldset>

        <p>
          <label>
            <input type="checkbox" name="agree" value="yes" required>
            약관에 동의합니다
          </label>
        </p>

        <button type="submit">가입하기</button>
        <button type="reset">되돌리기</button>
      </form>

      <p>이미 계정이 있으신가요? <a href="index.html">로그인</a></p>
    </section>
  </main>
</body>
</html>

채점 포인트

포인트 무엇을 봐야 하는가 배점 가중
<form method="post"> 모든 입력칸을 form으로 감쌌고 method가 post인가
라벨-입력칸 연결 각 칸이 <label for><input id> 같은 값으로 연결됐는가
입력 타입 정확성 이름=text, 이메일=email, 비밀번호=password, 생일=date로 맞췄는가
검사 속성 비밀번호 minlength="8", 자기소개 maxlength="150", 필수 칸 required
<select>+<option> 3개+ 빈 보기 외에 실제 보기가 3개 이상인가
<textarea> 사용 자기소개를 input이 아닌 textarea로 만들었는가
<fieldset>+<legend> 그룹화 관련 칸을 두 그룹으로 묶고 legend로 제목을 달았는가
약관 동의 체크박스 <label>로 감싸고 required를 건 체크박스인가
submit + reset 버튼 제출 버튼과 되돌리기 버튼이 둘 다 있는가

흔한 실수

  • 자기소개를 <input type="text">로 만듦 → 한 줄짜리라 긴 글이 안 들어가요. 여러 줄 입력은 반드시 <textarea>예요. 그리고 textarea는 <textarea></textarea>처럼 여닫는 태그가 있어요(input과 다른 점).
  • 라벨을 placeholder로 대체 → "이름" 같은 안내를 placeholder에만 넣고 <label>을 빼먹는 경우가 많아요. placeholder는 입력을 시작하면 사라지는 보조 힌트일 뿐이에요. 라벨은 반드시 따로 있어야 해요.
  • 약관 체크박스에 required를 안 검 → 동의 없이도 가입이 되어버려요. 체크박스에도 required가 그대로 동작해요.
  • 되돌리기 버튼을 <button>되돌리기</button>로만 적음 → 폼 안의 버튼은 type을 안 적으면 자동으로 submit이 돼요. 되돌리기는 꼭 type="reset"을 적어야 해요.
  • 같은 id를 두 칸에 씀 → id는 페이지에서 단 하나여야 해요. 라벨 연결이 엉뚱한 칸으로 갈 수 있어요.

실무 개선 포인트 (심화)

  • 비밀번호 칸의 autocomplete 값 구분: 로그인 폼은 current-password(기존 비밀번호 자동완성), 회원가입 폼은 new-password를 쓰는 게 정석이에요. new-password를 주면 브라우저·비밀번호 관리자가 "새 강력한 비밀번호 제안" 기능을 띄워줘요. 위 예시도 가입 폼이라 new-password로 적었어요.
  • 이메일 인증 흐름: 실무 회원가입은 이메일 칸 입력으로 끝나지 않고, 가입 후 "인증 메일의 링크를 눌러야 계정이 활성화"되는 단계가 붙어요. 이건 서버가 처리하는 일이라 지금은 폼 모양만 만들면 충분하고, 백엔드를 배울 때 다시 만나게 돼요.

🎯 [과제 2 예시답안] 즐겨 쓰는 사이트의 폼 뜯어보기

핵심 접근

이 과제는 코드를 짜는 게 아니라 남이 만든 폼을 개발자 도구로 읽어내는 눈을 기르는 것이에요. F12 → Elements 탭에서 <input>을 찾아 type·label·autocomplete·검사 속성이 어떻게 박혀 있는지 관찰하면 돼요. 큰 회사 사이트라고 모든 걸 완벽하게 챙기진 않아요. "어디는 잘 챙겼고 어디는 placeholder로 때웠는지"를 직접 보는 게 목적이에요.

분석 가이드

개발자 도구에서 이렇게 찾아보세요.

1. F12 (또는 우클릭 → 검사) → Elements 탭
2. 입력칸 위에 마우스 올리고 우클릭 → 검사
   → 그 <input> 태그가 코드에서 하이라이트됨
3. 펼쳐진 태그에서 속성 확인:
   type="..."  /  id="..."  /  바로 위·옆에 <label for="...">가 있는가
   autocomplete="..."  /  required  /  pattern="..."

<label>이 있는지 확인하는 팁: 입력칸 옆 글자(예: "아이디")를 클릭해보세요. 커서가 칸으로 쏙 들어가면 라벨이 제대로 연결된 거예요. 글자를 눌러도 아무 반응이 없으면 라벨 연결이 빠진 거고요.

모범 정리 예시

여러분이 직접 확인한 결과와 다를 수 있어요. 이건 형식을 보여드리는 예시예요.

사이트 입력칸 type label vs placeholder autocomplete required·pattern
A 사이트 (로그인) text, password label 있음 (정상) username, current-password required 둘 다
B 사이트 (검색창) search label 없음, placeholder만 없음 없음

한 줄 의견 예시:

"A 사이트는 두 입력칸 모두 라벨을 제대로 붙였고 autocomplete까지 챙겨서, 비밀번호 관리자가 자동완성을 도와줬어요. B 사이트 검색창은 placeholder만 있고 라벨이 없었는데, 검색창처럼 용도가 한눈에 보이는 곳은 aria-label로 보완하는 경우가 많아요. 라벨을 더 잘 챙긴 곳은 A 사이트였어요."

채점 포인트

포인트 무엇을 봐야 하는가 배점 가중
2곳 이상 분석 서로 다른 사이트 2곳을 실제로 열어 확인했는가
type 식별 각 입력칸의 type을 정확히 읽어냈는가 (text/email/password/search 등)
label vs placeholder 구분 라벨이 진짜 있는지, placeholder로 때웠는지 구분했는가
autocomplete 확인 autocomplete 속성 유무와 값을 적었는가
required·pattern 확인 유효성 속성이 보이는지 짚었는가
표 정리 + 의견 결과를 표로 정리하고 "라벨 잘 챙긴 곳" 의견을 달았는가

흔한 실수

  • placeholder를 라벨로 착각 → 입력칸 안에 글자가 보인다고 다 라벨이 아니에요. 그건 대부분 placeholder예요. 라벨인지 확인하려면 그 글자를 클릭해 커서가 칸으로 들어가는지 봐야 해요.
  • type을 안 보고 "글자 칸이네"로 뭉뚱그림 → 겉모습은 같아도 type이 email인지 text인지에 따라 동작이 달라요. 태그를 펼쳐 type 값을 직접 읽어야 해요.
  • 한 사이트만 분석 → 비교가 안 돼요. 잘 챙긴 곳과 덜 챙긴 곳을 나란히 봐야 차이가 느껴져요.

실무 개선 포인트 (심화)

  • 검색창의 aria-label: 검색창처럼 보이는 라벨을 일부러 안 두는 경우, 화면을 못 보는 사용자를 위해 <input aria-label="검색">으로 숨은 이름표를 다는 게 정석이에요. 아직 배우지 않은 속성이지만, 큰 사이트 검색창을 뜯어보면 자주 보여요. "라벨이 안 보인다고 접근성을 포기한 게 아니라, 눈에 안 보이는 라벨을 따로 챙겼구나"를 발견하면 좋은 관찰이에요.

💡 [생각해볼 주제 1 예시답안] placeholder만으로 입력칸을 안내하면 안 될까?

[문제 상황 요약]

요즘 많은 사이트가 화면을 깔끔하게 보이려고 라벨 없이 placeholder만으로 입력칸을 안내해요. 칸 안에 흐린 글씨로 "이메일"이라고 써두는 식이죠. 보기엔 깔끔한데, 우리는 수업에서 "라벨은 필수"라고 배웠어요. placeholder만 쓰면 어떤 사용자가, 어떤 순간에 불편을 겪을까요?

[튜터의 가이드 및 해설]

이건 깔끔함(디자인)과 사용성·접근성 사이의 트레이드오프 문제예요. placeholder는 분명 매력이 있어요. 칸과 안내가 한 덩어리라 화면이 정돈돼 보이고, 라벨을 따로 배치할 공간을 아낄 수 있죠. 하지만 placeholder에는 세 가지 약점이 있어요.

첫째, 입력을 시작하면 사라져요. 사용자가 이메일을 절반쯤 적다가 "어, 내가 지금 적던 게 이메일이었나 아이디였나?" 하고 헷갈릴 때, 확인할 안내가 이미 사라지고 없어요. 칸이 여러 개인 폼에서 특히 혼란스러워요.

둘째, 화면을 못 보는 사용자에게 전달이 약해요. 지난 시간 alt로 이미지에 의미를 담았던 것처럼, 라벨은 입력칸에 의미를 영구히 붙여줘요. placeholder는 스크린 리더가 읽어주긴 하지만 라벨만큼 확실하게, 일관되게 전달되지 않아요.

셋째, 흐린 회색 글씨라 잘 안 보여요. 시력이 약한 사용자나 밝은 곳에서 화면을 보는 사용자에게 흐린 placeholder는 읽기 힘들어요.

그래서 선택지를 정리하면 이래요.

  • Option A — 라벨 + placeholder 둘 다: 라벨로 칸의 의미를 영구히 붙이고, placeholder는 입력 예시("you@example.com")로 보조해요. 장점은 사용성·접근성 모두 챙김. 단점은 화면이 조금 덜 깔끔해 보일 수 있음.
  • Option B — placeholder만: 화면이 깔끔해요. 단점은 위 세 가지 약점을 그대로 떠안음.

현업에서는 보통 Option A를 기본으로 가요. 라벨은 의미, placeholder는 예시 힌트 — 둘의 역할이 다르니까 함께 쓰는 거예요. 화면 공간이 꼭 부족하면 라벨을 시각적으로만 숨기고(스크린 리더는 읽을 수 있게) placeholder를 보이게 하는 방법도 있지만, 그건 더 배운 뒤의 이야기예요. 지금 단계에서는 "라벨은 빼지 않는다"만 지키면 충분해요.

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

"placeholder는 라벨의 대체품이 아니라 보조 힌트예요. 입력을 시작하면 사라지고, 흐린 글씨라 잘 안 보이고, 스크린 리더 전달도 약하거든요. 그래서 저는 라벨로 칸의 의미를 영구히 붙이고 placeholder는 입력 예시로만 씁니다. 깔끔함을 위해 라벨을 빼면 가장 약한 사용자부터 폼을 못 채우게 돼요. 디자인은 라벨을 살리면서도 충분히 깔끔하게 만들 수 있다고 봅니다."


💡 [생각해볼 주제 2 예시답안] 공유 기능은 모달(<dialog>)이 맞을까, 팝오버가 맞을까?

[문제 상황 요약]

우리는 공유 기능을 모달 다이얼로그로 만들었어요. 그런데 앱마다 공유 방식이 조금씩 달라요. 어떤 곳은 화면을 덮는 모달로, 어떤 곳은 아래에서 슬쩍 올라오는 가벼운 창으로 띄우죠. 공유 같은 기능에는 모달과 팝오버 중 무엇이 더 어울릴까요? 판단 기준은 뭘까요?

[튜터의 가이드 및 해설]

이건 "이 작업이 사용자의 집중을 얼마나 요구하는가" 로 가르는 문제예요. 수업에서 비교했듯이 둘은 성격이 달라요.

  • Option A — 모달(<dialog> + show-modal): 뒤 배경이 어두워지고(backdrop) 못 만지게 막혀요. 바깥을 클릭해도 안 닫히고요. 장점은 사용자를 그 작업에 집중시켜서 끝까지 완료하게 유도하는 것. 단점은 무거워서, 슬쩍 보고 마는 가벼운 동작에 쓰면 답답해요.
  • Option B — 팝오버(popover 속성): 뒤 배경을 그대로 만질 수 있고, 바깥을 클릭하면 저절로 닫혀요. 장점은 가볍고 빠르게 떴다 사라짐. 단점은 작업 중 실수로 바깥을 누르면 닫혀버려서, 입력을 끝까지 받아야 하는 작업엔 부적합해요.

판단 기준은 두 가지로 좁혀져요. 하나, 바깥 클릭으로 닫혀도 되는 동작인가? 받는 사람 아이디를 입력하고 "보내기"를 눌러야 끝나는 공유라면, 입력 중 바깥을 눌렀다고 창이 닫혀 입력이 날아가면 안 돼요. 이건 모달이 맞아요. 둘, 배경막이 필요한가? "지금은 이 작업에 집중하세요"라는 신호로 뒤를 가려야 한다면 모달, 그냥 메뉴를 슬쩍 보여주는 거면 팝오버예요.

현업에서는 보통 공유에 입력이나 여러 단계가 끼면 모달, 단순히 "링크 복사 / 카톡 / 인스타스토리" 같은 버튼 나열만이면 팝오버로 가요. 같은 "공유"라도 안에 뭐가 들었느냐에 따라 갈리는 거예요. 우리 폼은 받는 사람을 입력받는 폼이 들어 있으니 모달이 합리적인 선택이었어요. 반대로 인스타그램 게시물의 "⋯ 더보기"처럼 메뉴만 슬쩍 보여주는 건 팝오버가 맞고요.

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

"모달이냐 팝오버냐는 '바깥을 눌러도 닫혀도 되는 작업인가'로 판단합니다. 공유처럼 받는 사람을 입력받고 끝까지 완료해야 하는 작업은, 실수로 바깥을 눌러 입력이 날아가면 안 되니까 배경을 막는 모달(<dialog>)이 맞아요. 반대로 더보기 메뉴처럼 슬쩍 보고 마는 건, 바깥 클릭으로 저절로 닫히는 팝오버가 가볍고 자연스럽습니다. 같은 공유라도 안에 입력 단계가 있으면 모달, 버튼 나열뿐이면 팝오버로 갈라요."


💡 [생각해볼 주제 3 예시답안] 브라우저의 기본 유효성 검사만 믿어도 될까?

[문제 상황 요약]

requiredminlength만 붙이면 브라우저가 잘못된 입력을 알아서 막아주는 걸 봤어요. 정말 편리하죠. 그런데 이 검사는 "사용자의 브라우저 안에서" 일어나요. 누군가 일부러 이 검사를 우회하거나, 개발자 도구로 required 속성을 지워버린다면 어떻게 될까요? 우리가 만든 폼의 검사를 어디까지 믿어도 될까요?

[튜터의 가이드 및 해설]

결론부터 말하면, 브라우저 검사는 "편의"이지 "방어선"이 아니에요. 왜냐하면 이 검사는 전부 사용자 손안의 브라우저에서 일어나거든요. 사용자가 마음만 먹으면 얼마든지 건드릴 수 있어요.

실제로 이렇게 뚫려요. 누군가 F12로 개발자 도구를 열고 <input ... required minlength="8">에서 requiredminlength를 지워버리면? 그 칸은 빈 값으로도, 한 글자로도 제출돼요. 우리가 건 검사가 통째로 무력화되는 거예요. 게다가 화면을 거치지 않고 /signup 주소로 곧장 데이터를 보내는 방법도 있어요. 그러면 우리 폼의 검사를 아예 거치지 않고 데이터가 도착해요.

그래서 검사를 두 종류로 나눠 생각해야 해요.

  • 클라이언트 검사(브라우저): 우리가 오늘 배운 required·minlength·pattern이에요. 장점은 사용자가 제출 버튼을 누르기도 전에 즉시 "여기 잘못됐어요"라고 빠른 피드백을 주는 것. 입력 경험이 좋아져요. 단점은 우회·조작이 가능해서 믿을 수 없다는 것.
  • 서버 검사(나중에 백엔드에서): 데이터가 서버에 도착한 뒤 서버가 다시 한번 검사하는 거예요. 사용자가 손댈 수 없는 영역이라 진짜 방어선이에요.

현업에서는 보통 둘 다 해요. 클라이언트 검사로 사용자에게 빠른 피드백을 주고, 서버 검사로 진짜 안전을 지켜요. "클라이언트 검사는 사용자 편의, 서버 검사는 시스템 보호" — 역할이 다르니 둘 다 필요한 거예요. 아직 서버를 배우지 않았으니 지금은 "브라우저 검사만으로는 부족하고, 나중에 백엔드에서 반드시 다시 검사한다"는 개념만 챙기면 충분해요. 깊은 구현은 백엔드를 배울 때 만나게 돼요.

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

"브라우저의 유효성 검사는 사용자 편의용이지 보안 장치가 아니에요. F12로 required 속성을 지우거나 폼을 거치지 않고 직접 요청을 보내면 그대로 뚫리거든요. 그래서 클라이언트 검사는 '빠른 피드백', 서버 검사는 '진짜 방어선'으로 역할을 나눠서 둘 다 둡니다. 클라이언트만 믿는 폼은 안전해 보일 뿐 실제로는 무방비예요. 입력은 항상 서버에서 다시 검증한다 — 이게 기본 원칙이라고 생각합니다."

더 배우려면

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

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