HTML 입문: 시맨틱 문서와 접근 가능한 폼 만들기

2026.09.05·수정 2026.09.14·약 26분·작성: 해비·블로그 소개

이 글에서 만드는 것

HTML 문서의 뼈대와 제목 계층을 만든 뒤, 이름표가 연결된 입력 폼을 완성합니다. JavaScript 없이 브라우저에서 실행하며 링크 이동, 키보드 조작, 필수 입력 검사를 구분해서 확인합니다.

완성하면 어떤 화면과 동작이 나오나요?

흰 배경에 모임 소개, 닉네임·이메일 입력 칸, 온라인·오프라인 선택, 제출 버튼이 위에서 아래로 표시됩니다. 이번 실습은 디자인을 꾸미는 단계가 아닙니다. 기본 모양이어도 아래 동작을 설명할 수 있으면 목표를 달성한 것입니다.

  • “모임 소개” 링크를 누르면 같은 페이지의 소개 부분으로 이동합니다.
  • 이메일 이름표를 누르면 해당 입력 칸에 초점이 갑니다.
  • 필수 값을 비우고 제출하면 브라우저가 입력을 요청합니다.
  • 연습 값을 정상 제출하면 주소에 값이 표시됩니다. 실제 신청이나 저장은 하지 않습니다.

시작·완성 예제 ZIP 내려받기 — 처음부터 긴 코드를 입력하기 어렵다면 starter 폴더로 시작하고 complete 폴더와 비교하세요. 실행 방법은 아래 설명과 ZIP의 README.md를 따릅니다.

먼저 볼 핵심 코드: 이름표와 입력 칸 연결

<label for="nickname">연습 닉네임</label>
<input id="nickname" name="nickname" required>

forid를 똑같이 적으면 이름표와 입력 칸이 연결됩니다. name은 제출할 값의 이름이고, required는 빈 값 제출을 막는 조건입니다. 이 작은 연결을 이해한 다음 아래 전체 문서에서 같은 패턴을 찾아보세요.

HTML 입문: 시맨틱 문서와 접근 가능한 폼 만들기 핵심 개념 도식
문서의 각 영역은 역할에 맞는 HTML 요소로 구분하고, 입력 칸은 label과 연결해 이름을 전달합니다.

목차

1. 문서의 뼈대와 스타일 파일 준비하기

새 폴더를 만들고 글 마지막의 전체 코드를 index.html로 저장합니다. 확장자가 .html.txt가 되지 않도록 확인하세요. 텍스트 편집기의 UTF-8 인코딩을 사용합니다. HTML의 읽기 순서와 기본 조작을 먼저 확인하고, styles.css에서는 글꼴·여백·입력 칸만 단정하게 정리합니다. 두 파일은 같은 폴더에 둡니다.

<!doctype html>은 표준 모드로 해석하도록 하는 선언입니다. html lang="ko"는 문서 언어를, meta charset은 문자 인코딩을 지정합니다. head에는 제목과 설정을, body에는 사용자가 읽고 조작할 내용을 씁니다. 탭에 보이는 title과 본문에 보이는 h1은 서로 다른 역할입니다.

파일을 브라우저에서 열면 모임 소개와 입력 연습이 위에서 아래로 이어집니다. 폼 제출까지 같은 방식으로 확인하려면 Python 3를 사용합니다. 터미널에서 python3 --version 또는 Windows의 py --version으로 설치를 확인하세요. 명령을 찾을 수 없으면 Python 공식 다운로드 페이지에서 운영체제에 맞는 정식 버전을 설치한 뒤 터미널을 다시 엽니다. 이 폴더에서 터미널을 열고 python3 -m http.server 8000 --bind 127.0.0.1을 실행하세요. Windows에서는 py -m http.server 8000 --bind 127.0.0.1을 사용할 수 있습니다. 브라우저에서 http://127.0.0.1:8000/index.html을 엽니다. 종료는 터미널의 Ctrl+C입니다.

2. 화면 위치보다 내용의 역할로 태그 고르기

header는 도입부, nav는 주요 탐색, main은 핵심 내용, footer는 마무리 정보를 맡습니다. 예제에서는 페이지 대표 제목을 h1 한 개로 정하고 소개와 연습을 h2로 나눴습니다. 글자 크기가 필요하다는 이유로 제목 단계를 건너뛰지 않습니다. WHATWG의 섹션 요소 정의를 기준으로 의미를 정한 뒤, 모양은 다음 CSS 실습에서 바꿉니다.

