리스트, 테이블, 폼 요소
목록과 표로 정보를 구조화하고 input·textarea·select를 조합해 기본 회원가입 폼을 작성합니다.
앞서 우리는 웹 페이지의 기본 뼈대를 만드는 HTML의 역할과 텍스트, 링크, 이미지를 다루는 방법을 학습했습니다.
이제는 웹 페이지에서 정보를 더욱 구조적이고 체계적으로 표현하거나, 사용자로부터 데이터를 입력받는 데 사용되는 핵심적인 HTML 요소들에 대해 깊이 있게 다룰 차례입니다.
이 장에서는 리스트(목록) 요소로 정보를 나열하고, 테이블(표) 요소로 데이터를 행/열 구조로 정리하는 방법을 다룹니다.
또한 웹 애플리케이션의 핵심 기능 중 하나인 폼(Form) 요소로 사용자의 입력을 받고 처리하는 기본 방법을 알아봅니다.
이 요소들은 웹 페이지의 기능성과 정보 전달력을 높이는 데 매우 중요한 역할을 합니다.
STRUCTURED HTML
HTML 요소는 데이터가 가진 관계에 맞춰 고릅니다
항목의 순서, 이름과 설명, 행·열 비교, 사용자 제출은 서로 다른 관계이며 함께 조합할 수 있습니다.
ul · 순서가 핵심이 아닌 항목
<ul>
<li>HTML</li>
<li>CSS</li>
</ul>관심사처럼 항목 순서를 바꾸어도 의미가 같은 목록입니다.
ol · 순서가 의미인 항목
<ol>
<li>재료 준비</li>
<li>조리</li>
</ol>절차·순위처럼 순서가 내용을 설명하는 목록입니다.
dl · 이름과 설명의 관계
<dl>
<dt>HTML</dt>
<dd>문서 구조</dd>
</dl>용어 풀이·메타데이터 등 이름과 값 또는 설명의 그룹을 표현합니다.
table · 행과 열의 비교
caption과 th의 scope로 표 이름과 머리글 관계를 설명합니다. 배치만 만들려고 쓰는 요소는 아닙니다.
form · 사용자 입력을 제출
<form action="/register"
method="post">
<!-- label과 named 입력 -->
<button type="submit">가입</button>
</form>form은 입력을 모아 제출하는 관계입니다. 내부에 목록이나 데이터 표가 함께 들어갈 수 있습니다.
다섯 요소는 서로 배타적인 선택지가 아닙니다. 한 문서나 폼 안에 여러 관계가 있으면 각 부분에 맞는 요소를 조합합니다.
리스트 요소: 정보를 체계적으로 나열하기
앞 절에서도 간략하게 다루었지만, 리스트 요소는 웹 페이지에서 관련 있는 항목들을 그룹화하여 보여줄 때 매우 유용합니다.
목록은 크게 세 가지 유형으로 나눌 수 있습니다.
순서 없는 목록 (Unordered List)
<ul> 태그는 항목의 순서가 중요하지 않을 때 사용하며, 각 항목은 <li> (list item) 태그로 정의됩니다.
웹 브라우저는 일반적으로 각 <li> 앞에 글머리 기호(•)를 표시합니다.
내비게이션 메뉴나 카테고리처럼 순서가 의미를 바꾸지 않는 항목에 사용합니다. 정보 자체가 중요하지 않다는 뜻은 아닙니다.
<h3>제가 좋아하는 과일들</h3>
<ul>
<li>사과</li>
<li>바나나</li>
<li>딸기</li>
</ul>순서 있는 목록 (Ordered List)
<ol> 태그는 항목의 순서가 중요할 때 사용하며, 각 항목은 역시 <li> 태그로 정의됩니다.
웹 브라우저는 기본적으로 각 <li> 앞에 숫자를 표시합니다.
레시피의 단계, 순위, 지시 사항 등 순서가 중요한 정보에 적합합니다.
<h3>커피 만드는 법</h3>
<ol>
<li>원두를 갈아 준비합니다.</li>
<li>뜨거운 물을 준비합니다.</li>
<li>천천히 물을 부어 커피를 추출합니다.</li>
<li>맛있게 드세요!</li>
</ol>-
type속성 (<ol>에 사용): 순서의 종류를 변경할 수 있습니다.type="1"(기본값): 숫자 (1, 2, 3...)type="a": 소문자 알파벳 (a, b, c...)type="A": 대문자 알파벳 (A, B, C...)type="i": 소문자 로마 숫자 (i, ii, iii...)type="I": 대문자 로마 숫자 (I, II, III...)
<h3>시험 점수 등급</h3> <ol type="A"> <li>A 학점</li> <li>B 학점</li> <li>C 학점</li> </ol> -
start속성 (<ol>에 사용): 목록 시작 번호를 변경할 수 있습니다.<h3>재개되는 작업 목록</h3> <ol start="5"> <li>이전 작업 마무리</li> <li>새로운 기능 개발 시작</li> <li>테스트 및 배포</li> </ol>
정의 목록 (Description List)
정의 목록은 용어와 그 용어에 대한 설명을 나열할 때 사용합니다.
용어 사전, 질문과 답변(FAQ) 등에 유용합니다.
<dl>(Description List): 정의 목록 전체를 감싸는 태그입니다.<dt>(Definition Term): 정의할 용어를 나타냅니다.<dd>(Definition Description): 용어에 대한 설명을 나타냅니다.
<h3>용어 사전</h3>
<dl>
<dt>HTML</dt>
<dd>웹 페이지의 구조를 정의하는 마크업 언어입니다.</dd>
<dt>CSS</dt>
<dd>웹 페이지의 스타일과 디자인을 담당하는 스타일 시트 언어입니다.</dd>
<dt>JavaScript</dt>
<dd>웹 페이지에 동적인 기능과 상호작용을 부여하는 프로그래밍 언어입니다.</dd>
</dl>테이블 요소: 데이터를 행과 열로 정리하기
테이블(Table)은 데이터를 행(row)과 열(column)의 형태로 정리하여 보여줄 때 사용합니다.
통계 데이터, 제품 비교, 시간표 등 구조화된 데이터를 표시하는 데 매우 유용합니다.
테이블은 여러 태그들의 조합으로 이루어집니다.
<table>: 테이블 전체를 감싸는 태그<caption>: 테이블의 제목이나 설명을 제공 (선택 사항)<thead>: 테이블의 헤더(제목) 행들을 그룹화<tbody>: 테이블의 본문(데이터) 행들을 그룹화<tfoot>: 테이블의 푸터(요약) 행들을 그룹화 (선택 사항)<tr>(Table Row): 테이블의 한 행을 나타냅니다.<th>(Table Header): 다른 셀의 제목인 헤더 셀을 나타냅니다. 열 제목뿐 아니라 행 제목에도 사용할 수 있습니다. 단순 표에서는scope="col"·scope="row"로 어느 방향의 셀을 설명하는지 명시하면 관계를 전달하는 데 도움이 됩니다.<td>(Table Data): 테이블 데이터 셀(실제 데이터)을 나타냅니다.
<h3>월별 웹사이트 방문자 수</h3>
<table>
<caption>2024년 월별 방문자 통계</caption>
<thead>
<tr>
<th>월</th>
<th>방문자 수</th>
<th>신규 방문자</th>
</tr>
</thead>
<tbody>
<tr>
<td>1월</td>
<td>10,500</td>
<td>3,200</td>
</tr>
<tr>
<td>2월</td>
<td>12,300</td>
<td>4,100</td>
</tr>
<tr>
<td>3월</td>
<td>15,800</td>
<td>5,500</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>총계</td>
<td>38,600</td>
<td>12,800</td>
</tr>
</tfoot>
</table>-
셀 병합 (
rowspan,colspan속성)colspan: 여러 개의 열을 하나의 셀로 병합할 때 사용합니다.rowspan: 여러 개의 행을 하나의 셀로 병합할 때 사용합니다.
<h3>시간표 (예시)</h3> <table> <thead> <tr> <th>시간</th> <th>월요일</th> <th>화요일</th> </tr> </thead> <tbody> <tr> <td>9:00 - 10:00</td> <td>HTML 기초</td> <td rowspan="2">CSS 심화</td> </tr> <tr> <td>10:00 - 11:00</td> <td>JavaScript 기초</td> </tr> <tr> <td colspan="3">점심 시간</td> </tr> </tbody> </table>
💡 Tip: 테이블은 데이터 표시를 위한 용도로만 사용해야 합니다.
웹 페이지의 레이아웃을 잡기 위해 테이블을 사용하는 것은 권장되지 않습니다.
이를 테이블 레이아웃이라고 부르며, 오래된 방식이고 웹 표준과 웹 접근성에도 좋지 않습니다.
현대 웹에서는 CSS Flexbox나 Grid를 사용합니다.
앞의 구조 선택 다이어그램은 목록과 표를 고를 때 확인할 관계 기준을 정리합니다. 방문자 표의 총계는 10,500 + 12,300 + 15,800 = 38,600, 신규 방문자는 3,200 + 4,100 + 5,500 = 12,800입니다. 시간표에서는 화요일 셀의 rowspan="2"가 다음 행까지 차지하므로 다음 행에 화요일 셀을 다시 쓰지 않습니다. 점심 셀의 colspan="3"은 시간·월요일·화요일 세 열을 한 셀로 합칩니다.
폼 요소: 사용자로부터 입력받기 (<form>)
리스트, 테이블, 폼 요소에서는 태그 의미, 브라우저 해석, 사용자에게 보이는 결과를 연결합니다.
레이블에서 서버 검증까지 이어지는 폼 제출 계약 — 상태 추적
required/type 제약 검사가 실패한 제출 A는 요청 전에 중단됩니다. 독립적인 유효한 제출 B는 name 기준 수집과 서버 검증으로 진행하며 서버 판정은 별도 경계입니다.
FORM SUBMISSION
브라우저 검증 뒤에 제출하고 서버에서 다시 검증합니다
입력 id는 레이블 연결, name은 제출 키입니다. 클라이언트 검증과 서버 검증은 서로 다른 단계입니다.
- 레이블과 입력 연결
label의 for와 입력 id를 연결합니다. 제출 데이터의 키는 id가 아니라 name입니다.
- 브라우저 제약 검증
일반 제출에서 required·type·minlength 등을 검사합니다. 실패하면 요청 전에 멈춥니다. novalidate·formnovalidate 또는 form.submit()은 검증을 우회할 수 있습니다.
- 제출 항목 구성
이름이 있고 활성화된 입력을 수집합니다. 체크되지 않은 checkbox·radio, disabled 입력, 제출에 쓰지 않은 버튼 등은 제외됩니다.
- 인코딩해 요청
GET은 URL query에, POST는 enctype에 맞춘 본문에 보냅니다. POST 자체가 내용을 암호화하지는 않습니다.
- 서버가 다시 검증
서버는 형식·권한·중복·업무 규칙 등을 별도로 확인합니다. 브라우저 검증만 신뢰하지 않습니다.
- 결과 또는 입력별 오류
성공 결과를 보여주거나 오류를 해당 입력과 연결합니다. 비밀번호를 응답에 다시 채워 넣지 않습니다.
교재의 가입 폼 · 일부 제출 항목
POST /register
user_id=codex
gender=male
terms_agree=on가입 예의 일부를 펼친 표시입니다. 실제 기본 인코딩은 URL-encoded 본문이며 이메일·비밀번호 등 나머지 named 입력도 포함됩니다.
왜 terms_agree=on인가?
교재 checkbox에는 name="terms_agree"와 required가 있고 value는 없습니다. 체크되면 기본값 on이 들어갑니다. 체크하지 않으면 일반 제출은 검증에서 멈춥니다.
교재 예제는 HTML 폼까지 구현합니다. /register 서버와 오류 응답은 별도 구현이 필요합니다. 비밀번호 확인의 required는 두 비밀번호가 같은지 검사하지 않습니다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
- 첫 장면은 invalid 제출 A, 나머지 두 장면은 독립적인 valid 제출 B의 수집·서버 검증입니다. 실패한 제출 A가 입력 수정이나 새 제출 없이 유효한 요청으로 바뀌는 흐름이 아닙니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 원도표 조건·주석 | 레이블과 입력 연결: label의 for와 입력 id를 연결합니다. 제출 데이터의 키는 id가 아니라 name입니다. |
| 원도표 조건·주석 | 브라우저 제약 검증: 일반 제출에서 required·type·minlength 등을 검사합니다. 실패하면 요청 전에 멈춥니다. novalidate·formnovalidate 또는 form.submit()은 검증을 우회할 수 있습니다. |
| 원도표 조건·주석 | 제출 항목 구성: 이름이 있고 활성화된 입력을 수집합니다. 체크되지 않은 checkbox·radio, disabled 입력, 제출에 쓰지 않은 버튼 등은 제외됩니다. |
| 원도표 조건·주석 | 인코딩해 요청: GET은 URL query에, POST는 enctype에 맞춘 본문에 보냅니다. POST 자체가 내용을 암호화하지는 않습니다. |
| 원도표 조건·주석 | 서버가 다시 검증: 서버는 형식·권한·중복·업무 규칙 등을 별도로 확인합니다. 브라우저 검증만 신뢰하지 않습니다. |
| 원도표 설명 | 성공 결과를 보여 주거나 오류를 해당 입력과 연결합니다. 비밀번호를 응답에 다시 채워 넣지 않습니다. |
| 원도표 조건·주석 | 결과 또는 입력별 오류: 성공 결과를 보여 주거나 오류를 해당 입력과 연결합니다. 비밀번호를 응답에 다시 채워 넣지 않습니다. |
| 원도표 설명 | 교재 checkbox에는 name="terms_agree"와 required가 있고 value는 없습니다. 체크되면 기본값 on이 들어갑니다. 체크하지 않으면 일반 제출은 검증에서 멈춥니다. |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 제출 A · 잘못된 필드 | 폼: invalid / HTTP: 없음 | required/type 조건을 통과하지 못한다 | 브라우저의 유효성 검사는 조건에 맞지 않는 제출 요청을 막는다 |
| 제출 B · 유효한 필드 | 폼: valid / 수집: name 기준 | 조건을 통과하고 name이 있는 값을 수집한다 | 화면 요소 모두가 제출 데이터가 되는 것은 아니다 |
| 제출 B · 서버 판정 | HTTP: 요청 발생 / 서버: 최종 업무 검증 | 보낸 값은 서버에서 독립적으로 검사한다 | 브라우저 통과는 서버 허용 보장이 아니다 |
레이블에서 서버 검증까지 이어지는 폼 제출 계약 — 상태 추적
required/type 제약 검사가 실패한 제출 A는 요청 전에 중단됩니다. 독립적인 유효한 제출 B는 name 기준 수집과 서버 검증으로 진행하며 서버 판정은 별도 경계입니다.
FORM SUBMISSION
브라우저 검증 뒤에 제출하고 서버에서 다시 검증합니다
입력 id는 레이블 연결, name은 제출 키입니다. 클라이언트 검증과 서버 검증은 서로 다른 단계입니다.
- 레이블과 입력 연결
label의 for와 입력 id를 연결합니다. 제출 데이터의 키는 id가 아니라 name입니다.
- 브라우저 제약 검증
일반 제출에서 required·type·minlength 등을 검사합니다. 실패하면 요청 전에 멈춥니다. novalidate·formnovalidate 또는 form.submit()은 검증을 우회할 수 있습니다.
- 제출 항목 구성
이름이 있고 활성화된 입력을 수집합니다. 체크되지 않은 checkbox·radio, disabled 입력, 제출에 쓰지 않은 버튼 등은 제외됩니다.
- 인코딩해 요청
GET은 URL query에, POST는 enctype에 맞춘 본문에 보냅니다. POST 자체가 내용을 암호화하지는 않습니다.
- 서버가 다시 검증
서버는 형식·권한·중복·업무 규칙 등을 별도로 확인합니다. 브라우저 검증만 신뢰하지 않습니다.
- 결과 또는 입력별 오류
성공 결과를 보여주거나 오류를 해당 입력과 연결합니다. 비밀번호를 응답에 다시 채워 넣지 않습니다.
교재의 가입 폼 · 일부 제출 항목
POST /register
user_id=codex
gender=male
terms_agree=on가입 예의 일부를 펼친 표시입니다. 실제 기본 인코딩은 URL-encoded 본문이며 이메일·비밀번호 등 나머지 named 입력도 포함됩니다.
왜 terms_agree=on인가?
교재 checkbox에는 name="terms_agree"와 required가 있고 value는 없습니다. 체크되면 기본값 on이 들어갑니다. 체크하지 않으면 일반 제출은 검증에서 멈춥니다.
교재 예제는 HTML 폼까지 구현합니다. /register 서버와 오류 응답은 별도 구현이 필요합니다. 비밀번호 확인의 required는 두 비밀번호가 같은지 검사하지 않습니다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
- 첫 장면은 invalid 제출 A, 나머지 두 장면은 독립적인 valid 제출 B의 수집·서버 검증입니다. 실패한 제출 A가 입력 수정이나 새 제출 없이 유효한 요청으로 바뀌는 흐름이 아닙니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 원도표 조건·주석 | 레이블과 입력 연결: label의 for와 입력 id를 연결합니다. 제출 데이터의 키는 id가 아니라 name입니다. |
| 원도표 조건·주석 | 브라우저 제약 검증: 일반 제출에서 required·type·minlength 등을 검사합니다. 실패하면 요청 전에 멈춥니다. novalidate·formnovalidate 또는 form.submit()은 검증을 우회할 수 있습니다. |
| 원도표 조건·주석 | 제출 항목 구성: 이름이 있고 활성화된 입력을 수집합니다. 체크되지 않은 checkbox·radio, disabled 입력, 제출에 쓰지 않은 버튼 등은 제외됩니다. |
| 원도표 조건·주석 | 인코딩해 요청: GET은 URL query에, POST는 enctype에 맞춘 본문에 보냅니다. POST 자체가 내용을 암호화하지는 않습니다. |
| 원도표 조건·주석 | 서버가 다시 검증: 서버는 형식·권한·중복·업무 규칙 등을 별도로 확인합니다. 브라우저 검증만 신뢰하지 않습니다. |
| 원도표 설명 | 성공 결과를 보여 주거나 오류를 해당 입력과 연결합니다. 비밀번호를 응답에 다시 채워 넣지 않습니다. |
| 원도표 조건·주석 | 결과 또는 입력별 오류: 성공 결과를 보여 주거나 오류를 해당 입력과 연결합니다. 비밀번호를 응답에 다시 채워 넣지 않습니다. |
| 원도표 설명 | 교재 checkbox에는 name="terms_agree"와 required가 있고 value는 없습니다. 체크되면 기본값 on이 들어갑니다. 체크하지 않으면 일반 제출은 검증에서 멈춥니다. |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 제출 A · 잘못된 필드 | 폼: invalid / HTTP: 없음 | required/type 조건을 통과하지 못한다 | 브라우저의 유효성 검사는 조건에 맞지 않는 제출 요청을 막는다 |
| 제출 B · 유효한 필드 | 폼: valid / 수집: name 기준 | 조건을 통과하고 name이 있는 값을 수집한다 | 화면 요소 모두가 제출 데이터가 되는 것은 아니다 |
| 제출 B · 서버 판정 | HTTP: 요청 발생 / 서버: 최종 업무 검증 | 보낸 값은 서버에서 독립적으로 검사한다 | 브라우저 통과는 서버 허용 보장이 아니다 |
웹 페이지에서 사용자로부터 정보를 입력받는 것은 매우 중요합니다.
로그인, 회원가입, 검색, 게시글 작성 등 거의 모든 웹 서비스는 사용자로부터의 입력을 필요로 합니다.
이러한 입력 양식을 폼(Form)이라고 하며, <form> 태그를 사용하여 만듭니다.
<form> 태그 안에는 다양한 종류의 입력 필드(input field)와 컨트롤 요소들이 포함됩니다.
-
<form>태그의 주요 속성action: 폼 데이터가 제출될 서버의 URL을 지정합니다.method: 폼 데이터를 서버로 전송하는 HTTP 메서드를 지정합니다.GET: URL에 폼 데이터를 쿼리 문자열로 추가하여 전송합니다. (주로 검색이나 간단한 데이터 요청 시 사용)POST: 폼 데이터를 HTTP 요청 본문에 담아 전송합니다. URL에 값을 붙이지 않는다는 뜻이며, POST 자체가 데이터를 암호화하지는 않습니다. 민감한 정보를 전달할 때는 HTTPS와 서버의 검증·접근 통제도 필요합니다. 파일 전송에는enctype="multipart/form-data"같은 인코딩 설정도 확인합니다.
<form action="/submit-form" method="POST">
</form>입력 필드 (Input Fields): <input>
<input> 태그는 폼에서 가장 많이 사용되는 요소입니다.
type 속성을 변경하여 다양한 종류의 입력 필드를 만들 수 있습니다.
<input>은 닫는 태그가 없는 빈 태그입니다.
-
type="text": 한 줄 텍스트 입력 필드<label for="username">사용자 이름:</label> <input type="text" id="username" name="username" placeholder="이름을 입력하세요">id: 각 입력 필드를 고유하게 식별하는 이름 (JavaScript나<label>과 연결)name: 일반 입력값을 제출할 때 키로 사용할 이름입니다.id가 있어도name이 없는 일반 입력값은 제출 데이터에 포함되지 않습니다.id는 label 연결,name은 전송 키라는 서로 다른 역할입니다.placeholder: 입력 필드에 아무것도 없을 때 표시되는 힌트 텍스트value: 입력 필드의 초기값required: 필수로 입력해야 하는 필드임을 명시
-
type="password": 비밀번호 입력 필드 (입력 시 문자 대신 • 또는 * 표시)<label for="password">비밀번호:</label> <input type="password" id="password" name="password" required> -
type="email": 이메일 주소 입력 필드 (유효성 검사 지원)<label for="email">이메일:</label> <input type="email" id="email" name="email"> -
type="number": 숫자 입력 필드 (화살표 버튼으로 숫자 증감 가능)<label for="quantity">수량:</label> <input type="number" id="quantity" name="quantity" min="1" max="10" value="1"> -
type="radio": 여러 옵션 중 하나만 선택할 수 있는 라디오 버튼- 같은
name속성을 가진 라디오 버튼들은 하나의 그룹으로 묶여 하나만 선택 가능합니다.
<p>성별:</p> <input type="radio" id="male" name="gender" value="male"> <label for="male">남성</label><br> <input type="radio" id="female" name="gender" value="female"> <label for="female">여성</label> - 같은
-
type="checkbox": 여러 옵션을 동시에 선택할 수 있는 체크박스<p>좋아하는 취미 (다중 선택 가능):</p> <input type="checkbox" id="reading" name="hobby" value="reading"> <label for="reading">독서</label><br> <input type="checkbox" id="travel" name="hobby" value="travel"> <label for="travel">여행</label> -
type="file": 파일 업로드 필드<label for="upload_file">파일 업로드:</label> <input type="file" id="upload_file" name="uploaded_file" accept=".pdf,.doc">accept: 파일 선택기에 제안할 유형입니다. 사용자가 다른 파일을 고르거나 직접 요청할 가능성이 있으므로 서버의 파일 유형 검사를 대신하지 않습니다.
-
type="submit": 폼을 제출하는 버튼<input type="submit" value="제출하기">value속성으로 버튼에 표시될 텍스트를 지정합니다.
-
<label>: 입력 필드의 레이블을 정의합니다.for속성을 사용하여 해당 레이블이 어떤input요소와 연결되는지id값으로 명시하는 것이 좋습니다. 이는 웹 접근성을 높여줍니다. (레이블을 클릭하면 해당 입력 필드가 활성화됩니다.)
여러 줄 텍스트 입력: <textarea>
여러 줄의 텍스트를 입력받을 때 사용합니다.
rows와 cols 속성으로 초기 크기를 지정할 수 있습니다.
<label for="message">메시지:</label><br>
<textarea id="message" name="message" rows="5" cols="30" placeholder="메시지를 입력하세요."></textarea>드롭다운 목록: <select>와 <option>
미리 정의된 여러 옵션 중에서 하나를 선택하도록 할 때 사용합니다.
<select>: 드롭다운 목록 전체를 감싸는 태그<option>: 드롭다운 목록의 각 항목을 나타냅니다.value: 서버로 전송될 실제 값selected: 기본적으로 선택되어 있을 옵션에 사용
<label for="country">국가 선택:</label>
<select id="country" name="country">
<option value="">-- 국가를 선택하세요 --</option>
<option value="kr">대한민국</option>
<option value="us" selected>미국</option>
<option value="jp">일본</option>
</select>버튼: <button>
클릭할 수 있는 버튼을 만듭니다.
<input type="button">과 유사하지만, <button> 태그는 텍스트뿐만 아니라 이미지와 같은 HTML 콘텐츠를 포함할 수 있어 더 유연합니다.
<button type="submit">회원가입</button>
<button type="reset">초기화</button>
<button type="button">클릭 미!</button>-
type속성submit(기본값): 폼을 제출합니다.reset: 폼 컨트롤을 HTML에 적힌 초기값·초기 선택 상태로 되돌립니다. 모든 값을 무조건 빈 문자열로 만드는 것은 아닙니다.button: 일반적인 버튼으로, 주로 자바스크립트와 함께 사용되어 특정 동작을 수행합니다.
폼 요소 그룹화
관련된 폼 요소들을 시각적, 의미적으로 그룹화할 때 사용합니다.
<fieldset>: 폼 요소들을 그룹화하는 박스<legend>:<fieldset>박스의 제목
<fieldset>
<legend>개인 정보</legend>
<label for="first_name">이름:</label>
<input type="text" id="first_name" name="first_name"><br><br>
<label for="last_name">성:</label>
<input type="text" id="last_name" name="last_name">
</fieldset>목록, 테이블, 폼을 실제 마크업으로 작성할 때는 빠뜨리기 쉬운 필수 연결점을 함께 점검합니다.
이 절의 두 통합 다이어그램은 리스트·테이블·폼의 선택 기준과 폼 요소의 핵심 속성을 나누어 정리합니다.
실습: 간단한 회원가입 폼 만들기
배운 폼 요소들을 조합하여 간단한 회원가입 폼을 만들어 봅시다.
web-dev-practice 폴더에 signup.html 파일을 만들고 아래 코드를 작성해 보세요.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>회원가입 페이지</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f2f2f2;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
}
.signup-container {
background-color: white;
padding: 30px 40px;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
width: 100%;
max-width: 450px;
}
h1 {
text-align: center;
color: #333;
margin-bottom: 30px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
color: #555;
font-weight: bold;
}
input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
textarea,
select {
width: calc(100% - 20px); /* 패딩 고려 */
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 1em;
box-sizing: border-box; /* 패딩이 너비에 포함되도록 */
}
input[type="radio"],
input[type="checkbox"] {
margin-right: 8px;
}
button[type="submit"] {
width: 100%;
padding: 12px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
font-size: 1.1em;
cursor: pointer;
transition: background-color 0.3s ease;
}
button[type="submit"]:hover {
background-color: #0056b3;
}
.radio-group label, .checkbox-group label {
display: inline-block;
margin-right: 15px;
font-weight: normal;
}
</style>
</head>
<body>
<div class="signup-container">
<h1>회원가입</h1>
<form action="/register" method="POST">
<div class="form-group">
<label for="user_id">아이디:</label>
<input type="text" id="user_id" name="user_id" placeholder="5~12자 영문/숫자" required minlength="5" maxlength="12">
</div>
<div class="form-group">
<label for="user_email">이메일:</label>
<input type="email" id="user_email" name="user_email" placeholder="[email protected]" required>
</div>
<div class="form-group">
<label for="user_password">비밀번호:</label>
<input type="password" id="user_password" name="user_password" placeholder="비밀번호 입력" required minlength="8">
</div>
<div class="form-group">
<label for="password_confirm">비밀번호 확인:</label>
<input type="password" id="password_confirm" name="password_confirm" placeholder="비밀번호 다시 입력" required>
</div>
<div class="form-group radio-group">
<label>성별:</label>
<input type="radio" id="gender_male" name="gender" value="male" checked>
<label for="gender_male">남성</label>
<input type="radio" id="gender_female" name="gender" value="female">
<label for="gender_female">여성</label>
<input type="radio" id="gender_other" name="gender" value="other">
<label for="gender_other">기타</label>
</div>
<div class="form-group">
<label for="birth_year">출생 연도:</label>
<select id="birth_year" name="birth_year">
<option value="">-- 선택 --</option>
<script>
const currentYear = new Date().getFullYear();
for (let i = currentYear; i >= 1900; i--) {
document.write(`<option value="${i}">${i}</option>`);
}
</script>
</select>
</div>
<div class="form-group">
<label for="self_intro">자기소개 (선택 사항):</label>
<textarea id="self_intro" name="self_intro" rows="5" placeholder="간단히 자신을 소개해 주세요."></textarea>
</div>
<div class="form-group checkbox-group">
<input type="checkbox" id="terms_agree" name="terms_agree" required>
<label for="terms_agree">서비스 이용 약관에 동의합니다.</label>
</div>
<button type="submit">회원가입</button>
</form>
</div>
</body>
</html>- 위 코드에서는 CSS를
<style>태그 내부에 직접 작성하여 예시의 간결성을 높였습니다. 실제 프로젝트에서는 별도의.css파일로 분리하는 것이 유지보수에 더 좋습니다. birth_year의 옵션은 현재 연도부터 1900년까지 페이지를 읽을 때 생성합니다. 아직 반복문이 낯설면 조건문·반복문을 참고하세요. 여기서는 표시된 연도와option의value가 일치하는지 관찰합니다.
이 폼으로 확인할 수 있는 범위
이 파일은 입력 화면과 브라우저 기본 검증을 제공합니다. /register 요청을 처리하는 서버나 사용자 저장소는 포함하지 않으므로, 제출 뒤 서버 오류가 보여도 곧바로 회원가입 성공이나 HTML 실패로 판단하지 않습니다. 실제 비밀번호 대신 학습용 값을 사용하세요.
| 입력 조건 | 브라우저에서 확인할 결과 | 이 예제만으로 확인되지 않는 것 |
|---|---|---|
| 필수 필드·약관을 비워 두고 제출 | 필수 입력 안내가 나오며 일반 제출이 막힘 | 서버가 같은 규칙을 검사하는지 |
아이디를 abc처럼 3자로 입력 | minlength="5" 조건에 맞지 않음 | 중복 아이디 여부 |
아이디를 가나다라마로 입력 | placeholder만으로 영문·숫자 제한이 적용되지는 않음 | 문자 종류 제한의 실제 구현 |
| 비밀번호 두 칸에 서로 다른 비어 있지 않은 값을 입력 | 두 값의 일치를 검사하는 코드가 없음 | 비밀번호 확인 검증 |
placeholder는 안내 문구이고 검증 규칙은 속성이나 코드로 따로 정의해야 합니다. required·길이·형식 검사도 서버 검증을 대신하지 않습니다. 기본 제출과 서버 처리의 구분은 HTML 표준의 폼 소개에서 확인할 수 있습니다.
전송 키를 스스로 예측하기
아이디를 learner1로 입력하고 성별은 “기타”, 약관은 선택했다고 가정합니다. id·name·value를 보고 전송될 아이디 키, 성별 값, 약관 값을 먼저 적어 보세요. user_id의 name만 지우면 화면과 label 연결, 전송 데이터 중 무엇이 달라질까요?
전송 관계 확인
일반 제출 데이터의 아이디는 user_id=learner1, 성별은 gender=other입니다. 약관 checkbox는 별도 value가 없어 선택 시 기본값 terms_agree=on을 사용합니다. 선택하지 않은 checkbox는 이 값이 포함되지 않으며, 여기서는 required 때문에 정상 제출도 막힙니다. name만 지우면 아이디 입력과 for="user_id"의 label 연결은 남지만 그 입력값의 전송 키는 없어집니다.
이번 장에서는 웹 페이지의 정보 전달력과 사용자 상호작용을 크게 향상시키는 리스트, 테이블, 그리고 폼 요소들에 대해 자세히 알아보았습니다.
이 요소들을 적절히 활용하면 데이터를 체계적으로 보여주고, 사용자로부터 필요한 정보를 효과적으로 수집할 수 있습니다.