본문으로 건너뛰기

안동민 개발노트

본문 시작

리스트, 테이블, 폼 요소

목록과 표로 정보를 구조화하고 input·textarea·select를 조합해 기본 회원가입 폼을 작성합니다.

앞서 우리는 웹 페이지의 기본 뼈대를 만드는 HTML의 역할과 텍스트, 링크, 이미지를 다루는 방법을 학습했습니다.

이제는 웹 페이지에서 정보를 더욱 구조적이고 체계적으로 표현하거나, 사용자로부터 데이터를 입력받는 데 사용되는 핵심적인 HTML 요소들에 대해 깊이 있게 다룰 차례입니다.

이 장에서는 리스트(목록) 요소로 정보를 나열하고, 테이블(표) 요소로 데이터를 행/열 구조로 정리하는 방법을 다룹니다.

또한 웹 애플리케이션의 핵심 기능 중 하나인 폼(Form) 요소로 사용자의 입력을 받고 처리하는 기본 방법을 알아봅니다.

이 요소들은 웹 페이지의 기능성과 정보 전달력을 높이는 데 매우 중요한 역할을 합니다.

항목·행열·입력 관계로 고르는 HTML 구조

FLOWCHART

항목·행열·입력 관계로 고르는 HTML 구조

ordered/unordered/term-description, two-dimensional comparison, user submission 질문으로 ul/ol/dl/table/form을 고르고 native semantics를 확인

관계로 고르는 HTML 구조 요소 사용자 제출, 행과 열의 이차원 비교, 용어와 설명, 순서 의미를 차례로 질문하여 form table dl ol ul 가운데 native semantics가 맞는 요소를 고르는 결정 트리 사용자 제출?label·control·validation 행 × 열 비교?header가 두 축을 설명 용어–설명?name과 value의 짝 순서가 의미?재배열하면 뜻이 변함 <form>fieldset · label · input <table>caption · th · scope <dl>dt와 dd 관계 <ul>동등한 항목 집합 <ol>단계 · 순위 · 순차 결과 YESNO
  1. 사용자가 값을 제출하는가?

    예: form과 label·control·validation 관계를 쓴다. 아니오: 다음 질문.

  2. 행과 열의 이차원 비교인가?

    예: table과 caption·th·scope를 쓴다. 아니오: 다음 질문.

  3. 용어와 설명의 짝인가?

    예: dl 안에 dt와 dd 관계를 표현한다. 아니오: 다음 질문.

  4. 항목 순서가 의미를 바꾸는가?

    예: ol, 아니오: 동등한 항목 집합인 ul을 쓴다.

dl·table·form은 목록의 다음 단계가 아니라 서로 다른 관계 모델이다. 모양이 아니라 native semantics와 입력·탐색 방식을 기준으로 고른다.


리스트 요소: 정보를 체계적으로 나열하기

2장에서도 간략하게 다루었지만, 리스트 요소는 웹 페이지에서 관련 있는 항목들을 그룹화하여 보여줄 때 매우 유용합니다.

목록은 크게 세 가지 유형으로 나눌 수 있습니다.

순서 없는 목록 (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): 테이블 헤더 셀(열의 제목)을 나타냅니다. (기본적으로 굵고 가운데 정렬됨)
  • <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를 사용합니다.

앞의 구조 선택 다이어그램은 목록과 표를 고를 때 확인할 관계 기준을 정리합니다.


폼 요소: 사용자로부터 입력받기 (<form>)

리스트, 테이블, 폼 요소에서는 태그 의미, 브라우저 해석, 사용자에게 보이는 결과를 연결합니다.

레이블에서 서버 검증까지 이어지는 폼 제출 계약

DATA-FLOW

레이블에서 서버 검증까지 이어지는 폼 제출 계약

레이블과 control에서 successful control만 name/value로 직렬화하고, method에 따라 URL query와 request body를 나눈 뒤 서버 검증 결과를 다시 폼에 연결한다.

레이블에서 서버 검증까지 이어지는 폼 제출 계약 Label and control, successful controls, name value serialization에서 GET query와 POST body로 분기하고 server validation 후 success response 또는 field-linked error가 해당 control로 돌아오는 왕복 계약을 표시한다. controls name=value GET POST query string encoded body VALID INVALID field error → 같은 control Label + control for/id · accessible name Successful controls enabled · checked · named Serialize name=value pairs 어떤 method인가? GET 또는 POST GET URL query ?name=value · bookmarkable POST request body form encoding · side effect 서버 검증 통과? schema · domain rule Success response redirect 또는 rendered result Field-linked errors value 유지 · aria-describedby
  1. Label + control

    for/id와 accessible name으로 입력의 의미를 연결한다.

  2. Successful controls

    disabled, unchecked, name 없는 control은 제출 payload에서 제외될 수 있다.

  3. name/value 직렬화

    선택된 control을 encoding 규칙에 맞는 pair로 만든다.

  4. method 분기

    GET은 URL query로 읽기 요청을, POST는 encoded body로 상태 변경 요청을 보낸다.

  5. 서버 검증 왕복

    성공은 redirect/result로, 오류는 value를 유지한 field-linked message로 같은 control에 돌아온다.

  • 제출 payload
  • GET·POST method 분기
  • field error 반환

브라우저 직렬화와 서버 검증은 서로 다른 계약이다. 서버 오류는 전체 실패 문구 하나가 아니라 원인이 있는 control에 다시 연결해야 한다.

웹 페이지에서 사용자로부터 정보를 입력받는 것은 매우 중요합니다.

로그인, 회원가입, 검색, 게시글 작성 등 거의 모든 웹 서비스는 사용자로부터의 입력을 필요로 합니다.

이러한 입력 양식을 폼(Form)이라고 하며, <form> 태그를 사용하여 만듭니다.

<form> 태그 안에는 다양한 종류의 입력 필드(input field)와 컨트롤 요소들이 포함됩니다.

  • <form> 태그의 주요 속성
    • action: 폼 데이터가 제출될 서버의 URL을 지정합니다.
    • method: 폼 데이터를 서버로 전송하는 HTTP 메서드를 지정합니다.
      • GET: URL에 폼 데이터를 쿼리 문자열로 추가하여 전송합니다. (주로 검색이나 간단한 데이터 요청 시 사용)
      • POST: 폼 데이터를 HTTP 요청 본문에 담아 전송합니다. (주로 회원가입, 로그인, 파일 업로드 등 민감하거나 많은 데이터를 전송 시 사용)
<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: 서버로 전송될 때 이 입력 필드의 데이터를 식별하는 이름 (필수)
    • 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>

여러 줄의 텍스트를 입력받을 때 사용합니다.

rowscols 속성으로 초기 크기를 지정할 수 있습니다.

<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: 폼의 모든 입력 필드를 초기화합니다.
    • 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 파일을 만들고 아래 코드를 작성해 보세요.

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@example.com" 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 <select> 옵션을 자바스크립트로 동적으로 생성하여 <script> 태그의 위치를 확인할 수 있도록 했습니다.

이번 장에서는 웹 페이지의 정보 전달력과 사용자 상호작용을 크게 향상시키는 리스트, 테이블, 그리고 폼 요소들에 대해 자세히 알아보았습니다.

이 요소들을 적절히 활용하면 데이터를 체계적으로 보여주고, 사용자로부터 필요한 정보를 효과적으로 수집할 수 있습니다.