요소 이 예제의 사용 기준
section 모임 소개와 입력 연습처럼 제목을 붙일 수 있는 주제 묶음입니다.
article 독립적으로 배포하거나 재사용할 수 있는 글에 적합합니다. 예제의 폼을 감싸려고 추가할 필요는 없습니다.
div 스타일을 위해 묶을 때 쓸 수 있는 일반 컨테이너입니다. 문서 주제나 제목을 자동으로 만들지 않습니다.
ul / li 준비물과 탐색 항목처럼 순서보다 묶음이 중요한 목록입니다.

내비게이션의 href="#about"은 같은 문서의 id="about"으로 이동합니다. id는 한 문서에서 고유해야 합니다. 같은 이름을 여러 곳에 붙이면 링크의 목적지와 폼 연결을 구분하기 어렵습니다.

3. label과 설명, 선택 묶음을 연결하기

입력 칸 옆에 글자를 적는 것만으로는 프로그램이 그 글자를 입력 이름으로 연결하지 못할 수 있습니다. 예제는 label for="email"input id="email"을 정확히 맞춥니다. 이메일이라는 글자를 클릭해 입력 칸에 초점이 가는지 확인하세요. WAI의 폼 이름표 안내도 가능한 경우 이 명시적 연결을 사용하도록 설명합니다.

name="email"은 제출 데이터의 키입니다. id는 문서 안의 연결 대상이므로 둘을 같은 문자열로 쓸 수는 있지만 역할은 다릅니다. aria-describedby="email-help"는 입력 이름과 별도로 도움말을 연결합니다. placeholder는 입력 중 사라질 수 있으므로 보이는 이름표의 대체재로 사용하지 않습니다.

온라인과 오프라인은 name="mode"가 같아서 하나만 선택하는 라디오 그룹입니다. fieldset으로 묶고 legend로 공통 질문을 제공합니다. 개별 선택지는 각각 다른 idlabel을 갖습니다. WAI의 폼 그룹 안내에서 설명하는 공통 질문과 개별 이름의 역할을 나누어 적용했습니다.

작게 이어 붙이기: 같은 질문의 선택지 묶기

<fieldset>
  <legend>참여 방식 (필수)</legend>
  <input id="online" name="mode" type="radio" value="online" required checked>
  <label for="online">온라인</label>
  <input id="offline" name="mode" type="radio" value="offline" required>
  <label for="offline">오프라인</label>
</fieldset>

4. 링크 이동과 폼 제출의 결과 구분하기

소개 위치로 이동하는 것은 a, 입력 값을 제출하는 것은 button type="submit"입니다. div에 클릭 처리만 붙이는 방식은 키보드 조작을 추가로 구현해야 합니다. 기본 요소를 쓰면 HTML 단계에서도 링크는 Enter로, 버튼은 키보드로 실행할 수 있습니다.

required가 있는 닉네임이나 이메일을 비우면 브라우저가 제출을 막고 해당 입력을 안내합니다. type="email"은 기본 형식을 확인하지만 주소가 실제로 존재하는지 확인하지는 않습니다. 안내 문구와 표시 모양은 브라우저 및 언어 설정에 따라 달라질 수 있습니다.

유효한 연습 값을 제출하면 method="get"에 따라 nickname, email, mode가 URL 쿼리에 포함됩니다. action="index.html#practice"는 같은 파일을 다시 열고 연습 영역으로 이동합니다. 이 페이지는 값을 저장하거나 신청을 접수하지 않습니다. 입력이 비워지거나 복원되는 모습은 브라우저에 따라 달라질 수 있습니다. 실제 서비스에서는 처리 서버와 서버 측 검증이 별도로 필요합니다. 폼의 구성과 전송 역할은 WHATWG Forms에서 확인할 수 있습니다.

5. 직접 확인하고 다음 실습으로 넘어가기

확인 방법 기대 결과
Tab을 처음부터 누릅니다. 본문 바로가기, 두 탐색 링크, 닉네임, 이메일, 라디오 그룹, 제출 버튼 순서로 주요 조작을 만납니다. 라디오 그룹 안에서는 방향키로 항목을 바꿉니다.
본문 바로가기에서 Enter를 누릅니다. main으로 이동합니다. tabindex=”-1″은 본문을 일반 Tab 순서에 추가하지 않고 초점 대상으로 만들기 위한 설정입니다.
이메일 이름표를 클릭합니다. 이메일 입력 칸으로 초점이 이동합니다.
빈 값으로 제출하고, 이메일에 abc를 입력해 다시 제출합니다. 필수 값 또는 이메일 형식 문제 때문에 해당 제출이 진행되지 않습니다.
닉네임에 learner, 이메일에 learner@example.com을 넣어 제출합니다. 같은 HTML 페이지가 열리고 URL에 연습 데이터가 포함됩니다. 실제 개인정보는 넣지 않습니다.

제목을 글자 크기로만 골랐다면 내용 계층에 맞게 수정하세요. 모든 입력을 이름표와 연결했고 링크와 버튼의 역할을 설명할 수 있다면 첫 실습은 끝납니다. 다음 단계에서는 이 문서 구조 위에 CSS를 연결해 크기와 스타일이 결정되는 규칙을 배웁니다.

완성 코드: 두 파일을 같은 폴더에 저장하기

아래 두 블록은 complete 폴더의 파일 전체입니다. 먼저 starter에서 과제를 수행하고, 동작이나 계산이 다를 때 해당 선언을 비교하세요. HTML 안에 CSS를 붙여 넣지 않고 styles.css를 연결합니다.

index.html

실습 파일

파일 경로를 확인하고 같은 프로젝트 안에 저장하세요. 이미지 등 소스 목록에 없는 파일과 실행 안내는 실습 ZIP에 포함되어 있습니다.

시작본의 주석에 과제를 작성한 뒤 완성본과 비교하세요. 시작본에는 참여 방식 입력이 없습니다. 완성본에는 기본 선택된 온라인과 오프라인 라디오가 있고 제출 URL에 mode 값이 포함됩니다.

수정 전

index.html

<!doctype html>
<html lang="ko">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>퍼블리싱 공부 모임 | HTML 실습</title>
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <a href="#main">본문으로 바로 가기</a>
    <header>
      <p>퍼블리싱 공부 모임</p>
      <nav aria-label="페이지 안내">
        <ul>
          <li><a href="#about">모임 소개</a></li>
          <li><a href="#practice">입력 연습</a></li>
        </ul>
      </nav>
    </header>
    <main id="main" tabindex="-1">
      <h1>HTML로 모임 안내 페이지 만들기</h1>
      <section id="about" aria-labelledby="about-title">
        <h2 id="about-title">모임 소개</h2>
        <p>토요일에 HTML 문서 구조와 폼을 연습합니다.</p>
        <ul>
          <li>대상: HTML을 처음 작성하는 분</li>
          <li>준비물: 텍스트 편집기와 웹 브라우저</li>
        </ul>
      </section>
      <section id="practice" aria-labelledby="practice-title">
        <h2 id="practice-title">입력 연습</h2>
        <p id="form-help">
          필수 항목을 채워 주세요. 실제 신청은 되지 않습니다. 연습용 값만 입력하면
          주소에 값이 표시되고 이 페이지가 다시 열립니다.
        </p>
        <form action="index.html#practice" method="get" aria-describedby="form-help">
          <p>
            <label for="nickname">연습 닉네임 (필수)</label><br />
            <input id="nickname" name="nickname" type="text" required maxlength="20" />
          </p>
          <p>
            <label for="email">연습 이메일 (필수)</label><br />
            <input
              id="email"
              name="email"
              type="email"
              required
              aria-describedby="email-help"
            />
          </p>
          <p id="email-help">실제 주소 대신 learner@example.com을 입력해 보세요.</p>
          <!-- 실습: 여기에 fieldset, legend, 참여 방식 라디오 두 개를 추가하세요. -->
          <p><button type="submit">연습 값 확인</button></p>
        </form>
      </section>
    </main>
    <footer><p>이 페이지는 HTML 학습용 예제입니다.</p></footer>
  </body>
</html>

styles.css

* {
  box-sizing: border-box;
}
body {
  margin: 0;
  padding: 1rem;
  font-family: system-ui, sans-serif;
  line-height: 1.6;
  color: #222;
  background: #fff;
}
header,
main,
footer {
  max-width: 48rem;
  margin-inline: auto;
}
a {
  color: #333;
}
input,
button {
  font: inherit;
  color: #222;
  background: #fff;
  border: 1px solid #777;
  padding: 0.5rem;
}
input:not([type='radio']) {
  width: 100%;
  max-width: 28rem;
}
fieldset {
  border: 1px solid #999;
  margin: 1rem 0;
  padding: 1rem;
}
button {
  cursor: pointer;
}
:focus-visible {
  outline: 3px solid #333;
  outline-offset: 4px;
}

수정 방법

index.html

수정 위치: form의 제출 버튼 바로 앞에 있는 실습 주석을 확인하세요.

이유: 서로 배타적인 선택지는 같은 name을 가진 radio로 묶고 fieldset과 legend로 질문의 범위를 전달해야 합니다.

재현과 기대 결과: 시작본에는 참여 방식 입력이 없습니다. 완성본에는 기본 선택된 온라인과 오프라인 라디오가 있고 제출 URL에 mode 값이 포함됩니다.

index.html에서 아래 차이가 있는 위치를 수정하세요. -는 삭제할 줄, +는 추가할 줄이며 표시는 실제 코드에 넣지 않습니다.

본문의 확인 절차로 변경 결과를 확인합니다.

--- 수정 전/index.html
+++ 수정 후/index.html
@@ -49,7 +49,24 @@
             />
           </p>
           <p id="email-help">실제 주소 대신 learner@example.com을 입력해 보세요.</p>
-          <!-- 실습: 여기에 fieldset, legend, 참여 방식 라디오 두 개를 추가하세요. -->
+          <fieldset>
+            <legend>참여 방식 (필수)</legend>
+            <p>
+              <input
+                id="online"
+                name="mode"
+                type="radio"
+                value="online"
+                required
+                checked
+              />
+              <label for="online">온라인</label>
+            </p>
+            <p>
+              <input id="offline" name="mode" type="radio" value="offline" required />
+              <label for="offline">오프라인</label>
+            </p>
+          </fieldset>
           <p><button type="submit">연습 값 확인</button></p>
         </form>
       </section>

수정 후 탭의 전체 파일과 비교한 뒤 본문의 확인 절차를 실행하세요.

styles.css

이 파일은 변경하지 않습니다. 다른 파일을 수정하는 동안 기존 내용을 유지하세요.

수정 후

index.html

<!doctype html>
<html lang="ko">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>퍼블리싱 공부 모임 | HTML 실습</title>
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <a href="#main">본문으로 바로 가기</a>
    <header>
      <p>퍼블리싱 공부 모임</p>
      <nav aria-label="페이지 안내">
        <ul>
          <li><a href="#about">모임 소개</a></li>
          <li><a href="#practice">입력 연습</a></li>
        </ul>
      </nav>
    </header>
    <main id="main" tabindex="-1">
      <h1>HTML로 모임 안내 페이지 만들기</h1>
      <section id="about" aria-labelledby="about-title">
        <h2 id="about-title">모임 소개</h2>
        <p>토요일에 HTML 문서 구조와 폼을 연습합니다.</p>
        <ul>
          <li>대상: HTML을 처음 작성하는 분</li>
          <li>준비물: 텍스트 편집기와 웹 브라우저</li>
        </ul>
      </section>
      <section id="practice" aria-labelledby="practice-title">
        <h2 id="practice-title">입력 연습</h2>
        <p id="form-help">
          필수 항목을 채워 주세요. 실제 신청은 되지 않습니다. 연습용 값만 입력하면
          주소에 값이 표시되고 이 페이지가 다시 열립니다.
        </p>
        <form action="index.html#practice" method="get" aria-describedby="form-help">
          <p>
            <label for="nickname">연습 닉네임 (필수)</label><br />
            <input id="nickname" name="nickname" type="text" required maxlength="20" />
          </p>
          <p>
            <label for="email">연습 이메일 (필수)</label><br />
            <input
              id="email"
              name="email"
              type="email"
              required
              aria-describedby="email-help"
            />
          </p>
          <p id="email-help">실제 주소 대신 learner@example.com을 입력해 보세요.</p>
          <fieldset>
            <legend>참여 방식 (필수)</legend>
            <p>
              <input
                id="online"
                name="mode"
                type="radio"
                value="online"
                required
                checked
              />
              <label for="online">온라인</label>
            </p>
            <p>
              <input id="offline" name="mode" type="radio" value="offline" required />
              <label for="offline">오프라인</label>
            </p>
          </fieldset>
          <p><button type="submit">연습 값 확인</button></p>
        </form>
      </section>
    </main>
    <footer><p>이 페이지는 HTML 학습용 예제입니다.</p></footer>
  </body>
</html>

styles.css

* {
  box-sizing: border-box;
}
body {
  margin: 0;
  padding: 1rem;
  font-family: system-ui, sans-serif;
  line-height: 1.6;
  color: #222;
  background: #fff;
}
header,
main,
footer {
  max-width: 48rem;
  margin-inline: auto;
}
a {
  color: #333;
}
input,
button {
  font: inherit;
  color: #222;
  background: #fff;
  border: 1px solid #777;
  padding: 0.5rem;
}
input:not([type='radio']) {
  width: 100%;
  max-width: 28rem;
}
fieldset {
  border: 1px solid #999;
  margin: 1rem 0;
  padding: 1rem;
}
button {
  cursor: pointer;
}
:focus-visible {
  outline: 3px solid #333;
  outline-offset: 4px;
}

index.html 전체 코드 보기

styles.css

styles.css 전체 코드 보기

starter에서 직접 바꿀 내용

  1. 입력 폼 끝에 fieldset과 legend로 참여 방식 질문을 추가하세요.
  2. 온라인·오프라인 라디오에 서로 다른 id, 같은 name=”mode”, 각 value와 label을 작성하세요. 온라인을 기본 선택하고 필수 선택 조건을 지정하세요.
  3. Tab과 방향키로 참여 방식을 바꾸고 연습 값을 제출해 주소의 mode 값을 확인하세요.

완성 파일과 비교하기 전에 위 확인 절차를 직접 수행하세요. 한 번에 한 속성 또는 한 구조만 바꾸고 화면이 달라지는 이유를 적으면 다음 문제에서도 원인을 찾기 쉽습니다.

이 글이 도움이 되었나요?

조회 중

웹 퍼블리싱 학습 순서

필수 13개 · 전체 15개

읽음 기록 관리

전체 과정 목차 (15개)
  1. 필수 학습 · HTML 입문: 시맨틱 문서와 접근 가능한 폼 만들기 현재 글
  2. 필수 학습 · CSS 입문: 박스 모델·선택자·캐스케이드 직접 확인하기
  3. 필수 학습 · CSS Flex와 미디어 쿼리 입문: 반응형 카드 페이지 만들기
  4. 필수 학습 · CSS position 완전 이해: relative·absolute·fixed·sticky 실습
  5. 필수 학습 · HTML details와 summary로 접근 가능한 FAQ 만들기
  6. 필수 학습 · CSS Grid 실습: Flex 학습 카드로 열 맞추기와 auto-fit 비교
  7. 필수 학습 · CSS overflow 해결: 긴 텍스트·min-width: 0·말줄임 실습
  8. 필수 학습 · HTML 표 만들기: caption·scope로 비교표를 읽기 쉽게, 모바일에서는 표만 스크롤하기
  9. 필수 선수 · HTML CSS 이미지 문제 해결 실습: 경로·비율·잘림·흐림·CLS
  10. 필수 학습 · Container Query 실습: 같은 카드를 본문과 사이드바에 배치하기
  11. 선택 참고 · CSS Baseline 보는 법: 지원 범위와 폴백을 직접 확인하기
  12. 필수 학습 · CSS z-index가 안 먹힐 때: stacking context와 부모 층 디버깅
  13. 필수 선수 · 웹 접근성 기초 점검: 키보드 순서·초점 표시·확대 화면을 직접 확인하는 체크리스트
  14. 필수 학습 · HTML CSS 종합 실습: 소개·카드·이미지·요금표·문의 폼·FAQ가 있는 반응형 서비스 페이지
  15. 선택 참고 · 프론트엔드 SEO 기초: HTML 구조와 이미지 성능 진단하기

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기