Skip to content

HTML 치트시트

웹 페이지를 만들기 위한 표준 마크업 언어.

01

문서 구조

기본 페이지 템플릿

모든 HTML5 문서는 <!DOCTYPE html>로 시작하고, 그 다음 접근성과 SEO를 위해 lang 속성이 있는 <html>이 옵니다. <head>는 메타데이터(charset, viewport, title)를 포함하고, <body>는 보이는 콘텐츠를 가집니다. viewport 메타 태그는 모바일 기기에서 적절한 렌더링을 보장합니다.

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My Web Page</title>
</head>
<body>
  <h1>Hello, World!</h1>
  <p>Welcome to my page.</p>
</body>
</html>

Head 섹션 & 메타데이터

<head> 요소는 기계가 읽을 수 있는 메타데이터를 포함합니다. charset은 문자 인코딩을 선언합니다(UTF-8은 거의 모든 문자를 포함). description 메타 태그는 SEO에 중요합니다 - 검색 엔진이 결과에 표시합니다. 스크립트에 defer를 사용하여 HTML 파싱 후에 로드하세요.

html
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Page Title - Site Name</title>
  <meta name="description" content="A brief description for SEO">
  <meta name="author" content="John Doe">
  <link rel="stylesheet" href="style.css">
  <script src="app.js" defer></script>
</head>

주석 & 조건부 주석

HTML 주석은 <!--로 시작해서 -->로 끝납니다. 브라우저에 표시되지 않지만 페이지 소스에서 볼 수 있습니다. 주석은 메모를 남기고, 섹션을 표시하고(TODO, FIXME), 문서화하는 데 유용합니다. 조건부 주석(IE 전용)은 더 이상 사용되지 않지만 레거시 코드에 나타날 수 있습니다.

html
<!-- This is a single-line comment -->

<!--
  This is a
  multi-line comment
-->

<!-- TODO: Add form validation -->
<!-- FIXME: Fix layout on mobile -->

<!--[if lt IE 9]>
  <script src="html5shiv.js"></script>
<![endif]-->

제목 계층

HTML은 6개의 제목 수준을 제공합니다. h1(가장 높음)에서 h6(가장 낮음). 수준을 건너뛰지 않고 계층적 순서로 사용하세요. 검색 엔진은 페이지 구조를 이해하기 위해 제목을 사용합니다. 모범 사례: 페이지당 하나의 h1, 그 다음 주요 섹션은 h2, 하위 섹션은 h3 등.

html
<h1>Main Page Title</h1>
<h2>Major Section</h2>
<h3>Subsection</h3>
<h4>Sub-subsection</h4>
<h5>Minor Heading</h5>
<h6>Lowest Level Heading</h6>

<!-- Use only one h1 per page for SEO -->
<h1>Blog Post Title</h1>
<h2>Introduction</h2>
<h3>Background</h3>

Div & Span 컨테이너

<div>는 요소를 그룹화하고 스타일을 적용하는 블록 수준 컨테이너입니다. <span>은 텍스트의 작은 부분에 스타일을 지정하는 인라인 컨테이너입니다. 둘 다 의미가 없는 일반 요소입니다 - 더 나은 접근성과 SEO를 위해 적절할 때 시맨틱 태그(header, nav, main, article)를 선호하세요.

html
<div class="container">
  <div id="header" class="header">
    <span class="logo">MySite</span>
    <span class="tagline">Best Content</span>
  </div>
  <div class="content">
    <p>Main <span class="highlight">content</span> here.</p>
  </div>
</div>
02

텍스트 서식

굵게, 이탤릭 & 강조

<strong>은 중요한 텍스트를 나타내고(스크린 리더가 강조), <b>는 순수하게 시각적인 굵게입니다. 마찬가지로 <em>은 스트레스 강조를 나타내고(시맨틱), <i>는 시각적 이탤릭입니다. 접근성을 위해 <strong>과 <em>을 선호하세요. <b>와 <i>는 시맨틱 중요성 없이 스타일 목적으로 허용됩니다.

html
<p>This is <strong>important</strong> text.</p>
<p>This is <b>bold</b> text.</p>
<p>This is <em>emphasized</em> text.</p>
<p>This is <i>italic</i> text.</p>
<p><strong>Warning:</strong> Do not touch!</p>

Mark, Delete & Insert

<mark>는 관련성 있는 텍스트를 강조합니다(검색 강조 표시 같은). <del>은 삭제된 텍스트를 표시하고, <ins>은 삽입된 텍스트를 표시합니다 - 편집을 표시하는 데 유용합니다. <s>는 오래된 정보에 취소선을 그립니다. <small>은 부가 설명이나 저작권에 사용됩니다. <u>는 링크와 닮아 조심스럽게 사용해야 합니다.

html
<p>Please <mark>highlight this</mark> word.</p>
<p>The price is <del>$99</del> <ins>$79</ins> now.</p>
<p><s>Old information</s> is struck through.</p>
<p>Use <u>underline</u> sparingly.</p>
<p>Small <small>print</small> for side comments.</p>

위첨자 & 아래첨자

<sub>는 아래첨자 텍스트(기준선 아래)를 만듭니다 - 화학식(H2O)과 수학 변수에 사용. <sup>는 위첨자 텍스트(기준선 위)를 만듭니다 - 지수(x²), 서수(1st), 각주에 사용. 둘 다 수직 위치와 글꼴 크기를 조정하는 인라인 요소입니다.

html
<p>H<sub>2</sub>O is water.</p>
<p>E = mc<sup>2</sup> is Einstein's equation.</p>
<p>1st<sup>st</sup> January 2024</p>
<p>CO<sub>2</sub> emissions are rising.</p>
<p>x<sup>2</sup> + y<sup>2</sup> = r<sup>2</sup></p>

인용 & 출처

<blockquote>는 블록 수준 인용(들여쓰기, 여러 줄)에 사용됩니다. <q>는 인라인 짧은 인용에 사용됩니다(자동으로 따옴표 추가). cite 속성은 출처 URL을 제공합니다. <cite>는 작품의 제목이나 저자 이름을 태그합니다. 이 요소들은 인용된 콘텐츠에 대한 시맨틱 마크업을 향상시킵니다.

html
<blockquote cite="https://example.com/source">
  <p>The best way to predict the future is to invent it.</p>
  <footer>— <cite>Alan Kay</cite></footer>
</blockquote>

<p>As <q cite="https://example.com">someone once said</q>,
knowledge is power.</p>

코드 & 서식이 지정된 텍스트

<code>는 인라인 코드 스니펫(고정폭 글꼴)을 표시합니다. <pre>는 서식이 지정된 텍스트를 위해 공백과 줄바꿈을 보존합니다. <pre><code>를 결합하면 적절한 들여쓰기로 코드 블록을 표시합니다. <kbd>는 키보드 입력을 나타냅니다. <samp>는 프로그램 출력에 사용하고, <var>는 수학 표현의 변수에 사용합니다.

html
<p>Use the <code>console.log()</code> function.</p>
<p>Press <kbd>Ctrl</kbd> + <kbd>C</kbd> to copy.</p>

<pre><code>function hello() {
  console.log("Hello, World!");
  return true;
}</code></pre>

줄바꿈 & 가로선

<br>은 텍스트 내에 줄바꿈을 만듭니다(드물게 사용, CSS 선호). <hr>은 섹션 간의 주제적 구분을 나타내는 가로선을 만듭니다. <wbr>은 긴 단어나 URL에 대해 줄바꿈 기회를 제안하여 브라우저가 최적의 지점에서 줄바꿈할 수 있게 합니다. 간격을 위해 <br>을 사용하지 마세요 - CSS 마진을 대신 사용하세요.

html
<p>First line<br>Second line<br>Third line</p>

<p>Paragraph one.</p>
<hr>
<p>Paragraph two after a thematic break.</p>

<wbr><!-- word break opportunity for long URLs -->
https://example.com/<wbr>very<wbr>long<wbr>url
04

이미지 & 멀티미디어

이미지

항상 접근성과 SEO를 위해 alt 텍스트를 포함하세요 - 스크린 리더와 이미지 로드 실패 시 이미지를 설명합니다. loading='lazy'는 화면 밖 이미지 로딩을 지연시켜 페이지 속도를 향상합니다. srcset과 sizes는 반응형 이미지를 가능하게 합니다 - 브라우저가 기기 해상도와 viewport에 따라 최적의 이미지를 선택합니다. 레이아웃 이동을 방지하기 위해 항상 width와 height를 지정하세요.

html
<!-- Basic image -->
<img src="photo.jpg" alt="A sunset over mountains" width="800" height="600">

<!-- Image with lazy loading -->
<img src="hero.jpg" alt="Hero banner" loading="lazy" decoding="async">

<!-- Responsive image with srcset -->
<img
  src="medium.jpg"
  srcset="small.jpg 480w, medium.jpg 800w, large.jpg 1200w"
  sizes="(max-width: 600px) 480px, 800px"
  alt="Responsive photo"
>

Picture 요소

<picture>는 art direction과 형식 폴백을 제공합니다. <source> 요소는 대안을 제공합니다 - 브라우저가 지원하는 첫 번째 형식을 선택합니다(WebP/AVIF가 더 작음). media 속성은 다른 화면 크기에 다른 이미지를 가능하게 합니다. 마지막 <img>는 폴백이며 항상 있어야 합니다. 이는 완전히 다른 이미지를 제공하는 데 srcset보다 우수합니다.

html
<picture>
  <source srcset="webp-image.webp" type="image/webp">
  <source srcset="avif-image.avif" type="image/avif">
  <source srcset="wide.jpg" media="(min-width: 800px)">
  <img src="fallback.jpg" alt="Art-directed image">
</picture>

오디오

<audio>는 사운드 콘텐츠를 포함합니다. controls는 재생/일시정지/볼륨 컨트롤을 추가합니다. 여러 <source> 요소는 형식 폴백을 제공합니다(MP3가 가장 널리 지원됨). autoplay는 브라우저에 의해 제한됩니다 - muted autoplay는 일반적으로 허용됩니다. loop는 오디오를 반복합니다. 오디오가 지원되지 않는 경우 내부 텍스트가 표시됩니다. 사용자 경험을 위해 항상 controls를 제공하세요.

html
<audio controls>
  <source src="song.mp3" type="audio/mpeg">
  <source src="song.ogg" type="audio/ogg">
  Your browser does not support the audio element.
</audio>

<!-- Autoplay (muted required in most browsers) -->
<audio autoplay muted loop>
  <source src="background.mp3" type="audio/mpeg">
</audio>

비디오

<video>는 재생, 일시정지, 볼륨, 전체화면 컨트롤과 함께 비디오를 포함합니다. poster는 재생 전 썸네일을 설정합니다. 여러 source는 형식 폴백을 제공합니다(MP4/H.264가 가장 호환됨). <track>은 접근성을 위해 자막, 캡션, 또는 설명을 추가합니다. playsinline은 iOS에서 강제 전체화면을 방지합니다. muted로 자동 재생하지 않는 한 항상 controls를 포함하세요.

html
<video controls width="640" height="360" poster="thumbnail.jpg">
  <source src="movie.mp4" type="video/mp4">
  <source src="movie.webm" type="video/webm">
  <track kind="subtitles" src="subs.vtt" srclang="en" label="English">
  Your browser does not support video.
</video>

<!-- Autoplay muted loop (common for backgrounds) -->
<video autoplay muted loop playsinline>
  <source src="bg.mp4" type="video/mp4">
</video>

Figure & Figcaption

<figure>는 캡션과 함께 이미지, 다이어그램, 코드 목록, 또는 인용 같은 자체 완비된 콘텐츠를 그룹화합니다. <figcaption>은 캡션 또는 범례를 제공합니다. 이 시맨틱 그룹화는 접근성을 향상합니다 - 스크린 리더가 관계를 알립니다. 그림은 텍스트에서 참조될 수 있고('Figure 1 참조') 문맥을 잃지 않고 레이아웃에서 이동할 수 있습니다.

html
<figure>
  <img src="chart.png" alt="Quarterly sales chart showing 20% growth">
  <figcaption>Figure 1: Q4 2024 sales growth by region.</figcaption>
</figure>

<figure>
  <blockquote>
    <p>The best way to predict the future is to invent it.</p>
  </blockquote>
  <figcaption>— Alan Kay, 1971</figcaption>
</figure>

Iframe 포함

<iframe>은 외부 콘텐츠(비디오, 지도, 다른 페이지)를 포함합니다. 접근성을 위해 항상 title 속성을 포함하세요. loading='lazy'는 화면 밖 iframe의 성능을 향상합니다. allow 속성은 기능 정책을 지정합니다. sandbox 속성은 보안을 위해 iframe의 기능을 제한합니다. 주의 - iframe은 성능과 보안에 영향을 미칠 수 있습니다.

html
<!-- Embed a YouTube video -->
<iframe
  src="https://www.youtube.com/embed/dQw4w9WgXcQ"
  width="560" height="315"
  title="YouTube video"
  frameborder="0"
  allow="accelerometer; autoplay; encrypted-media"
  allowfullscreen>
</iframe>

<!-- Embed a map -->
<iframe
  src="https://maps.google.com/maps?q=Paris&output=embed"
  title="Map of Paris" loading="lazy">
</iframe>
05

리스트

순서 없는 리스트

<ul>은 순서 없는(글머리 기호) 리스트를 만듭니다. 각 항목은 <li>로 감싸집니다. list-style-type CSS 속성은 글머리 스타일(disc, circle, square, none)을 변경합니다. 순서 없는 리스트는 순서가 중요하지 않은 항목에 사용됩니다. 내비게이션 메뉴와 기능 리스트에 list-style: none과 사용자 정의 스타일을 사용하세요.

html
<ul>
  <li>Apple</li>
  <li>Banana</li>
  <li>Cherry</li>
</ul>

<!-- Custom bullet style via CSS -->
<ul style="list-style-type: square;">
  <li>First item</li>
  <li>Second item</li>
</ul>

순서 있는 리스트

<ol>은 순차적 항목을 위해 순서 있는(번호 매김) 리스트를 만듭니다. start 속성은 시작 번호를 설정합니다. reversed는 항목을 내림차순으로 표시합니다. type 속성은 번호 매김 스타일(1, A, a, I, i)을 변경합니다. 단계, 순위, 또는 순서가 중요한 모든 시퀀스에 <ol>을 사용하세요.

html
<ol>
  <li>First step</li>
  <li>Second step</li>
  <li>Third step</li>
</ol>

<!-- Start from a specific number -->
<ol start="5">
  <li>Fifth item</li>
  <li>Sixth item</li>
</ol>

<!-- Reverse order -->
<ol reversed>
  <li>Countdown 3</li>
  <li>Countdown 2</li>
  <li>Countdown 1</li>
</ol>

설명 리스트

<dl>은 용어(<dt>)와 설명(<dd>)을 짝지어 설명 리스트를 만듭니다. 이는 용어집, FAQ 페이지, 용어-정의 쌍에 이상적입니다. 여러 <dd>가 하나의 <dt>를 설명할 수 있고, 그 반대도 가능합니다. 설명 리스트는 정의 테이블이 부족한 시맨틱 의미를 제공하여 접근성을 향상합니다.

html
<dl>
  <dt>HTML</dt>
  <dd>HyperText Markup Language</dd>

  <dt>CSS</dt>
  <dd>Cascading Style Sheets</dd>

  <dt>JavaScript</dt>
  <dd>A programming language for the web</dd>
</dl>

중첩 리스트

리스트는 <li> 안에 <ul>이나 <ol>을 배치하여 중첩할 수 있습니다. 브라우저는 자동으로 중첩된 리스트를 들여쓰기하고 글머리 스타일을 변경할 수 있습니다. 중첩은 목차, 파일 트리, 또는 다단계 메뉴 같은 계층적 구조를 만듭니다. 사용성을 위해 중첩을 합리적으로 유지하세요(2-3 수준). 깊게 중첩된 리스트는 읽기 어려워집니다.

html
<ul>
  <li>Fruits
    <ul>
      <li>Apple</li>
      <li>Banana</li>
    </ul>
  </li>
  <li>Vegetables
    <ol>
      <li>Carrot</li>
      <li>Spinach</li>
    </ol>
  </li>
</ul>

링크가 있는 리스트 (내비게이션)

<nav> 내부의 링크 리스트는 시맨틱 내비게이션 메뉴를 만듭니다. 중첩된 <ul> 요소는 드롭다운 서브메뉴를 형성합니다. 이 구조는 스크린 리더에 접근 가능하고 CSS로 쉽게 스타일링할 수 있습니다. 대화형 드롭다운에 aria 속성(aria-expanded, aria-haspopup)을 사용하세요. 리스트 기반 내비게이션은 접근 가능한 메뉴의 표준 패턴입니다.

html
<nav>
  <ul class="menu">
    <li><a href="/">Home</a></li>
    <li><a href="/products">Products</a>
      <ul class="submenu">
        <li><a href="/products/a">Product A</a></li>
        <li><a href="/products/b">Product B</a></li>
      </ul>
    </li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>
06

테이블

기본 테이블

<table>은 표 형식 데이터를 만듭니다. <thead>는 헤더 행을, <tbody>는 본문 행을, <tfoot>은 푸터 행을 그룹화합니다. <tr>은 테이블 행, <th>는 헤더 셀(굵게, 가운데 정렬), <td>는 데이터 셀입니다. thead/tbody/tfoot 사용은 접근성을 향상하고 더 나은 스타일링을 가능하게 합니다. 테이블은 레이아웃이 아닌 데이터에 사용해야 합니다.

html
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Age</th>
      <th>City</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Alice</td>
      <td>30</td>
      <td>New York</td>
    </tr>
    <tr>
      <td>Bob</td>
      <td>25</td>
      <td>London</td>
    </tr>
  </tbody>
</table>

셀 병합

colspan은 셀이 여러 열을 병합하게 합니다(수평 병합). rowspan은 셀이 여러 행을 병합하게 합니다(수직 병합). 이 속성은 여러 열을 그룹화하는 헤더 같은 복잡한 테이블 레이아웃을 만듭니다. 병합에 주의하세요 - 스크린 리더가 파싱하기 어려워질 수 있습니다. 항상 접근성을 테스트하세요.

html
<table border="1">
  <tr>
    <th colspan="2">Name</th>
    <th rowspan="2">Age</th>
  </tr>
  <tr>
    <th>First</th>
    <th>Last</th>
  </tr>
  <tr>
    <td>Alice</td>
    <td>Smith</td>
    <td>30</td>
  </tr>
</table>

테이블 캡션 & 범위

<caption>은 테이블의 제목을 제공하여 접근성을 향상합니다. <th>의 scope 속성은 스크린 리더에게 헤더가 열(scope='col')에 적용되는지 행(scope='row')에 적용되는지 알려줍니다. 이는 복잡한 테이블에 필수적입니다. 더 복잡한 테이블의 경우 id와 headers 속성을 사용하여 셀을 헤더와 명시적으로 연결하세요.

html
<table>
  <caption>Employee Salary Report 2024</caption>
  <thead>
    <tr>
      <th scope="col">Name</th>
      <th scope="col">Department</th>
      <th scope="col">Salary</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Alice</th>
      <td>Engineering</td>
      <td>$95,000</td>
    </tr>
  </tbody>
</table>

컬럼 그룹

<colgroup>은 스타일링을 위해 컬럼을 그룹화합니다. 그 안의 <col> 요소는 특정 컬럼에 스타일(특히 너비)을 적용합니다. 이는 모든 셀에 너비를 설정하는 것보다 효율적입니다. 컬럼 그룹은 또한 한 번에 여러 컬럼을 스타일링하기 위해 span 속성을 지원합니다. 모든 행에 걸쳐 일관된 컬럼 너비에 사용하세요.

html
<table>
  <colgroup>
    <col style="width: 30%;">
    <col style="width: 50%;">
    <col style="width: 20%;">
  </colgroup>
  <tr>
    <th>Product</th>
    <th>Description</th>
    <th>Price</th>
  </tr>
  <tr>
    <td>Laptop</td>
    <td>15-inch laptop</td>
    <td>$999</td>
  </tr>
</table>

테이블 스타일링

border-collapse: collapse는 인접한 테두리를 하나로 병합합니다. 얼룩무늬 행(가독성 향상)을 위해 :nth-child(even)를 사용하세요. :hover는 마우스 오버 시 행을 강조합니다. th 스타일링은 헤더를 구분합니다. 테이블 스타일링을 깔끔하고 읽기 쉽게 유지하세요 - 과도한 테두리를 피하세요. 반응형 테이블은 작은 화면을 위해 래퍼에 overflow-x: auto가 필요할 수 있습니다.

html
<style>
  table { border-collapse: collapse; width: 100%; }
  th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
  th { background-color: #f4f4f4; }
  tr:nth-child(even) { background-color: #f9f9f9; }
  tr:hover { background-color: #e0e0e0; }
</style>
<table>
  <tr><th>Name</th><th>Score</th></tr>
  <tr><td>Alice</td><td>95</td></tr>
  <tr><td>Bob</td><td>87</td></tr>
</table>
07

폼 & 입력

폼 구조

<form>은 입력 컨트롤을 감쌉니다. action은 제출 URL을 지정하고, method는 GET(URL에 보임) 또는 POST(숨겨짐)입니다. enctype='multipart/form-data'는 파일 업로드에 필요합니다. <fieldset>은 관련 필드를 그룹화하고, <legend>은 캡션을 제공합니다. 접근성을 위해 항상 for/id 매칭으로 <label>을 입력과 연결하세요.

html
<form action="/submit" method="POST" enctype="multipart/form-data">
  <fieldset>
    <legend>Personal Information</legend>

    <label for="name">Name:</label>
    <input type="text" id="name" name="name" required>

    <label for="email">Email:</label>
    <input type="email" id="email" name="email" required>
  </fieldset>

  <button type="submit">Submit</button>
</form>

입력 유형

HTML5는 내장 검증과 모바일 친화적 키보드가 있는 많은 입력 유형을 도입했습니다. type='email'은 이메일 형식을 검증합니다. type='number'는 스핀너를 제공합니다. type='date'와 'time'은 네이티브 픽커를 표시합니다. type='color'는 색상 픽커를 엽니다. type='range'는 슬라이더를 만듭니다. type='file'은 accept로 파일 유형을 제한합니다. type='hidden'은 사용자에게 표시되지 않는 데이터를 저장합니다.

html
<input type="text" name="username" placeholder="Username">
<input type="email" name="email" required>
<input type="password" name="pwd" minlength="8">
<input type="number" name="age" min="0" max="120" step="1">
<input type="date" name="birthday">
<input type="time" name="appointment">
<input type="color" name="favcolor" value="#ff0000">
<input type="range" name="volume" min="0" max="100">
<input type="file" name="upload" accept="image/*">
<input type="hidden" name="token" value="abc123">

Select & Textarea

<select>는 드롭다운을 만듭니다. <optgroup>은 레이블로 관련 옵션을 그룹화합니다. <option>은 선택 항목을 정의합니다; selected 속성은 하나를 미리 선택합니다. <textarea>는 여러 줄 텍스트 입력에 사용됩니다. rows와 cols는 보이는 크기를 설정합니다. maxlength는 문자 수를 제한합니다. placeholder는 힌트를 제공합니다. 둘 다 required와 disabled 속성을 지원합니다.

html
<label for="country">Country:</label>
<select id="country" name="country">
  <optgroup label="North America">
    <option value="us">United States</option>
    <option value="ca">Canada</option>
  </optgroup>
  <optgroup label="Europe">
    <option value="uk">United Kingdom</option>
    <option value="fr">France</option>
  </optgroup>
</select>

<label for="bio">Bio:</label>
<textarea id="bio" name="bio" rows="4" cols="40"
  maxlength="500" placeholder="Tell us about yourself"></textarea>

체크박스 & 라디오 버튼

체크박스는 여러 선택을 허용하고, 라디오 버튼은 하나만 허용합니다(같은 name을 공유할 때). checked 속성은 옵션을 미리 선택합니다. 클릭 가능한 텍스트를 위해 항상 <label>로 입력을 감싸세요. 접근성을 위해 관련 선택 항목을 그룹화하기 위해 <fieldset>과 <legend>를 사용하세요. 값은 선택된 경우에만 서버로 전송됩니다.

html
<fieldset>
  <legend>Select your interests:</legend>
  <label><input type="checkbox" name="interest" value="tech" checked> Tech</label>
  <label><input type="checkbox" name="interest" value="music"> Music</label>
  <label><input type="checkbox" name="interest" value="sports"> Sports</label>
</fieldset>

<fieldset>
  <legend>Choose a plan:</legend>
  <label><input type="radio" name="plan" value="free" checked> Free</label>
  <label><input type="radio" name="plan" value="pro"> Pro</label>
  <label><input type="radio" name="plan" value="enterprise"> Enterprise</label>
</fieldset>

폼 검증

HTML5는 내장 클라이언트 측 검증을 제공합니다. required는 비어 있으면 제출을 방지합니다. minlength/maxlength는 텍스트 길이를 제한합니다. pattern은 사용자 정의 검증에 regex를 사용합니다(title 속성이 사용자를 안내). type='email'과 type='url'은 형식을 검증합니다. min/max는 숫자와 날짜에 작동합니다. 검증을 비활성화하려면 폼에 novalidate를 추가하세요.

html
<form>
  <input type="text" name="username" required
         minlength="3" maxlength="20"
         pattern="[A-Za-z0-9_]+"
         title="3-20 alphanumeric characters">

  <input type="email" name="email" required>

  <input type="url" name="website"
         placeholder="https://example.com">

  <input type="number" name="age" min="18" max="99">

  <button type="submit">Submit</button>
</form>

버튼 & Datalist

type='submit'은 폼을 제출하고, type='reset'은 모든 필드를 지우고, type='button'은 사용자 정의 버튼입니다. <datalist>은 <input> 요소에 자동완성 제안을 제공합니다 - list/id로 연결합니다. <select>와 달리 사용자는 사용자 정의 값을 입력할 수 있습니다. 이는 텍스트 입력의 유연성과 제안의 편리함을 결합합니다.

html
<!-- Button types -->
<button type="submit">Submit Form</button>
<button type="reset">Reset</button>
<button type="button" onclick="alert('Hi')">Click Me</button>

<!-- Datalist for autocomplete suggestions -->
<label for="browser">Choose a browser:</label>
<input list="browsers" name="browser" id="browser">
<datalist id="browsers">
  <option value="Chrome">
  <option value="Firefox">
  <option value="Safari">
  <option value="Edge">
</datalist>
08

시맨틱 HTML

Header & 내비게이션

<header>는 소개 콘텐츠를 나타냅니다 - 일반적으로 로고, 제목, 내비게이션. 페이지 상단이나 <article>이나 <section> 내에 나타날 수 있습니다. <nav>는 내비게이션 링크를 식별하여 스크린 리더가 내비게이션으로 건너뛸 수 있게 합니다. 페이지는 여러 헤더를 가질 수 있습니다(예: 섹션당 하나). Header는 head와 같지 않습니다 - 보이는 콘텐츠입니다.

html
<header>
  <h1>My Website</h1>
  <nav>
    <ul>
      <li><a href="/">Home</a></li>
      <li><a href="/blog">Blog</a></li>
      <li><a href="/contact">Contact</a></li>
    </ul>
  </nav>
</header>

Main & Article

<main>은 페이지의 주요 콘텐츠를 감쌉니다(페이지당 하나만). <article>은 독립적으로 배포될 수 있는 자체 완비된 콘텐츠(블로그 게시물, 뉴스 기사, 포럼 게시물)를 나타냅니다. <time>과 datetime은 SEO를 위해 기계가 읽을 수 있는 날짜를 제공합니다. <section>은 제목과 함께 주제적으로 관련된 콘텐츠를 그룹화합니다. 이 태그들은 문서 개요와 접근성을 향상합니다.

html
<main>
  <article>
    <h1>Blog Post Title</h1>
    <p>Published on <time datetime="2024-01-15">January 15, 2024</time></p>
    <p>The main content of the article...</p>
    <section>
      <h2>Subsection</h2>
      <p>More content...</p>
    </section>
  </article>
</main>

Section & Aside

<section>은 제목과 관련 콘텐츠를 그룹화합니다 - 콘텐츠에 자연스러운 제목이 있을 때 사용하세요. <aside>은 주요 콘텐츠와 접적으로 관련된 콘텐츠(사이드바, 인용구, 광고, 관련 링크)를 나타냅니다. 둘 다 문서 구조를 향상합니다. 섹션은 중첩될 수 있습니다. aside는 일반적으로 사이드바로 스타일링되지만 시맨틱이지 표현용이 아닙니다.

html
<section>
  <h2>Services</h2>
  <p>We offer the following services:</p>
  <article>
    <h3>Web Design</h3>
    <p>Custom website design...</p>
  </article>
</section>

<aside>
  <h3>Related Articles</h3>
  <ul>
    <li><a href="/post1">Related Post 1</a></li>
    <li><a href="/post2">Related Post 2</a></li>
  </ul>
</aside>

Footer

<footer>는 페이지나 섹션의 푸터 콘텐츠를 포함합니다 - 저작권, 관련 문서 링크, 연락처 정보, 사이트맵. 페이지는 여러 푸터를 가질 수 있습니다(예: 기사 섹션당 하나). 푸터는 시각적으로 하단에 있을 필요는 없지만, 시맨틱적으로 마무리 콘텐츠를 나타냅니다. 저작권 기호에 &copy;를 사용하세요.

html
<footer>
  <div>
    <h3>About Us</h3>
    <p>Company information...</p>
  </div>
  <nav>
    <a href="/privacy">Privacy Policy</a>
    <a href="/terms">Terms of Service</a>
    <a href="/sitemap">Sitemap</a>
  </nav>
  <p>&copy; 2024 My Company. All rights reserved.</p>
</footer>

Details & Summary

<details>는 대화형 공개 위젯을 만듭니다 - 사용자가 <summary>을 클릭할 때까지 콘텐츠가 숨겨집니다. open 속성은 기본적으로 펼쳐진 상태로 표시합니다. 이는 네이티브 HTML이며 JavaScript가 필요 없습니다. FAQ, 접을 수 있는 섹션, 점진적 공개에 적합합니다. 중첩된 details는 다단계 아코디언을 만듭니다. [open] 속성 선택자로 스타일링하세요.

html
<details>
  <summary>Click to expand FAQ</summary>
  <p>This content is hidden by default and shown when the summary is clicked.</p>
</details>

<!-- Open by default -->
<details open>
  <summary>What is HTML?</summary>
  <p>HTML (HyperText Markup Language) is the standard language for creating web pages.</p>
</details>

<!-- Nested details -->
<details>
  <summary>Advanced Topics</summary>
  <details>
    <summary>Sub-topic</summary>
    <p>Nested content...</p>
  </details>
</details>
09

메타 태그 & SEO

Charset & Viewport

charset='UTF-8'은 문자 인코딩을 선언합니다 - 모든 문자를 올바르게 표시하는 데 필수적입니다. viewport 메타 태그는 반응형 디자인에 중요합니다: width=device-width는 기기 너비에 일치하고, initial-scale=1.0은 줌 수준을 설정합니다. 이것이 없으면 모바일 브라우저가 데스크톱 너비로 페이지를 렌더링합니다. 이 두 메타 태그는 모든 HTML 문서에 있어야 합니다.

html
<head>
  <!-- Character encoding -->
  <meta charset="UTF-8">

  <!-- Responsive viewport -->
  <meta name="viewport"
        content="width=device-width, initial-scale=1.0">

  <!-- Internet Explorer compatibility -->
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
</head>

Description & Keywords

description 메타 태그는 SEO에 가장 중요합니다 - 검색 엔진이 결과에서 제목 아래에 표시합니다(160자 이내로 유지). keywords는 최신 검색 엔진에서 대부분 무시됩니다. robots는 인덱싱을 제어합니다: index/noindex, follow/nofollow. author는 콘텐츠 작성자를 표시합니다. 클릭률을 높이기 위해 매력적인 description을 작성하세요.

html
<meta name="description"
      content="Free HTML tutorials for beginners. Learn HTML tags, attributes, and semantic markup with examples.">

<meta name="keywords"
      content="HTML, tutorial, web development, markup">

<meta name="author" content="John Doe">
<meta name="robots" content="index, follow">

Open Graph (Facebook)

Open Graph(og:) 메타 태그는 Facebook, LinkedIn, 기타 플랫폼에서 공유될 때 페이지가 어떻게 나타나는지 제어합니다. og:title, og:description, og:image, og:url이 가장 중요합니다. og:type은 article, website, product 등이 될 수 있습니다. 이미지는 최소 1200x630 픽셀이어야 합니다. 배포 전 Facebook의 Sharing Debugger로 테스트하세요.

html
<meta property="og:title" content="My Amazing Article">
<meta property="og:description" content="A brief description of the article.">
<meta property="og:image" content="https://example.com/image.jpg">
<meta property="og:url" content="https://example.com/article">
<meta property="og:type" content="article">
<meta property="og:site_name" content="My Website">
<meta property="og:locale" content="en_US">

Twitter Cards

Twitter Card 메타 태그는 Twitter/X에서 링크가 어떻게 나타나는지 제어합니다. twitter:card는 summary, summary_large_image, 또는 player가 될 수 있습니다. 큰 이미지 카드가 가장 매력적입니다. twitter:site와 twitter:creator는 Twitter 계정에 연결합니다. Open Graph 태그가 있으면 Twitter가 폴백하지만, 명시적 Twitter 태그가 더 많은 제어를 제공합니다. Twitter Card Validator로 테스트하세요.

html
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="My Amazing Article">
<meta name="twitter:description" content="A brief description.">
<meta name="twitter:image" content="https://example.com/image.jpg">
<meta name="twitter:site" content="@mywebsite">
<meta name="twitter:creator" content="@author">

Canonical & 구조화된 데이터

canonical 링크 태그는 검색 엔진에게 페이지의 선호 URL을 알려주어 중복 콘텐츠 페널티를 방지합니다. JSON-LD 구조화된 데이터는 검색 엔진이 콘텐츠를 이해하는 데 도움을 주어 리치 스니펫(검색 결과의 별점, breadcrumbs, FAQ 아코디언)을 가능하게 합니다. Article, Product, Event, 또는 FAQPage 같은 schema.org 유형을 사용하세요. Google의 Rich Results Test로 검증하세요.

html
<!-- Canonical URL (prevents duplicate content issues) -->
<link rel="canonical" href="https://example.com/article">

<!-- Structured data (JSON-LD) -->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "My Article Title",
  "author": "John Doe",
  "datePublished": "2024-01-15",
  "image": "https://example.com/image.jpg"
}
</script>
10

SVG & Canvas

SVG 기본

SVG(Scalable Vector Graphics)는 품질 손실 없이 확장되는 해상도 독립적인 그래픽을 만듭니다. 인라인 SVG는 CSS로 스타일링되고 JavaScript로 조작될 수 있습니다. 일반적인 모양: <circle>, <rect>, <line>, <ellipse>, <polygon>, <path>. SVG는 아이콘, 로고, 다이어그램에 이상적입니다. 래스터 이미지와 달리 SVG는 어떤 크기에서도 선명합니다.

html
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
  <circle cx="100" cy="100" r="50" fill="blue" stroke="black" stroke-width="2"/>
  <text x="100" y="105" text-anchor="middle" fill="white">Circle</text>
</svg>

<!-- Inline SVG (can be styled with CSS) -->
<svg width="100" height="100">
  <rect x="10" y="10" width="80" height="80" fill="red" rx="10"/>
</svg>

SVG 모양

SVG 모양은 좌표 기반 속성을 사용합니다. <rect>는 x, y, width, height가 필요하고, rx/ry는 둥근 모서리를 만듭니다. <circle>은 cx, cy(중심)와 r(반지름)이 필요합니다. <ellipse>는 다른 x/y 반지름을 위해 rx와 ry를 사용합니다. <line>은 x1,y1에서 x2,y2로 연결합니다. <polygon>은 points 목록을 받습니다. fill은 내부 색상, stroke는 윤곽선을 설정합니다.

html
<svg width="300" height="200">
  <!-- Rectangle -->
  <rect x="10" y="10" width="80" height="50" fill="orange" rx="10" ry="10"/>

  <!-- Circle -->
  <circle cx="200" cy="50" r="40" fill="green"/>

  <!-- Ellipse -->
  <ellipse cx="100" cy="150" rx="60" ry="30" fill="purple"/>

  <!-- Line -->
  <line x1="10" y1="180" x2="290" y2="180" stroke="black" stroke-width="2"/>

  <!-- Polygon -->
  <polygon points="250,10 290,80 210,80" fill="pink"/>
</svg>

SVG Paths

<path>는 가장 강력한 SVG 요소이며, 명령과 함께 d 속성을 사용합니다: M(moveto), L(lineto), H/V(수평/수직), C(cubic Bezier), Q(quadratic Bezier), A(arc), Z(close path). 대문자 = 절대 좌표, 소문자 = 상대 좌표. viewBox는 좌표계를 정의합니다. SVG 아이콘은 확장 가능하고 스타일 가능한 아이콘에 paths를 사용합니다.

html
<svg width="200" height="200">
  <!-- M=move, L=line, C=curve, Z=close -->
  <path d="M 10 10 L 100 10 L 100 100 Z" fill="none" stroke="black"/>

  <!-- Bezier curve -->
  <path d="M 10 100 C 50 10, 150 10, 190 100" fill="none" stroke="red"/>

  <!-- Arc -->
  <path d="M 10 100 A 90 90 0 0 1 190 100" fill="none" stroke="blue"/>
</svg>

<!-- SVG icon example -->
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
  <path d="M12 2L2 7v10l10 5 10-5V7z"/>
</svg>

Canvas 기본

<canvas>은 JavaScript를 통해 조작되는 비트맵 그리기 표면입니다. getContext('2d')로 2D 컨텍스트를 얻습니다. SVG와 달리 canvas는 픽셀 기반(확장 불가)이고 DOM의 일부가 아닙니다. Canvas는 복잡한 애니메이션, 게임, 이미지 처리에 더 좋습니다. SVG는 정적 그래픽, 아이콘, 대화형 차트에 더 좋습니다. 필요에 따라 선택하세요.

html
<canvas id="myCanvas" width="300" height="200"></canvas>

<script>
  const canvas = document.getElementById('myCanvas');
  const ctx = canvas.getContext('2d');

  // Draw a rectangle
  ctx.fillStyle = 'blue';
  ctx.fillRect(10, 10, 100, 80);

  // Draw a circle
  ctx.beginPath();
  ctx.arc(200, 50, 30, 0, Math.PI * 2);
  ctx.fillStyle = 'red';
  ctx.fill();

  // Draw text
  ctx.font = '20px Arial';
  ctx.fillText('Hello Canvas', 50, 150);
</script>

인라인 SVG 아이콘

SVG <symbol>은 <use href='#id'>로 참조되는 재사용 가능한 아이콘을 정의합니다. 이것이 SVG 스프라이트 기술입니다 - 아이콘을 한 번 정의하고 어디서든 사용하세요. symbol은 숨겨지고(display:none) <use>로 인스턴스화됩니다. 아이콘은 fill='currentColor'을 통해 CSS 'color'에서 색상을 상속합니다. 이 접근 방식은 효율적(하나의 정의, 여러 사용)이고 완전히 스타일 가능합니다.

html
<!-- Reusable SVG symbol -->
<svg style="display:none">
  <symbol id="icon-home" viewBox="0 0 24 24">
    <path d="M12 2L2 12h3v8h6v-6h2v6h6v-8h3z"/>
  </symbol>
</svg>

<!-- Use the icon -->
<svg width="24" height="24" fill="currentColor">
  <use href="#icon-home"/>
</svg>

<!-- Styled icon -->
<svg width="32" height="32" class="icon" style="color: blue;">
  <use href="#icon-home"/>
</svg>
11

접근성 (ARIA)

ARIA 역할

ARIA 역할은 스크린 리더를 위해 요소가 하는 일을 정의합니다. 랜드마크 역할(banner, navigation, main, complementary, contentinfo)은 스크린 리더 사용자가 페이지 영역 간에 이동할 수 있게 합니다. HTML5 시맨틱 요소(<header>, <nav>, <main>)는 암시적 ARIA 역할을 가지고 있어 role 속성 추가는 종종 중복입니다 - 하지만 오래된 브라우저에 유용합니다. 가능할 때 항상 ARIA보다 시맨틱 HTML을 선호하세요.

html
<!-- Landmark roles for page structure -->
<header role="banner">Site Header</header>
<nav role="navigation">Main Menu</nav>
<main role="main">Primary Content</main>
<aside role="complementary">Sidebar</aside>
<footer role="contentinfo">Footer</footer>
<form role="search">Search Form</form>

<!-- Document structure roles -->
<article role="article">Blog Post</article>
<section role="region" aria-label="Comments">Comments</section>

aria-label & aria-labelledby

aria-label은 보이는 레이블이 없을 때 접근 가능한 이름을 정의합니다(예: 아이콘 전용 버튼). aria-labelledby는 컨트롤을 레이블링하는 보이는 텍스트 요소의 ID를 참조합니다 - 화면에 보이는 레이블이 이미 있을 때 유용합니다. 여러 ID가 나열되면 텍스트가 순서대로 연결됩니다. 보이는 텍스트가 있을 때 레이블을 동기화 상태로 유지하므로 aria-labelledby를 선호하세요.

html
<!-- aria-label provides accessible name directly -->
<button aria-label="Close menu" onclick="closeMenu()">✕</button>
<input type="search" aria-label="Search products" />

<!-- aria-labelledby references visible text -->
<div id="billing-label">Billing Address</div>
<input type="text" aria-labelledby="billing-label" />

<!-- Multiple labels combined -->
<span id="city">City</span>
<span id="required">required</span>
<input aria-labelledby="city required" />

aria-describedby & 툴팁

aria-describedby는 요소 ID를 통해 요소를 추가 설명 텍스트에 연결합니다. 스크린 리더는 레이블 후에 이 설명을 알립니다. 도움말 텍스트, 힌트, 오류 메시지에 이상적입니다. 오류 메시지에 role='alert'을 사용하여 나타날 때 즉시 알려지도록 하세요. 오류가 있는 필드에 aria-invalid='true'를 설정하여 스크린 리더가 잘못된 상태를 나타내도록 하세요.

html
<label for="password">Password</label>
<input type="password" id="password"
       aria-describedby="pwd-help pwd-rules" />
<p id="pwd-help">Must be at least 8 characters</p>
<p id="pwd-rules">Include uppercase, number, and symbol</p>

<!-- Error messaging -->
<input type="email" id="email" aria-describedby="email-error" aria-invalid="true" />
<p id="email-error" role="alert">Please enter a valid email address</p>

실시간 영역 (aria-live)

실시간 영역은 동적 콘텐츠 변경을 스크린 리더 사용자에게 알립니다. aria-live='polite'는 알리기 전에 일시정지를 기다립니다(상태 업데이트에 좋음). aria-live='assertive'는 즉시 중단합니다(중요한 오류용). aria-atomic='true'는 변경된 부분만이 아닌 전체 영역 콘텐츠를 읽습니다. aria-relevant는 어떤 변경 유형(추가, 제거, 텍스트)이 알림을 트리거하는지 지정합니다. SPA, 채팅 앱, 자동 업데이트 콘텐츠에 필수적입니다.

html
<!-- Polite: announces when user is idle -->
<div aria-live="polite" id="status">Saving...</div>

<!-- Assertive: announces immediately, interrupts -->
<div aria-live="assertive" role="alert" id="errors">
  Connection lost!
</div>

<!-- aria-atomic reads entire region, not just changes -->
<div aria-live="polite" aria-atomic="true" id="cart">
  3 items in cart
</div>

<!-- aria-relevant controls what changes are announced -->
<div aria-live="polite" aria-relevant="additions text">
  Chat messages appear here
</div>

포커스 관리 & tabindex

tabindex는 키보드 포커스 동작을 제어합니다. tabindex='0'은 비대화형 요소(div, span)를 자연스러운 DOM 순서로 포커스 가능하게 만듭니다 - 사용자 정의 위젯에 사용하세요. tabindex='-1'은 JS .focus()를 통해서만 프로그래밍 방식으로 포커스 가능하게 만듭니다 - 모달과 건너뛰기 대상에 유용합니다. 자연스러운 탭 순서를 재정의하고 혼란을 만들므로 양수 tabindex 값을 사용하지 마세요. 항상 페이지의 첫 번째 포커스 가능 요소로 건너뛰기 링크를 제공하세요.

html
<!-- tabindex="0": element is focusable in natural order -->
<div tabindex="0" role="button" onkeypress="handleKey()">
  Custom clickable div
</div>

<!-- tabindex="-1": focusable only via JS, removed from tab order -->
<div tabindex="-1" id="modal">Modal content</div>
<script>
  document.getElementById('modal').focus();
</script>

<!-- tabindex="1+": DO NOT USE — breaks natural tab order -->
<!-- Avoid: <div tabindex="2"> -->

<!-- Skip link for keyboard users -->
<a href="#main-content" class="skip-link">Skip to main content</a>
<main id="main-content">...</main>

숨겨진 콘텐츠 & 스크린 리더 전용

aria-hidden='true'는 요소를 보이게 유지하면서 접근성 트리에서 제거합니다 - 장식용 아이콘, 중복 텍스트, 또는 화면 밖 콘텐츠에 사용하세요. .sr-only 패턴은 텍스트를 시각적으로 숨기지만 스크린 리더가 읽을 수 있게 합니다 - 아이콘 전용 버튼에 필수적입니다. 포커스 가능한 요소에 aria-hidden을 사용하지 마세요. 키보드와 스크린 리더 내비게이션 간의 단절을 만듭니다. clip 기법이 가장 강력한 시각적 숨김 방법입니다.

html
<!-- aria-hidden: visible but hidden from screen readers -->
<div aria-hidden="true">
  <i class="icon-decoration"></i> Decorative only
</div>

<!-- visually hidden but available to screen readers -->
<style>
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap; border: 0;
}
</style>
<button>
  <span class="sr-only">Delete item</span>
  <i class="icon-trash" aria-hidden="true"></i>
</button>
12

HTML5 API (지리정보, 저장소, 드래그 & 드롭)

Geolocation API

Geolocation API는 사용자의 물리적 위치를 요청할 수 있게 합니다. 브라우저는 항상 권한을 요청합니다 - 사용자가 명시적으로 액세스를 허가해야 합니다. getCurrentPosition은 성공과 오류 콜백과 옵션 객체(enableHighAccuracy, timeout, maximumAge)를 받습니다. 연속 추적에는 watchPosition()을 사용하고, 반환된 ID를 clearWatch()에 전달하세요. 항상 오류를 우아하게 처리하세요(권한 거부, 위치 사용 불가, 시간 초과). Geolocation은 최신 브라우저에서 HTTPS가 필요합니다.

html
<!-- Request user location -->
<button onclick="getLocation()">Get My Location</button>
<p id="demo"></p>

<script>
function getLocation() {
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(
      (pos) => {
        document.getElementById("demo").innerHTML =
          "Lat: " + pos.coords.latitude +
          "<br>Lon: " + pos.coords.longitude;
      },
      (err) => {
        alert("Error: " + err.message);
      },
      { enableHighAccuracy: true, timeout: 5000 }
    );
  } else {
    alert("Geolocation not supported.");
  }
}
</script>

웹 저장소 (localStorage & sessionStorage)

웹 저장소는 클라이언트에 키-값 저장소를 제공합니다. localStorage는 무기한 유지되고, sessionStorage는 탭이 닫힐 때 지워집니다. 둘 다 문자열만 저장합니다 - 객체에는 JSON.stringify/parse를 사용하세요. 저장소 제한은 origin당 약 5-10MB입니다. 쿠키와 달리 저장소 데이터는 모든 HTTP 요청과 함께 전송되지 않습니다. 주의: 저장소는 동기식이고 메인 스레드를 차단하며, 같은 origin의 모든 스크립트가 액세스할 수 있습니다(민감한 데이터에 부적합).

html
<!-- localStorage: persists until cleared -->
<script>
// Store data
localStorage.setItem("username", "alice");
localStorage.setItem("prefs", JSON.stringify({theme: "dark", lang: "en"}));

// Retrieve data
const user = localStorage.getItem("username");
const prefs = JSON.parse(localStorage.getItem("prefs") || "{}");

// Remove single item
localStorage.removeItem("username");

// Clear all items
localStorage.clear();

// sessionStorage: cleared when tab closes
sessionStorage.setItem("tempToken", "abc123");
</script>

드래그 앤 드롭 API

HTML5 드래그 앤 드롭 API는 네이티브 드래그 상호작용을 가능하게 합니다. 소스 요소에 draggable='true'를 설정하세요. ondragstart는 드래그가 시작될 때 발생합니다 - dataTransfer.setData()를 사용하여 드래그된 데이터를 저장하세요. 드롭 대상의 ondragover는 드롭을 허용하기 위해 preventDefault()를 호출해야 합니다. ondrop은 실제 드롭을 처리합니다 - dataTransfer.getData()로 데이터를 검색하세요. OS에서 브라우저로 파일을 드래그할 수 있습니다(event.dataTransfer.files 사용). 복잡한 앱의 경우 더 나은 크로스 브라우저 지원을 위해 SortableJS 같은 라이브러리를 고려하세요.

html
<div id="drag1" draggable="true" ondragstart="drag(event)">
  Drag me!
</div>
<div id="dropzone" ondrop="drop(event)" ondragover="allowDrop(event)">
  Drop here
</div>

<script>
function allowDrop(ev) {
  ev.preventDefault(); // necessary to allow dropping
}

function drag(ev) {
  ev.dataTransfer.setData("text", ev.target.id);
}

function drop(ev) {
  ev.preventDefault();
  const data = ev.dataTransfer.getData("text");
  ev.target.appendChild(document.getElementById(data));
}
</script>

Page Visibility API

Page Visibility API는 페이지가 사용자에게 보이는지(최소화되지 않음, 백그라운드 탭이 아님) 알려줍니다. 사용자가 보지 않을 때 비용이 많이 드는 애니메이션, 비디오 재생, 또는 폴링을 일시정지하여 배터리와 CPU를 절약하세요. document.hidden은 불린이고, document.visibilityState는 'visible', 'hidden', 또는 'prerender'를 반환합니다. visibilitychange 이벤트는 전환 시 발생합니다. 이는 실제 가시성 감지를 위해 blur/focus보다 신뢰할 수 있습니다.

html
<script>
document.addEventListener("visibilitychange", () => {
  if (document.hidden) {
    console.log("Tab is hidden — pause video/animation");
    video.pause();
  } else {
    console.log("Tab is visible — resume");
    video.play();
  }
});

// Check current state
if (document.visibilityState === "visible") {
  console.log("Page is visible");
}
</script>

Fullscreen API

Fullscreen API는 모든 요소를 전체화면 모드로 표시할 수 있게 합니다. requestFullscreen()은 사용자 제스처(클릭/키 누름)로 트리거되어야 합니다. 다른 브라우저는 벤더 접두사(webkit, moz, ms)가 필요할 수 있습니다. fullscreenchange 이벤트는 전체화면 진입 또는 종료 시 발생합니다. document.fullscreenElement는 현재 전체화면 요소를 참조합니다(전체화면이 아닌 경우 null). :fullscreen CSS 의사 클래스로 전체화면 요소를 다르게 스타일링하세요.

html
<button onclick="openFullscreen()">Fullscreen</button>
<button onclick="closeFullscreen()">Exit</button>
<div id="container">Content to fullscreen</div>

<script>
function openFullscreen() {
  const elem = document.getElementById("container");
  if (elem.requestFullscreen) {
    elem.requestFullscreen();
  } else if (elem.webkitRequestFullscreen) {
    elem.webkitRequestFullscreen();
  }
}

function closeFullscreen() {
  if (document.exitFullscreen) {
    document.exitFullscreen();
  }
}

document.addEventListener("fullscreenchange", () => {
  console.log("Fullscreen:", !!document.fullscreenElement);
});
</script>
13

웹 컴포넌트

사용자 정의 요소

사용자 정의 요소로 캡슐화된 동작을 가진 재사용 가능한 HTML 태그를 만들 수 있습니다. 클래스는 HTMLElement(또는 HTMLElement 서브클래스)를 확장합니다. connectedCallback은 요소가 DOM에 추가될 때 발생합니다 - 렌더링에 사용하세요. attributeChangedCallback은 속성 변경에 반응하지만, observedAttributes에 나열된 속성에만 해당합니다. 이름은 충돌을 피하기 위해 하이픈을 포함해야 합니다(예: 'my-greeting'). customElements.define()이 요소를 등록합니다. 스타일 캡슐화가 필요하지 않으면 Shadow DOM을 피하세요.

html
<my-greeting name="World"></my-greeting>

<script>
class MyGreeting extends HTMLElement {
  constructor() {
    super();
    this.name = this.getAttribute("name") || "Guest";
  }
  connectedCallback() {
    this.innerHTML = `<h2>Hello, ${this.name}!</h2>`;
  }
  // Observe attribute changes
  static get observedAttributes() {
    return ["name"];
  }
  attributeChangedCallback(name, oldVal, newVal) {
    if (name === "name") {
      this.name = newVal;
      this.connectedCallback();
    }
  }
}
customElements.define("my-greeting", MyGreeting);
</script>

Shadow DOM

Shadow DOM은 스타일과 DOM 캡슐화를 제공합니다 - 섀도우 트리 내부의 스타일은 외부로 누출되지 않고, 페이지 스타일도 내부로 누출되지 않습니다. attachShadow({mode:'open'})는 element.shadowRoot로 액세스 가능한 섀도우 루트를 만듭니다. mode:'closed'는 외부 액세스를 거부합니다(거의 사용 안 됨). <slot> 요소는 light DOM 자식이 투영되는 자리 표시자입니다. 이름 있는 슬롯(<slot name='title'>)은 일치하는 slot 속성을 가진 자식과 매칭됩니다. ::slotted()가 투영된 콘텐츠를 스타일링합니다. Shadow DOM은 자체 완비된 재사용 가능한 컴포넌트 구축의 핵심입니다.

html
<my-card>
  <span slot="title">Card Title</span>
  <p slot="body">Card content here.</p>
</my-card>

<script>
class MyCard extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: "open" });
    shadow.innerHTML = `
      <style>
        .card { border: 1px solid #ccc; padding: 16px; border-radius: 8px; }
        ::slotted([slot="title"]) { font-size: 1.5em; font-weight: bold; }
      </style>
      <div class="card">
        <slot name="title"></slot>
        <slot name="body"></slot>
      </div>
    `;
  }
}
customElements.define("my-card", MyCard);
</script>

HTML 템플릿

<template> 요소는 페이지 로드 시 렌더링되지 않는 HTML을 보유합니다 - 콘텐츠는 비활성입니다(스크립트 실행 안 됨, 이미지 로드 안 됨, 스타일 적용 안 됨). .content(DocumentFragment)로 콘텐츠에 액세스하고 cloneNode(true)로 복제하여 인스턴스화하세요. 템플릿은 JavaScript로 생성된 반복 구조에 이상적입니다. innerHTML과 달리 템플릿은 한 번 파싱되고 재파싱 없이 반복 복제될 수 있습니다. 사용자 정의 요소와 Shadow DOM과 결합하여 웹 컴포넌트 표준을 형성합니다.

html
<template id="row-template">
  <tr>
    <td class="name"></td>
    <td class="email"></td>
  </tr>
</template>

<script>
function addRow(name, email) {
  const template = document.getElementById("row-template");
  const clone = template.content.cloneNode(true);
  clone.querySelector(".name").textContent = name;
  clone.querySelector(".email").textContent = email;
  document.querySelector("tbody").appendChild(clone);
}

addRow("Alice", "[email protected]");
addRow("Bob", "[email protected]");
</script>

라이프사이클 콜백

사용자 정의 요소는 4개의 라이프사이클 콜백을 가집니다. constructor()는 요소가 생성될 때 실행됩니다(여기서 무거운 작업 피하기). connectedCallback()은 DOM에 추가될 때 발생합니다 - 설정과 렌더링에 이상적입니다. disconnectedCallback()은 제거 시 발생합니다 - 정리에 사용하세요(이벤트 리스너, 타이머). adoptedCallback()은 adoptNode()를 통해 새 문서로 이동될 때 발생합니다(드묾). attributeChangedCallback()은 관찰된 속성에만 발생합니다. 업그레이드 순서는 항상: constructor → connectedCallback입니다. 속성 변경은 connectedCallback 전에 발생할 수 있습니다.

html
<script>
class LifecycleDemo extends HTMLElement {
  constructor() {
    super();
    console.log("1. constructor: element created");
  }
  connectedCallback() {
    console.log("2. connected: added to DOM");
  }
  disconnectedCallback() {
    console.log("3. disconnected: removed from DOM");
  }
  adoptedCallback() {
    console.log("4. adopted: moved to new document");
  }
  attributeChangedCallback(attr, oldVal, newVal) {
    console.log(`5. attribute '${attr}' changed: ${oldVal} -> ${newVal}`);
  }
  static get observedAttributes() {
    return ["data-status"];
  }
}
customElements.define("lifecycle-demo", LifecycleDemo);
</script>

사용자 정의된 내장 요소

사용자 정의된 내장 요소는 네이티브 HTML 요소(button, input, div 같은)를 확장하여 내장 동작을 상속합니다. 사용자 정의 태그 이름 대신 'is' 속성을 사용하세요. customElements.define()의 세 번째 인수는 확장 중인 네이티브 요소를 지정합니다. 이는 완전히 새로운 것을 만드는 대신 기존 요소를 강화할 때 유용합니다(예: <input>에 검증 추가). 참고: Safari는 사용자 정의된 내장 요소를 지원하지 않습니다 - 크로스 브라우저 호환성을 위해 자율 사용자 정의 요소를 고려하세요.

html
<!-- Extend a native button -->
<button is="my-button">Click me</button>

<script>
class MyButton extends HTMLButtonElement {
  constructor() {
    super();
    this.addEventListener("click", () => {
      this.textContent = "Clicked!";
      this.style.background = "lightgreen";
    });
  }
  connectedCallback() {
    this.style.fontWeight = "bold";
  }
}
customElements.define("my-button", MyButton,
  { extends: "button" });
</script>
14

미디어 요소 (오디오 & 비디오)

비디오 요소

<video> 요소는 플러그인 없이 비디오를 포함합니다. controls 속성은 내장 재생/일시정지/볼륨 컨트롤을 표시합니다. poster는 재생 전 미리보기 이미지를 지정합니다. 여러 <source> 태그는 형식 폴백을 제공합니다 - 브라우저가 지원하는 첫 번째 것을 사용합니다(MP4/H.264가 가장 널리 지원되고, WebM은 개방적이고 효율적). <track> 요소는 WebVTT 파일을 통해 자막, 캡션, 또는 설명을 추가합니다. 매우 오래된 브라우저를 위해 항상 폴백 텍스트를 포함하세요. 자동 재생의 경우 muted 속성을 추가하세요 - 대부분의 브라우저가 소리와 함께 자동 재생을 차단합니다.

html
<video width="640" height="360" controls poster="preview.jpg">
  <source src="movie.mp4" type="video/mp4">
  <source src="movie.webm" type="video/webm">
  <track kind="subtitles" src="subs_en.vtt"
         srclang="en" label="English" default>
  <track kind="captions" src="caps_en.vtt"
         srclang="en" label="English Captions">
  Your browser does not support the video tag.
</video>

오디오 요소

<audio> 요소는 사운드를 포함합니다. controls는 내장 플레이어 UI를 표시합니다. controls가 없으면 요소가 보이지 않습니다 - JavaScript로 재생을 제어하세요(play(), pause(), volume, currentTime). preload='auto'는 브라우저가 파일을 버퍼링하도록 제안합니다; 'metadata'는 지속 시간/정보만 로드합니다; 'none'은 재생할 때까지 아무것도 로드하지 않습니다. MP3는 보편적으로 지원되고, OGG Vorbis는 개방적이지만 Safari에서 지원되지 않습니다. 게임이나 정밀한 타이밍의 경우 더 낮은 지연 시간과 효과를 위해 <audio> 대신 Web Audio API를 사용하세요.

html
<!-- Basic audio player -->
<audio controls>
  <source src="song.mp3" type="audio/mpeg">
  <source src="song.ogg" type="audio/ogg">
  Your browser does not support audio.
</audio>

<!-- Audio controlled by JavaScript -->
<audio id="player" src="song.mp3" preload="auto"></audio>
<button onclick="document.getElementById('player').play()">Play</button>
<button onclick="document.getElementById('player').pause()">Pause</button>
<button onclick="document.getElementById('player').volume += 0.1">Vol+</button>

Picture & srcset (반응형 이미지)

반응형 이미지는 적절한 크기의 이미지를 제공하여 성능을 향상합니다. 너비 설명자(480w, 800w)와 sizes 힌트가 있는 srcset은 브라우저가 최적의 이미지를 선택하게 합니다 - 이는 해상도 전환에 선호됩니다. media 쿼리가 있는 <picture>는 art direction을 가능하게 합니다(다른 화면에 다른 크롭). type 속성이 있는 <source>는 JPG/PNG 폴백과 함께 최신 형식 폴백(WebP, AVIF)을 제공합니다. 항상 폴백과 접근성을 위해 <picture>의 마지막 자식으로 일반 <img>를 포함하세요.

html
<!-- srcset: let browser choose resolution -->
<img src="small.jpg"
     srcset="small.jpg 480w, medium.jpg 800w, large.jpg 1200w"
     sizes="(max-width: 600px) 480px, 800px"
     alt="Responsive image">

<!-- picture: art direction with different images -->
<picture>
  <source media="(max-width: 600px)" srcset="mobile.jpg">
  <source media="(max-width: 1200px)" srcset="tablet.jpg">
  <img src="desktop.jpg" alt="Art-directed image">
</picture>

<!-- Modern format with fallback -->
<picture>
  <source type="image/webp" srcset="photo.webp">
  <source type="image/avif" srcset="photo.avif">
  <img src="photo.jpg" alt="With format fallback">
</picture>

iframe 포함

iframe은 현재 페이지 내에 다른 문서를 포함합니다. title 속성은 접근성에 필수적입니다 - 스크린 리더가 알립니다. sandbox 속성은 보안을 위해 iframe의 기능을 제한합니다: 빈 값은 모든 것을 차단합니다; 토큰(allow-scripts, allow-forms, allow-same-origin)을 추가하여 특정 기능을 다시 활성화하세요. loading='lazy'는 viewport 근처까지 로딩을 지연시킵니다. allow는 기능 정책(camera, microphone, autoplay)을 지정합니다. 신뢰할 수 없는 콘텐츠를 포함할 때 주의하세요 - sandbox하세요. 크로스 origin iframe은 JavaScript로 액세스할 수 없습니다.

html
<!-- Basic iframe -->
<iframe src="https://example.com"
        width="600" height="400"
        title="Embedded Content">
</iframe>

<!-- Sandboxed iframe for security -->
<iframe src="untrusted.html"
        sandbox="allow-scripts allow-same-origin"
        loading="lazy">
</iframe>

<!-- YouTube embed -->
<iframe src="https://www.youtube.com/embed/VIDEO_ID"
        allow="accelerometer; autoplay; encrypted-media"
        allowfullscreen
        title="YouTube video">
</iframe>

Embed & Object

<embed>와 <object>는 오래된 포함 방법으로, 대부분 <iframe>과 <video>로 대체되었습니다. <embed>는 self-closing이고 간단하지만 폴백 콘텐츠를 제공하지 않습니다. <object>는 더 유연합니다 - 태그 내부의 콘텐츠는 포함된 리소스를 표시할 수 없을 때 폴백으로 작용합니다. PDF와 SVG에 폴백이 필요한 경우 <object>를 사용하세요. 최신 웹 개발에서는 외부 페이지에 <iframe>, 미디어에 <video>/<audio>, 벡터 그래픽에 인라인 <svg>를 선호하세요. <embed>는 주로 플러그인 콘텐츠에 사용됩니다(Flash, 현재 더 이상 사용되지 않음).

html
<!-- embed: simple, self-closing -->
<embed src="animation.svg" type="image/svg+xml"
       width="300" height="200">

<!-- object: more flexible with fallback -->
<object data="document.pdf" type="application/pdf"
        width="100%" height="600px">
  <p>Unable to display PDF.
     <a href="document.pdf">Download it instead.</a>
  </p>
</object>

<!-- embed YouTube without iframe -->
<embed src="https://www.youtube.com/v/VIDEO_ID"
       type="application/x-shockwave-flash"
       width="560" height="315">
15

입력 유형 심층

날짜 & 시간 입력

HTML5 날짜/시간 입력 유형은 JavaScript 라이브러리 없이 네이티브 픽커를 제공합니다. type='date'는 달력을, type='time'은 시간 픽커를, type='datetime-local'은 둘 다 제공합니다. type='month'와 type='week'는 월과 주를 선택합니다. min/max는 선택 가능한 범위를 제한합니다. step은 세밀함을 정의합니다(예: 30분 간격의 경우 초 단위로 step='1800'). 표시 형식은 브라우저/로케일에 따라 다르지만, 제출된 값은 항상 ISO 8601 형식(YYYY-MM-DD)입니다. 모든 브라우저가 모든 유형을 동등하게 지원하지는 않습니다 - 크로스 브라우저 테스트하세요.

html
<label for="birthday">Birthday:</label>
<input type="date" id="birthday" min="1900-01-01" max="2025-12-31">

<label for="appt">Appointment:</label>
<input type="time" id="appt" min="09:00" max="17:00" step="1800">

<label for="meeting">Meeting:</label>
<input type="datetime-local" id="meeting">

<label for="month">Pick a month:</label>
<input type="month" id="month">

<label for="week">Pick a week:</label>
<input type="week" id="week">

숫자 & 범위 입력

type='number'는 위/아래 화살표가 있는 스핀너 컨트롤을 제공합니다. min, max, step이 값을 제한합니다. 통화의 경우 step='0.01'을 사용하세요. type='range'는 슬라이더를 렌더링합니다 - 값이 기본적으로 표시되지 않으므로 항상 <output>이나 현재 값을 보여주는 보이는 레이블과 짝지으세요. 숫자 입력은 비숫자 입력을 필터링하지만 일부 잘못된 문자를 여전히 허용합니다; 서버 측에서 검증하세요. 스핀너가 필요 없는 수량의 경우 inputmode='numeric'과 pattern 검증이 있는 type='text'를 고려하세요.

html
<label for="qty">Quantity (1-100):</label>
<input type="number" id="qty" min="1" max="100"
       step="1" value="10">

<label for="price">Price ($):</label>
<input type="number" id="price" min="0" step="0.01"
       placeholder="0.00">

<label for="volume">Volume:</label>
<input type="range" id="volume" min="0" max="100"
       value="50" oninput="out.value=this.value">
<output id="out">50</output>

색상 & 파일 입력

type='color'는 네이티브 색상 픽커를 열고 hex 값(#rrggbb)을 반환합니다. type='file'은 사용자가 파일을 선택하게 합니다. accept 속성은 MIME 유형이나 확장자로 필터링합니다(image/*, .pdf, image/png). multiple 속성은 여러 파일 선택을 허용합니다. JavaScript에서 files 속성(FileList)을 통해 선택된 파일에 액세스하세요. URL.createObjectURL()을 사용하여 이미지의 로컬 미리보기 URL을 만드세요. 대용량 파일 업로드의 경우 청크 업로드나 진행률 추적을 위한 File API를 고려하세요. 항상 서버 측에서 파일 유형과 크기를 검증하세요.

html
<label for="color">Pick a color:</label>
<input type="color" id="color" value="#ff0000">

<label for="avatar">Upload avatar:</label>
<input type="file" id="avatar" accept="image/*">

<label for="docs">Upload documents:</label>
<input type="file" id="docs" accept=".pdf,.doc,.docx" multiple>

<!-- File input with image preview -->
<input type="file" accept="image/*" onchange="preview(this)">
<img id="preview-img" hidden>
<script>
function preview(input) {
  const file = input.files[0];
  if (file) {
    document.getElementById("preview-img").src =
      URL.createObjectURL(file);
    document.getElementById("preview-img").hidden = false;
  }
}
</script>

datalist (입력 제안)

<datalist>은 입력 필드에 자동완성 제안을 제공합니다. datalist의 id와 일치하는 list 속성을 통해 입력에 연결하세요. <select>와 달리 사용자는 여전히 어떤 값이든 입력할 수 있습니다 - 제안은 선택 사항입니다. text, number, date, color, range 입력과 작동합니다. 사용자가 입력할 때 브라우저가 일치하는 제안을 표시합니다. 이는 간단한 사용 사례에 대해 JavaScript 자동완성 라이브러리의 가벼운 대안입니다. 참고: datalist 내부의 옵션 스타일링은 지원되지 않습니다 - 브라우저 기본값으로 렌더링됩니다.

html
<label for="browser">Favorite browser:</label>
<input list="browsers" id="browser" name="browser">
<datalist id="browsers">
  <option value="Chrome">
  <option value="Firefox">
  <option value="Safari">
  <option value="Edge">
  <option value="Opera">
</datalist>

<!-- Works with various input types -->
<label for="color">Choose color:</label>
<input type="color" list="preset-colors" id="color">
<datalist id="preset-colors">
  <option value="#ff0000">
  <option value="#00ff00">
  <option value="#0000ff">
</datalist>

폼 검증 속성

HTML5는 속성을 통해 내장 클라이언트 측 검증을 제공합니다. required는 비어 있으면 제출을 방지합니다. type='email'/'url'은 형식을 자동으로 검증합니다. minlength/maxlength는 텍스트 길이를 제한합니다. min/max는 숫자와 날짜를 제한합니다. pattern은 사용자 정의 검증에 regex를 사용합니다. 브라우저는 기본 오류 버블을 표시합니다. setCustomValidity()로 메시지를 사용자 정의하세요 - 하지만 sticky 오류를 방지하기 위해 입력 시 항상 지우세요(setCustomValidity('')). 클라이언트 측 검증은 UX를 향상하지만, 우회될 수 있으므로 보안을 위해 서버 측 검증과 짝지어야 합니다.

html
<form>
  <label>Email: <input type="email" required></label>
  <label>Username:
    <input type="text" required minlength="3" maxlength="20"
           pattern="[a-zA-Z0-9_]+">
  </label>
  <label>Age:
    <input type="number" min="18" max="120" required>
  </label>
  <label>Website:
    <input type="url" placeholder="https://example.com">
  </label>
  <button type="submit">Submit</button>
</form>

<!-- Custom validation message -->
<input type="text" required
  oninvalid="this.setCustomValidity('Please enter your name')"
  oninput="this.setCustomValidity('')">
16

서비스 워커 & PWA

서비스 워커 등록

서비스 워커는 웹 페이지와 별도로 백그라운드에서 실행되는 JavaScript 파일로, 오프라인 지원, 푸시 알림, 백그라운드 동기화를 가능하게 합니다. 메인 페이지에서 등록하세요 - HTTPS를 통해 제공되어야 합니다. 서비스 워커 파일의 위치는 scope를 결정합니다(해당 디렉토리와 하위 디렉토리의 페이지를 제어). 등록은 비동기이고 scope당 한 번만 발생합니다. 첫 방문 후, SW는 후속 페이지 로드 시 활성화됩니다. 페이지가 대화형이 된 후 등록을 지연시키기 위해 load 이벤트를 사용하세요.

html
<!-- Register a service worker in main page -->
<script>
if ("serviceWorker" in navigator) {
  window.addEventListener("load", () => {
    navigator.serviceWorker
      .register("/sw.js")
      .then((reg) => {
        console.log("SW registered:", reg.scope);
      })
      .catch((err) => {
        console.log("SW registration failed:", err);
      });
  });
}
</script>

서비스 워커: 캐싱

서비스 워커 라이프사이클에는 3단계가 있습니다: install(핵심 자산 캐싱), activate(오래된 캐시 정리), fetch(네트워크 요청 가로채기). 여기 표시된 cache-first 전략은 캐시된 응답을 즉시 제공하고 네트워크로 폴백합니다. 다른 전략으로는 network-first(네트워크 시도, 캐시 폴백)와 stale-while-revalidate(캐시 제공, 백그라운드에서 업데이트)가 있습니다. 캐시 업데이트를 트리거하려면 CACHE_NAME을 올리세요. Cache API는 Request/Response 쌍을 저장합니다. 서비스 워커는 필요할 때만 실행되며 메모리 절약을 위해 브라우저에 의해 종료될 수 있습니다.

html
// sw.js - Cache assets for offline use
const CACHE_NAME = "my-app-v1";
const ASSETS = ["/", "/index.html", "/style.css", "/app.js"];

// Install: cache core assets
self.addEventListener("install", (e) => {
  e.waitUntil(
    caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS))
  );
});

// Fetch: serve from cache, fall back to network
self.addEventListener("fetch", (e) => {
  e.respondWith(
    caches.match(e.request).then((cached) => {
      return cached || fetch(e.request);
    })
  );
});

// Activate: clean old caches
self.addEventListener("activate", (e) => {
  e.waitUntil(
    caches.keys().then((keys) =>
      Promise.all(
        keys.filter((k) => k !== CACHE_NAME)
            .map((k) => caches.delete(k))
      )
    )
  );
});

웹 앱 매니페스트

웹 앱 매니페스트는 웹 앱을 설치 가능하게 만드는 JSON 파일입니다(홈 화면에 추가). name은 전체 이름이고, short_name은 홈 화면 아이콘에 나타납니다. display:'standalone'은 브라우저 UI를 숨겨 네이티브 앱처럼 보이게 합니다. theme_color는 브라우저 크롬 색상에 영향을 줍니다. icons는 최소 192px과 512px 크기를 포함해야 합니다. 'purpose':'maskable'은 Android가 다른 모양에 아이콘을 적응시키게 합니다. 유효한 매니페스트와 등록된 서비스 워커가 PWA의 최소 요구사항입니다. PWA 준수를 위해 Lighthouse로 테스트하세요.

html
<!-- Link the manifest in HTML -->
<link rel="manifest" href="manifest.json">

<!-- manifest.json -->
{
  "name": "My PWA App",
  "short_name": "MyApp",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#1976d2",
  "orientation": "portrait",
  "icons": [
    {
      "src": "/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icon-512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable"
    }
  ]
}

푸시 알림

푸시 알림은 탭이 닫혀 있을 때도 사용자를 재참여시킬 수 있게 합니다. 먼저 Notification.requestPermission()로 권한을 요청하세요. 그런 다음 PushManager를 통해 구독하세요 - 구독 객체는 endpoint URL과 키를 포함합니다. 이 구독을 서버로 보내고, 서버는 Web Push API(인증을 위한 VAPID 키 포함)를 통해 푸시 메시지를 보내는 데 사용합니다. 서비스 워커의 push 이벤트 핸들러가 알림을 표시합니다. userVisibleOnly:true는 모든 푸시가 알림을 표시해야 함을 의미합니다(조용한 푸시 없음). HTTPS와 서비스 워커가 필요합니다.

html
<!-- Request notification permission -->
<button onclick="subscribe()">Enable Notifications</button>

<script>
async function subscribe() {
  const permission = await Notification.requestPermission();
  if (permission !== "granted") return;

  const reg = await navigator.serviceWorker.ready;
  const subscription = await reg.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: urlBase64ToUint8Array(VAPID_KEY)
  });
  // Send subscription to your server
  await fetch("/api/subscribe", {
    method: "POST",
    body: JSON.stringify(subscription)
  });
}
</script>

<!-- In sw.js: handle push events -->
self.addEventListener("push", (e) => {
  const data = e.data.json();
  e.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body,
      icon: "/icon-192.png",
      badge: "/badge.png"
    })
  );
});

백그라운드 동기화

백그라운드 동기화는 사용자가 안정적인 연결을 가질 때까지 작업을 지연시킬 수 있게 합니다. sync 이벤트를 등록하면, 연결이 복원될 때 브라우저가 이를 발생시킵니다 - 사용자가 탭을 닫았더라도. sync 이벤트 핸들러(서비스 워커 내)가 지연된 작업을 수행합니다. 작업이 오류를 throw하면, 브라우저가 자동으로 지수적 백오프로 재시도합니다. e.tag는 동기화 유형을 식별합니다 - 태그당 하나의 동기화만 대기열에 추가됩니다. 보류 중인 데이터를 저장하기 위해 IndexedDB를 사용하세요. 이는 메시징 앱, 폼 제출, 데이터 동기화에 이상적입니다. 정기적 동기화(정기 업데이트용)는 Chrome에서 사용 가능하지만 모든 브라우저에서는 아닙니다.

html
// Register a sync event from the page
navigator.serviceWorker.ready.then((reg) => {
  return reg.sync.register("send-messages");
});

// Handle sync in sw.js
self.addEventListener("sync", (e) => {
  if (e.tag === "send-messages") {
    e.waitUntil(sendPendingMessages());
  }
});

async function sendPendingMessages() {
  const messages = await getMessagesFromIndexedDB();
  for (const msg of messages) {
    try {
      await fetch("/api/messages", {
        method: "POST",
        body: JSON.stringify(msg)
      });
      await deleteMessageFromIndexedDB(msg.id);
    } catch (err) {
      throw err; // triggers retry
    }
  }
}
17

메타 태그 심층 (Open Graph & Twitter)

Open Graph 태그 (Facebook)

Open Graph(OG) 태그는 Facebook, LinkedIn, 대부분의 소셜 플랫폼에서 공유될 때 페이지가 어떻게 나타나는지 제어합니다. og:title은 헤드라인이고, og:description은 요약이며, og:image는 미리보기 이미지입니다(1200x630px 권장). og:type(website, article, video.movie)은 렌더링에 영향을 줍니다. 기사의 경우 더 풍부한 미리보기를 위해 article:published_time, article:author, article:section을 추가하세요. 더 빠른 렌더링을 위해 항상 og:image 크기를 지정하세요. Facebook의 Sharing Debugger로 테스트하세요. OG 태그는 소셜 미디어 마케팅과 클릭률에 필수적입니다.

html
<head>
  <meta property="og:title" content="My Awesome Article">
  <meta property="og:description" content="A deep dive into modern web development.">
  <meta property="og:type" content="article">
  <meta property="og:url" content="https://example.com/article">
  <meta property="og:image" content="https://example.com/img/og.jpg">
  <meta property="og:image:width" content="1200">
  <meta property="og:image:height" content="630">
  <meta property="og:site_name" content="My Website">
  <meta property="og:locale" content="en_US">
  <meta property="article:published_time" content="2025-01-15T08:00:00Z">
  <meta property="article:author" content="Jane Doe">
</head>

Twitter Card 태그

Twitter Card 태그는 X(Twitter)에서 링크가 어떻게 나타나는지 제어합니다. twitter:card 유형은 'summary'(작은 이미지), 'summary_large_image'(텍스트 위 큰 이미지), 'player'(비디오/오디오), 또는 'app'이 될 수 있습니다. twitter:site는 사이트의 @handle이고, twitter:creator는 작성자의 @handle입니다. 이미지는 최소 300x157px(summary) 또는 600x314px(큰 이미지)이어야 합니다. twitter:image:alt는 접근성을 향상합니다. Twitter의 Card Validator로 테스트하세요. OG 태그가 있더라도 Twitter 전용 태그를 추가하면 Twitter 미리보기를 더 많이 제어할 수 있습니다.

html
<head>
  <meta name="twitter:card" content="summary_large_image">
  <meta name="twitter:site" content="@mywebsite">
  <meta name="twitter:creator" content="@janedoe">
  <meta name="twitter:title" content="My Awesome Article">
  <meta name="twitter:description" content="A deep dive into modern web dev.">
  <meta name="twitter:image" content="https://example.com/img/twitter.jpg">
  <meta name="twitter:image:alt" content="Article cover image">
</head>

Viewport & 모바일 메타

viewport 메타 태그는 반응형 디자인에 중요합니다 - 이것이 없으면 모바일 브라우저가 데스크톱 너비로 페이지를 렌더링하고 축소합니다. width=device-width는 기기 너비에 일치하고, initial-scale=1은 줌 수준을 설정합니다. WCAG 접근성 가이드라인을 위반하므로 사용자 줌 비활성화(user-scalable=no)를 피하세요. apple-mobile-web-app-capable은 iOS 홈 화면에 추가 시 앱을 전체화면으로 만듭니다. theme-color는 모바일 Chrome과 Safari에서 브라우저 주소 표시줄 색상에 영향을 줍니다. 이 모바일 전용 태그는 PWA와 모바일 경험을 크게 향상합니다.

html
<head>
  <!-- Responsive viewport -->
  <meta name="viewport"
        content="width=device-width, initial-scale=1.0">

  <!-- Disable zoom (accessibility concern — avoid) -->
  <meta name="viewport"
        content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">

  <!-- iOS Safari: full-screen web app -->
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style"
        content="black-translucent">
  <meta name="apple-mobile-web-app-title" content="MyApp">

  <!-- Android Chrome theme color -->
  <meta name="theme-color" content="#1976d2">
</head>

HTTP-Equiv & Charset

http-equiv 메타 태그는 HTML에서 직접 HTTP 헤더를 시뮬레이션합니다. charset은 인코딩 기반 XSS 공격을 방지하기 위해 <head>의 첫 번째 요소이고 첫 1024바이트 내에 있어야 합니다. http-equiv='refresh'는 지연 후 리디렉션합니다 - SEO를 위해 서버 측 리디렉션을 선호하세요. 메타를 통한 Content-Security-Policy는 제한적입니다(report-uri 없음, frame-ancestors 없음) - HTTP 헤더를 선호하세요. 메타를 통한 Cache-Control은 신뢰할 수 없습니다 - HTTP 헤더를 대신 사용하세요. X-UA-Compatible은 IE에게 최신 렌더링 엔진을 사용하도록 강제합니다. 가능할 때 항상 http-equiv보다 실제 HTTP 헤더를 선호하세요.

html
<head>
  <!-- Character encoding (must be first in head) -->
  <meta charset="UTF-8">

  <!-- Refresh/redirect after 5 seconds -->
  <meta http-equiv="refresh" content="5; url=https://example.com">

  <!-- Set content security policy -->
  <meta http-equiv="Content-Security-Policy"
        content="default-src 'self'; script-src 'self'">

  <!-- X-UA-Compatible for IE -->
  <meta http-equiv="X-UA-Compatible" content="IE=edge">

  <!-- Cache control -->
  <meta http-equiv="Cache-Control"
        content="no-cache, no-store, must-revalidate">
</head>

구조화된 데이터 (JSON-LD)

JSON-LD(JavaScript Object Notation for Linked Data)는 구조화된 데이터를 위한 Google의 권장 형식입니다. 검색 엔진이 콘텐츠를 이해하는 데 도움을 주어 검색 결과의 리치 스니펫(별점, 이벤트 날짜, breadcrumbs, FAQ 아코디언)을 가능하게 합니다. 일반적인 유형에는 Article, Product, Recipe, Event, LocalBusiness, FAQPage가 있습니다. type='application/ld+json'으로 <head>에 스크립트를 배치하세요. Google의 Rich Results Test로 검증하세요. 구조화된 데이터는 리치 스니펫을 보장하지 않지만 자격을 부여합니다. 검색 결과의 클릭률을 높이는 강력한 SEO 도구입니다.

html
<head>
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "Article",
    "headline": "My Awesome Article",
    "author": {
      "@type": "Person",
      "name": "Jane Doe"
    },
    "datePublished": "2025-01-15",
    "image": "https://example.com/img/article.jpg",
    "publisher": {
      "@type": "Organization",
      "name": "My Website",
      "logo": {
        "@type": "ImageObject",
        "url": "https://example.com/logo.png"
      }
    }
  }
  </script>
</head>
18

시맨틱 HTML 심층

Article vs Section vs Div

올바른 컨테이너 요소 선택은 접근성과 SEO에 중요합니다. <article>은 독립적으로 배포될 수 있는 콘텐츠(블로그 게시물, 뉴스, 댓글, 제품 카드)에 사용됩니다. <section>은 주제적으로 관련된 콘텐츠를 그룹화합니다 - 항상 제목이 있어야 합니다. <div>는 시맨틱 요소가 맞지 않을 때 레이아웃/스타일링을 위한 최후의 수단입니다. 중첩된 <article> 요소(게시물 내의 댓글 같은)는 유효합니다. 경험칙: 콘텐츠가 RSS를 통해 신디케이트될 수 있으면 <article>을; 주제적 챕터이면 <section>을; 순수하게 레이아웃용이면 <div>를 사용하세요.

html
<!-- <article>: self-contained, syndicable content -->
<article>
  <h2>Blog Post Title</h2>
  <p>Content that makes sense on its own...</p>
  <article><h3>Comment 1</h3><p>...</p></article>
</article>

<!-- <section>: thematic grouping with a heading -->
<section>
  <h2>Chapter 1: Introduction</h2>
  <p>Related content...</p>
</section>

<!-- <div>: generic container, no semantic meaning -->
<div class="layout-wrapper">
  <div class="grid-item">Styling hook only</div>
</div>

Figure & Figcaption

<figure>는 메인 텍스트에서 참조되는 자체 완비된 콘텐츠(이미지, 다이어그램, 코드 목록, 또는 인용)를 나타냅니다. <figcaption>은 캡션/제목을 제공합니다. 일반 <img>와 달리 figure/figcaption은 시각적 요소를 설명에 시맨틱하게 연결합니다. 스크린 리더는 캡션과 함께 그림을 알립니다. 그림은 참조하는 텍스트를 기준으로 어디든 배치할 수 있습니다. 캡션이 있고 번호로 참조되는 콘텐츠(Figure 1, Listing 1)에 <figure>를 사용하세요. 순수하게 장식용 이미지의 경우 빈 alt와 함께 일반 <img>를 사용하세요.

html
<figure>
  <img src="chart.png" alt="Sales growth chart showing 40% increase">
  <figcaption>
    Figure 1: Quarterly sales growth from Q1 to Q4 2025.
    Data source: Internal sales database.
  </figcaption>
</figure>

<!-- Figure can contain code blocks too -->
<figure>
  <pre><code>const x = 42;</code></pre>
  <figcaption>Listing 1: Variable declaration example</figcaption>
</figure>

Details & Summary (공개)

<details>와 <summary>는 JavaScript 없이 네이티브 접을 수 있는(아코디언) 섹션을 만듭니다. <summary>를 클릭하면 콘텐츠가 토글됩니다. open 속성은 기본적으로 펼쳐진 상태로 만듭니다. toggle 이벤트는 펼치기/접기 시 발생합니다. 이는 FAQ, 설정 패널, 점진적 공개에 훌륭합니다. 스크린 리더는 공개 위젯으로 알립니다. CSS로 기본 삼각형 마커를 스타일링할 수 있습니다(summary::-webkit-details-marker 또는 list-style). 복잡한 상호작용의 경우 JavaScript가 여전히 필요할 수 있지만, 간단한 토글의 경우 이 네이티브 솔루션이 이상적입니다.

html
<!-- Native collapsible without JavaScript -->
<details>
  <summary>Click to expand FAQ</summary>
  <p>Here is the hidden answer that appears when expanded.</p>
</details>

<!-- Open by default -->
<details open>
  <summary>Already expanded</summary>
  <p>This content is visible on page load.</p>
</details>

<!-- Nested disclosures -->
<details>
  <summary>Level 1</summary>
  <details>
    <summary>Level 2</summary>
    <p>Deeply nested content</p>
  </details>
</details>

Time & Mark 요소

<time>은 ISO 8601 형식으로 기계가 읽을 수 있는 datetime 속성과 함께 사람이 읽을 수 있는 날짜/시간을 감쌉니다. 이는 검색 엔진, 달력, 보조 기술이 날짜를 올바르게 파싱하는 데 도움을 줍니다. datetime은 날짜(YYYY-MM-DD), 시간(HH:MM), 시간대가 있는 datetime, 지속 시간(PT2H30M)을 지원합니다. <mark>는 검색어 일치 같이 관련성을 위해 강조된 텍스트를 나타냅니다. 중요성/강조를 나타내는 <strong>이나 <em>과 달리 <mark>는 문맥적 관련성을 나타냅니다. 두 요소 모두 시맨틱 의미와 SEO를 향상합니다.

html
<!-- <time>: machine-readable dates/times -->
<p>Published on
  <time datetime="2025-01-15">January 15, 2025</time>
</p>
<p>Event at
  <time datetime="2025-03-20T14:30-05:00">2:30 PM EST</time>
</p>
<p>Duration:
  <time datetime="PT2H30M">2 hours 30 minutes</time>
</p>

<!-- <mark>: highlighted/relevant text -->
<p>Search results: the keyword
  <mark>HTML5</mark> appears in 3 documents.
</p>

Dialog 요소

<dialog> 요소는 라이브러리 없이 네이티브 모달과 비모달 대화 상자를 제공합니다. showModal()은 모달로 엽니다(배경, 페이지 상호작용 차단); show()는 비모달로 엽니다. close()로 닫습니다. form method='dialog'는 제출 시 대화 상자를 닫고, 버튼의 value가 returnValue가 됩니다. ::backdrop 의사 요소가 모달 오버레이를 스타일링합니다. ESC 키는 모달 대화 상자를 자동으로 닫습니다. 최상위 레이어 렌더링은 z-index와 관계없이 대화 상자가 모든 다른 콘텐츠 위에 나타나게 합니다. close 이벤트는 닫은 후 발생합니다. 이제 잘 지원되며 많은 JavaScript 모달 라이브러리를 대체합니다.

html
<!-- Native modal dialog -->
<dialog id="myDialog">
  <h2>Confirm Action</h2>
  <p>Are you sure you want to proceed?</p>
  <form method="dialog">
    <button value="cancel">Cancel</button>
    <button value="confirm">Confirm</button>
  </form>
</dialog>

<button onclick="document.getElementById('myDialog').showModal()">
  Open Modal
</button>

<script>
const dialog = document.getElementById("myDialog");
dialog.addEventListener("close", () => {
  console.log("Dialog closed with:", dialog.returnValue);
});
// Close on backdrop click
dialog.addEventListener("click", (e) => {
  if (e.target === dialog) dialog.close();
});
</script>
19

고급 폼 & 입력

Fieldset & Legend

<fieldset>은 관련 폼 컨트롤을 그룹화하고, <legend>은 그룹의 캡션을 제공합니다. 이는 접근성에 중요합니다 - 스크린 리더가 그룹의 각 컨트롤 전에 legend를 알립니다. fieldset의 disabled 속성은 내부의 모든 컨트롤을 비활성화합니다. fieldset은 또한 기본 테두리로 시각적 조직을 향상합니다. 라디오 버튼의 경우 fieldset/legend가 그룹을 레이블링하는 권장 방법입니다. fieldset을 너무 깊게 중첩하지 마세요. 스크린 리더 사용자에게 혼란을 줄 수 있습니다.

html
<form>
  <fieldset>
    <legend>Shipping Address</legend>
    <label>Street: <input type="text" name="street" required></label>
    <label>City: <input type="text" name="city" required></label>
    <label>ZIP: <input type="text" name="zip" pattern="[0-9]{5}"></label>
  </fieldset>

  <fieldset disabled>
    <legend>Billing (same as shipping)</legend>
    <label>Card: <input type="text" name="card"></label>
  </fieldset>

  <fieldset>
    <legend>Subscription Plan</legend>
    <label><input type="radio" name="plan" value="free"> Free</label>
    <label><input type="radio" name="plan" value="pro" checked> Pro</label>
  </fieldset>
</form>

Output & Progress 요소

<output>은 계산 결과를 표시합니다 - for 속성을 통해 폼 입력과 실시간 관계를 가집니다. 계산된 값에 대해 span보다 시맨틱하게 더 의미 있습니다. <progress>는 작업 완료를 나타냅니다(value/max); 값이 없으면 불확정 스피너를 표시합니다. <meter>는 알려진 범위 내의 스칼라 값을 나타냅니다(디스크 공간이나 테스트 점수 같은) - low, high, optimum 속성은 색상에 영향을 주는 임계값을 정의합니다(녹색/노란색/빨간색). progress와 meter는 브라우저마다 다른 내장 스타일을 가지지만 CSS로 사용자 정의할 수 있습니다.

html
<form oninput="result.value = parseInt(a.value) + parseInt(b.value)">
  <input type="number" id="a" value="10"> +
  <input type="number" id="b" value="20"> =
  <output name="result" for="a b">30</output>
</form>

<!-- Progress bar -->
<label>Downloading: <progress id="prog" value="70" max="100">70%</progress></label>

<!-- Meter (gauge within a range) -->
<label>Disk usage: <meter value="0.6" min="0" max="1" low="0.5" high="0.8" optimum="0.2">60%</meter></label>
<label>Score: <meter value="85" min="0" max="100" low="40" high="70" optimum="90">85</meter></label>

폼 자동완성

autocomplete 속성은 저장된 사용자 데이터를 사용하여 폼을 채우는 데 도움을 줍니다. 표준화된 토큰을 사용하세요: 'name', 'email', 'tel', 'street-address', 'address-level2'(도시), 'postal-code', 'country'. 신용카드의 경우: 'cc-name', 'cc-number', 'cc-exp'. autocomplete='off'는 자동 채우기를 비활성화합니다(비민감 필드에 대해서는 브라우저가 무시할 수 있음). autocomplete='one-time-code'는 모바일에서 SMS 코드 자동 채우기를 트리거합니다. 적절한 autocomplete 토큰은 폼 완료율과 사용자 경험을 극적으로 향상합니다. 또한 비밀번호 관리자가 필드를 올바르게 식별하는 데 도움을 줍니다.

html
<form autocomplete="on">
  <!-- Browser can autofill name -->
  <label>Name: <input type="text" name="name" autocomplete="name"></label>

  <!-- Email autofill -->
  <label>Email: <input type="email" name="email" autocomplete="email"></label>

  <!-- Address autofill tokens -->
  <fieldset>
    <legend>Address</legend>
    <input autocomplete="street-address">
    <input autocomplete="address-level2"> <!-- City -->
    <input autocomplete="postal-code">
    <input autocomplete="country">
  </fieldset>

  <!-- Disable autocomplete for sensitive field -->
  <label>SSN: <input type="text" autocomplete="off"></label>

  <!-- One-time code (SMS) -->
  <label>Code: <input type="text" autocomplete="one-time-code"></label>
</form>

폼 제출 메서드

GET은 폼 데이터를 URL에 추가합니다(보임, 북마크 가능, 길이 제한) - 검색과 필터에 사용하세요. POST는 데이터를 요청 본문에 보냅니다(보이지 않음, 길이 제한 없음) - 데이터 생성/업데이트에 사용하세요. enctype='multipart/form-data'는 파일 업로드에 필요합니다. 버튼의 name/value 쌍이 제출에 포함됩니다 - 다른 값으로 같은 name을 가진 여러 제출 버튼이 다른 작업을 트리거할 수 있습니다. AJAX 제출의 경우 FormData 객체와 fetch()를 사용하세요. GET 데이터는 브라우저 기록과 서버 로그에 나타나므로 민감한 데이터에는 항상 POST를 사용하세요.

html
<!-- GET: data in URL query string -->
<form method="GET" action="/search">
  <input name="q" value="html5">
  <!-- URL: /search?q=html5 -->
</form>

<!-- POST: data in request body -->
<form method="POST" action="/submit" enctype="application/x-www-form-urlencoded">
  <input name="name" value="Alice">
</form>

<!-- File upload: multipart -->
<form method="POST" action="/upload" enctype="multipart/form-data">
  <input type="file" name="document">
</form>

<!-- Form with custom submit button -->
<form method="POST" action="/save">
  <button type="submit" name="action" value="save">Save</button>
  <button type="submit" name="action" value="publish">Publish</button>
</form>

Contenteditable & Spellcheck

contenteditable='true'는 모든 요소를 브라우저에서 직접 편집 가능하게 만듭니다 - 리치 텍스트 에디터의 기반입니다. spellcheck='true'는 브라우저 맞춤법 검사를 활성화합니다(오철자에 빨간 밑줄). 코드 스니펫의 경우 가양성을 방지하기 위해 spellcheck='false'를 설정하세요. contenteditable 속성은 상속될 수 있습니다 - 'false'로 설정하지 않는 한 자식 요소도 편집 가능합니다. 편집 가능한 콘텐츠 저장에는 JavaScript가 필요합니다(예: 입력 시 localStorage에 저장). contenteditable로 전체 리치 텍스트 에디터 구축은 복잡합니다(붙여넣기, 서식, 커서 위치 처리) - 프로덕션 사용을 위해 Quill, TipTap, 또는 ProseMirror 같은 라이브러리를 고려하세요.

html
<!-- Editable div -->
<div contenteditable="true">
  Click to edit this text directly in the browser.
</div>

<!-- Editable with spellcheck -->
<p contenteditable="true" spellcheck="true">
  Typoos will be underlined in red.
</p>

<!-- Turn off spellcheck for code -->
<pre contenteditable="true" spellcheck="false">
  const varible = "code"; // no spellcheck
</pre>

<!-- Entire document editable -->
<body contenteditable="true">

<!-- Save editable content -->
<div id="note" contenteditable="true"
     oninput="localStorage.setItem('note', this.innerHTML)">
  <script>document.getElementById('note').innerHTML =
    localStorage.getItem('note') || '';</script>
</div>
20

SEO 메타 태그

Open Graph 태그

Open Graph 태그(Facebook이 생성)는 소셜 미디어에서 공유될 때 페이지가 어떻게 나타나는지 제어합니다. og:image는 1200x630px이어야 합니다. 이 태그가 없으면 플랫폼이 페이지 콘텐츠에서 미리보기를 만들고, 종종 잘못됩니다.

html
<meta property="og:title" content="My Awesome Article">
<meta property="og:description" content="A deep dive into modern web design.">
<meta property="og:image" content="https://example.com/cover.jpg">
<meta property="og:url" content="https://example.com/article">
<meta property="og:type" content="article">
<meta property="og:site_name" content="Example Site">

Twitter Cards

Twitter Card 태그는 X/Twitter에서 링크가 어떻게 렌더링되는지 제어합니다. summary_large_image는 제목 위에 큰 이미지를 표시하고, summary는 더 작은 정사각형 썸네일을 표시합니다. twitter:site 태그는 카드를 계정에 연결합니다.

html
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@yoursite">
<meta name="twitter:title" content="My Article">
<meta name="twitter:description" content="A deep dive into web design.">
<meta name="twitter:image" content="https://example.com/cover.jpg">

Canonical & Robots

rel="canonical"은 마스터 URL을 가리켜 중복 콘텐츠 페널티를 방지합니다. robots 메타 태그는 크롤러를 지시합니다: noindex는 결과에서 페이지를 제거하고, nofollow는 링크 따라가기를 차단합니다.

html
<!-- Canonical: tells search engines the preferred URL -->
<link rel="canonical" href="https://example.com/article">

<!-- Robots: control indexing -->
<meta name="robots" content="index, follow">
<meta name="robots" content="noindex, nofollow">
<meta name="robots" content="noindex, follow">

구조화된 데이터 (JSON-LD)

JSON-LD 구조화된 데이터는 검색 엔진이 콘텐츠를 이해하는 데 도움을 주어 리치 결과(별점, breadcrumbs, 이벤트 정보)를 가능하게 합니다. Schema.org가 어휘를 정의합니다. head나 body 내부에 script 태그를 배치하세요.

html
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "Modern Web Design",
  "author": { "@type": "Person", "name": "Jane Doe" },
  "datePublished": "2025-01-15",
  "image": "https://example.com/cover.jpg"
}
</script>

Meta Description & Viewport

description 메타 태그는 클릭률에 영향을 미칩니다 - 160자 이내로 유지하세요. viewport 태그는 모바일 반응형 디자인에 필수입니다. theme-color는 브라우저 UI 바를 스타일링합니다. charset은 head의 첫 번째 요소여야 합니다.

html
<meta name="description" content="A concise 150-160 character summary that appears in search results.">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#4285f4">
<meta charset="UTF-8">
21

SVG 기본

기본 모양

SVG는 XML 요소로 그립니다. 모양에는 rect, circle, ellipse, line, polyline, polygon이 포함됩니다. 래스터 이미지와 달리 SVG는 품질 손실 없이 확장되며 CSS로 스타일링할 수 있습니다. fill은 내부 색상을, stroke는 윤곽선을 설정합니다.

html
<svg width="200" height="150" xmlns="http://www.w3.org/2000/svg">
  <rect x="10" y="10" width="80" height="50" fill="steelblue"/>
  <circle cx="140" cy="40" r="30" fill="tomato"/>
  <line x1="10" y1="100" x2="180" y2="100" stroke="black" stroke-width="2"/>
  <ellipse cx="100" cy="120" rx="60" ry="20" fill="none" stroke="green"/>
</svg>

Paths

path 요소는 가장 강력한 SVG 기본 요소입니다. 명령: M(이동), L(선), H/V(수평/수직), C(cubic bezier), Q(quadratic), A(arc), Z(닫기). 대문자 = 절대 좌표, 소문자 = 상대 좌표.

html
<svg width="200" height="200">
  <!-- M=move, L=line, C=cubic bezier, Z=close -->
  <path d="M 10 10 L 100 10 L 100 100 Z" fill="none" stroke="black"/>
  <path d="M 10 150 C 50 50, 150 50, 190 150" stroke="red" fill="none"/>
  <path d="M 10 180 Q 100 120 190 180" stroke="blue" fill="none"/>
</svg>

그룹 & 재사용

g로 요소를 그룹화하여 공유 속성을 적용하거나 함께 변환하세요. defs 내부에 재사용 가능한 모양을 정의하고 use href="#id"로 참조하세요. 이는 SVG를 DRY하게 유지하고 더 작게 만듭니다.

html
<svg width="200" height="200">
  <defs>
    <g id="star">
      <polygon points="50,5 61,39 98,39 68,61 79,95 50,75 21,95 32,61 2,39 39,39"/>
    </g>
  </defs>
  <use href="#star" x="0" y="0" fill="gold"/>
  <use href="#star" x="100" y="100" fill="orange"/>
</svg>

텍스트 & 스타일링

SVG 텍스트는 실제 텍스트입니다 - 선택 가능하고, 검색 가능하며, 어떤 크기에서도 선명합니다. font-family, font-size, font-weight가 CSS를 반영합니다. text-anchor는 수평 정렬(start/middle/end)을 제어합니다.

html
<svg width="300" height="100">
  <text x="10" y="50" font-family="Arial" font-size="24" fill="navy">
    Hello SVG
  </text>
  <text x="150" y="80" font-size="16" font-weight="bold" text-anchor="middle">
    Centered Bold Text
  </text>
</svg>

그라데이션 & 패턴

그라데이션은 defs에 정의되고 fill="url(#id)"로 참조됩니다. linearGradient는 선을 따라 전환하고, radialGradient는 중심점에서 방사됩니다. 각 stop이 백분율 오프셋에서 색상을 정의합니다.

html
<svg width="200" height="100">
  <defs>
    <linearGradient id="lg" x1="0%" y1="0%" x2="100%" y2="0%">
      <stop offset="0%" stop-color="red"/>
      <stop offset="100%" stop-color="blue"/>
    </linearGradient>
    <radialGradient id="rg">
      <stop offset="0%" stop-color="yellow"/>
      <stop offset="100%" stop-color="green"/>
    </radialGradient>
  </defs>
  <rect width="100" height="100" fill="url(#lg)"/>
  <rect x="100" width="100" height="100" fill="url(#rg)"/>
</svg>
22

Canvas

모양 그리기

Canvas API는 2D 렌더링 컨텍스트를 통해 canvas 요소에 픽셀을 그립니다. fillRect/strokeRect는 사각형을 그리고, paths(beginPath, arc, lineTo)는 fill()이나 stroke() 전에 복잡한 모양을 만듭니다. Canvas는 래스터입니다 - 크기 조정 시 그림이 흐려집니다.

html
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');

ctx.fillStyle = 'tomato';
ctx.fillRect(10, 10, 100, 50);

ctx.strokeStyle = 'navy';
ctx.lineWidth = 3;
ctx.strokeRect(130, 10, 80, 80);

ctx.beginPath();
ctx.arc(200, 150, 40, 0, Math.PI * 2);
ctx.fill();

Paths & 선

Paths는 점별로 모양을 만듭니다. moveTo는 새 하위 경로를 시작하고, lineTo는 직선 세그먼트를 추가하고, closePath는 시작점에 연결합니다. bezierCurveTo와 quadraticCurveTo는 제어점으로 곡선을 그립니다.

html
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(150, 50);
ctx.lineTo(100, 150);
ctx.closePath();
ctx.fillStyle = 'gold';
ctx.fill();
ctx.stroke();

// Bezier curves
ctx.beginPath();
ctx.moveTo(0, 200);
ctx.bezierCurveTo(100, 100, 200, 300, 300, 200);
ctx.stroke();

이미지 그리기

drawImage는 이미지, 비디오, 또는 다른 canvas를 그립니다. 9인수 형식은 소스 사각형을 잘라내어 대상 사각형에 그립니다 - 스프라이트 시트에 유용합니다. 그리기 전에 onload를 기다리세요.

html
const img = new Image();
img.src = 'photo.jpg';
img.onload = () => {
  ctx.drawImage(img, 0, 0);                    // full image
  ctx.drawImage(img, 0, 0, 200, 150);          // scaled
  ctx.drawImage(img, 50, 50, 100, 100, 300, 0, 100, 100); // cropped
};

애니메이션 루프

requestAnimationFrame은 브라우저 다시 그릴 때마다 draw()를 예약합니다(~60fps), 탭이 숨겨지면 일시정지합니다. clearRect는 잔상을 방지하기 위해 매 프레임 canvas를 지웁니다. 물리의 경우 일관된 속도를 위해 델타 시간을 곱하세요.

html
let x = 0;
function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = 'steelblue';
  ctx.fillRect(x, 50, 40, 40);
  x += 2;
  if (x > canvas.width) x = -40;
  requestAnimationFrame(draw);
}
requestAnimationFrame(draw);

픽셀 조작

getImageData는 원시 RGBA 픽셀 값(채널당 0-255)의 Uint8ClampedArray를 반환합니다. 직접 픽셀 액세스는 필터와 효과를 가능하게 합니다. putImageData는 수정된 버퍼를 다시 씁니다. 큰 canvas에는 느립니다.

html
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data; // RGBA bytes

for (let i = 0; i < data.length; i += 4) {
  const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
  data[i] = data[i + 1] = data[i + 2] = avg; // grayscale
}

ctx.putImageData(imageData, 0, 0);
23

Iframe & 포함

Iframe 기본

iframe은 현재 페이지 내에 다른 문서를 포함합니다. title 속성은 접근성에 필요합니다. loading="lazy"는 viewport 근처까지 로딩을 지연시킵니다. 레이아웃 이동을 방지하기 위해 항상 width와 height를 설정하세요.

html
<iframe
  src="https://example.com/widget"
  width="600"
  height="400"
  title="Example Widget"
  loading="lazy"
  referrerpolicy="no-referrer">
</iframe>

Sandbox 속성

sandbox 속성은 iframe 기능을 제한합니다. 빈 값은 모든 것을 차단합니다. 토큰을 추가하여 기능을 다시 활성화하세요: allow-scripts, allow-forms, allow-same-origin, allow-popups. 신뢰할 수 없는 콘텐츠에 대해 allow-scripts와 allow-same-origin을 결합하지 마세요.

html
<!-- Locks down the iframe completely -->
<iframe src="untrusted.html" sandbox></iframe>

<!-- Selectively re-enable features -->
<iframe
  src="widget.html"
  sandbox="allow-scripts allow-same-origin allow-forms">
</iframe>

Embed & Object

embed와 object는 레거시 요소입니다. object는 리소스 실패 시 표시되는 폴백 콘텐츠를 지원합니다. 최신 HTML5 요소(video, audio, picture)가 선호됩니다. HTML 콘텐츠에는 iframe을, 비디오에는 video를 사용하세요.

html
<!-- Embed for plugins/media -->
<embed src="video.mp4" type="video/mp4" width="400" height="300">

<!-- Object with fallback content -->
<object data="report.pdf" type="application/pdf" width="100%" height="600">
  <p>Your browser cannot display PDFs. <a href="report.pdf">Download</a></p>
</object>

<!-- Video with multiple sources -->
<video controls>
  <source src="movie.webm" type="video/webm">
  <source src="movie.mp4" type="video/mp4">
  Your browser does not support video.
</video>

postMessage 통신

postMessage는 iframe 경계(다른 origin)를 넘어 통신하는 유일한 방법입니다. 항상 세 번째 인수에 대상 origin을 지정하세요. 수신 측에서는 메시지를 신뢰하기 전에 e.origin을 검증하세요.

html
<!-- Parent page -->
<iframe id="f" src="child.html"></iframe>
<script>
  const frame = document.getElementById('f');
  frame.contentWindow.postMessage({ type: 'greet', text: 'Hi' }, 'https://example.com');

  window.addEventListener('message', (e) => {
    if (e.origin !== 'https://example.com') return;
    console.log('From child:', e.data);
  });
</script>

반응형 Iframe

iframe을 반응형으로 만들려면(예: 16:9 비디오), 화면비 백분율(16:9의 경우 56.25%)과 같은 padding-bottom으로 컨테이너를 감싸세요. 컨테이너를 채우기 위해 iframe을 절대 위치시키세요.

html
<div style="position:relative; padding-bottom:56.25%; height:0; overflow:hidden;">
  <iframe
    src="https://youtube.com/embed/abc"
    style="position:absolute; top:0; left:0; width:100%; height:100%; border:0;"
    title="Video"
    allowfullscreen>
  </iframe>
</div>
24

성능 최적화

리소스 힌트

리소스 힌트는 브라우저에게 필요하기 전에 연결을 준비하거나 자산을 가져오도록 알립니다. preconnect는 DNS/TCP/TLS를 웜업합니다. preload는 현재 페이지의 중요 자산을 일찍 가져옵니다. prefetch는 유휴 시간에 다음 페이지 리소스를 가져옵니다.

html
<!-- Preconnect to a third-party origin -->
<link rel="preconnect" href="https://fonts.googleapis.com">

<!-- DNS prefetch for lower-priority origins -->
<link rel="dns-prefetch" href="https://analytics.example.com">

<!-- Preload critical assets -->
<link rel="preload" href="hero.webp" as="image">
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>

<!-- Prefetch next page -->
<link rel="prefetch" href="/next-page.html">

이미지 지연 로딩

loading="lazy"는 사용자가 가까이 스크롤할 때까지 화면 밖 이미지 로딩을 지연시킵니다. 레이아웃 이동을 방지하기 위해 항상 width와 height를 설정하세요. picture 요소는 JPG 폴백과 함께 최신 형식(WebP/AVIF)을 제공합니다.

html
<!-- Native lazy loading -->
<img src="photo.jpg" loading="lazy" width="800" height="600" alt="...">

<!-- Picture with responsive sources -->
<picture>
  <source srcset="small.webp" media="(max-width: 600px)" type="image/webp">
  <source srcset="large.webp" type="image/webp">
  <img src="large.jpg" loading="lazy" width="1200" height="800" alt="...">
</picture>

스크립트 로딩 전략

속성이 없으면 스크립트는 HTML 파싱을 차단합니다. async는 병렬로 다운로드하지만 준비되면 즉시 실행합니다 - 순서 보장 안 됨. defer는 병렬로 다운로드하고 DOM 파싱 후 순서대로 실행합니다 - 메인 스크립트에 최적.

html
<!-- Normal: blocks HTML parsing -->
<script src="app.js"></script>

<!-- Async: downloads in parallel, runs ASAP -->
<script src="analytics.js" async></script>

<!-- Defer: downloads in parallel, runs after HTML parse -->
<script src="app.js" defer></script>

<!-- Module scripts defer by default -->
<script type="module" src="app.mjs"></script>

크리티컬 CSS

크리티컬 CSS(above-the-fold 콘텐츠에 필요한 스타일)를 인라인화하면 렌더 차단 CSS 요청을 제거하여 첫 페인트를 가속합니다. 나머지 CSS는 preload 트릭을 통해 비동기로 로드됩니다. Critical 같은 도구가 크리티컬 CSS를 자동 추출합니다.

html
<!-- Inline above-the-fold CSS in head -->
<head>
  <style>
    body { margin: 0; font-family: sans-serif; }
    .hero { height: 100vh; background: #f0f0f0; }
  </style>
  <!-- Load the rest asynchronously -->
  <link rel="preload" href="full.css" as="style" onload="this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="full.css"></noscript>
</head>

성능 측정

Performance API는 실제 사용자 타이밍을 측정합니다. 주요 지표: FCP(First Contentful Paint), LCP(Largest Contentful Paint), CLS(Cumulative Layout Shift), INP(Interaction to Next Paint). 랩 데이터에는 Lighthouse를 사용하세요.

html
// PerformanceObserver for Core Web Vitals
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(entry.name, entry.startTime);
  }
}).observe({ entryTypes: ['paint', 'largest-contentful-paint'] });

// Navigation timing
const [nav] = performance.getEntriesByType('navigation');
console.log('DOM Content Loaded:', nav.domContentLoadedEventEnd);
console.log('Load:', nav.loadEventEnd);
25

고급 폼

입력 유형

HTML5 입력 유형은 내장 검증과 UI를 제공합니다. email/url은 형식을 검증합니다. number/date는 픽커를 제공합니다. color/range는 전문화된 UI를 가집니다. accept는 파일 유형을 필터링합니다. 브라우저가 자동으로 검증을 처리합니다.

html
<input type="email" required>
<input type="url" placeholder="https://">
<input type="number" min="0" max="100" step="5">
<input type="date" min="2024-01-01">
<input type="color" value="#ff0000">
<input type="range" min="0" max="10">
<input type="file" accept="image/*">

폼 검증

required는 비어 있으면 제출을 방지합니다. minlength/maxlength는 길이를 제한합니다. pattern은 regex 검증을 사용합니다. 브라우저가 제출을 방지하고 오류를 표시합니다. setCustomValidity와 invalid 이벤트로 사용자 정의하세요.

html
<form>
  <input type="text" required minlength="3" maxlength="20" pattern="[A-Za-z]+">
  <input type="email" required>
  <input type="submit" value="Submit">
</form>

Fieldset & Legend

fieldset은 관련 폼 필드를 그룹화합니다. legend는 캡션을 제공합니다. fieldset의 disabled는 모든 필드를 비활성화합니다. 관련 입력을 그룹화하여 접근성을 향상합니다. 스크린 리더가 각 필드에 대해 legend를 알립니다.

html
<fieldset>
  <legend>Shipping Address</legend>
  <label>Street: <input type="text" name="street"></label>
  <label>City: <input type="text" name="city"></label>
</fieldset>
<fieldset disabled>
  <legend>Billing (disabled)</legend>
  <input type="text" name="billing">
</fieldset>

Datalist

datalist는 입력에 자동완성 제안을 제공합니다. 사용자는 선택하거나 자유롭게 입력할 수 있습니다. select와 달리 사용자 정의 값을 허용합니다. list 속성이 id로 datalist에 입력을 연결합니다. 검색과 태그에 유용합니다.

html
<label>Choose browser:
  <input list="browsers" name="browser">
</label>
<datalist id="browsers">
  <option value="Chrome">
  <option value="Firefox">
  <option value="Safari">
</datalist>

Output 요소

output은 계산된 결과를 표시합니다. for 속성은 입력 ID에 연결합니다. 폼 변경과 함께 실시간으로 업데이트됩니다. 간단한 계산에 JavaScript가 필요 없습니다. 실시간 영역으로 스크린 리더에 접근 가능합니다.

html
<form oninput="result.value = parseInt(a.value) + parseInt(b.value)">
  <input type="number" id="a" value="0"> +
  <input type="number" id="b" value="0"> =
  <output name="result" for="a b">0</output>
</form>
26

접근성 (a11y)

ARIA 레이블

aria-label은 보이는 텍스트가 없는 요소에 접근 가능한 이름을 제공합니다. aria-expanded는 토글 상태를 나타냅니다. aria-controls는 제어되는 요소에 연결합니다. HTML 시맨틱이 충분하지 않을 때만 ARIA를 사용하세요.

html
<button aria-label="Close menu" onclick="closeMenu()">
  <svg>...</svg>
</button>
<nav aria-label="Main navigation">...</nav>
<button aria-expanded="false" aria-controls="menu">Menu</button>

랜드마크 역할

랜드마크 역할은 스크린 리더 사용자의 내비게이션을 돕습니다. 대부분의 시맨틱 요소는 암시적 역할을 가집니다. 필요할 때만 명시적 역할을 추가하세요. form의 role="search"는 검색 랜드마크를 만듭니다. 시맨틱 요소와 역할 중복을 피하세요.

html
<header role="banner">...</header>
<nav role="navigation">...</nav>
<main role="main">...</main>
<aside role="complementary">...</aside>
<footer role="contentinfo">...</footer>
<form role="search">...</form>

포커스 관리

키보드 사용자를 위해 포커스를 관리하세요. 모달을 열 때 포커스를 내부로 이동하세요. 닫을 때 트리거로 포커스를 반환하세요. tabindex="-1"로 요소를 프로그래밍 방식으로 포커스 가능하게 만드세요. 대체 없이 포커스 윤곽선을 제거하지 마세요.

html
<button id="open">Open</button>
<div id="modal" hidden>
  <button id="close">Close</button>
</div>
<script>
document.getElementById('open').onclick = () => {
  modal.hidden = false;
  document.getElementById('close').focus();
};
</script>

건너뛰기 링크

건너뛰기 링크는 키보드 사용자가 반복적인 내비게이션을 우회하게 합니다. 포커스될 때까지 시각적으로 숨겨집니다. href는 메인 콘텐츠 ID를 가리킵니다. 접근성 준수(WCAG)에 필수적입니다. 내비게이션 효율을 향상합니다.

html
<body>
  <a href="#main" class="skip-link">Skip to main content</a>
  <nav>...long navigation...</nav>
  <main id="main">...</main>
</body>
<style>
.skip-link { position: absolute; left: -9999px; }
.skip-link:focus { left: 0; }
</style>

Alt 텍스트

Alt 텍스트는 스크린 리더를 위해 이미지를 설명합니다. 빈 alt=""는 장식용 이미지를 표시합니다(무시됨). 외모가 아닌 콘텐츠와 목적을 설명하세요. 복잡한 이미지의 경우 다른 곳에 더 긴 설명을 제공하세요. 툴팁에 alt를 사용하지 마세요(title 사용).

html
<!-- Informative image -->
<img src="chart.png" alt="Bar chart showing 30% increase in sales">
<!-- Decorative image -->
<img src="spacer.gif" alt="">
<!-- Complex image -->
<img src="diagram.png" alt="Network topology" longdesc="diagram-desc.html">
27

일반적인 함정

Alt 텍스트 누락

Alt 텍스트 누락은 접근성을 깨뜨립니다. 스크린 리더가 파일 이름을 읽습니다. "image"나 "photo"는 도움이 되지 않습니다. 콘텐츠와 목적을 설명하세요. 빈 alt=""는 장식용 이미지를 표시합니다. alt 속성을 완전히 생략하지 마세요.

html
<!-- BAD: no alt -->
<img src="photo.jpg">
<!-- BAD: unhelpful alt -->
<img src="photo.jpg" alt="image">
<!-- GOOD: descriptive alt -->
<img src="photo.jpg" alt="Team meeting in conference room">
<!-- Decorative: empty alt -->
<img src="border.png" alt="">

Div 수프

모든 것에 div를 사용하면 시맨틱 의미가 제거됩니다. 스크린 리더가 내비게이션할 수 없습니다. SEO가 콘텐츠 구조를 이해할 수 없습니다. 시맨틱 요소를 사용하세요: header, nav, main, article, section, aside, footer. 시맨틱 의도 없는 그룹화에만 div를 사용하세요.

html
<!-- BAD: div for everything -->
<div class="header">...</div>
<div class="nav">...</div>
<div class="article">...</div>
<!-- GOOD: semantic elements -->
<header>...</header>
<nav>...</nav>
<article>...</article>

인라인 스타일

인라인 스타일은 콘텐츠와 표현을 혼합하여 유지보수를 어렵게 합니다. 높은 특이성을 가져 스타일시트를 재정의합니다. 캐싱되거나 재사용될 수 없습니다. 클래스와 외부 스타일시트를 사용하세요. 동적 값에만 인라인 스타일을 사용하세요.

html
<!-- BAD: inline styles -->
<div style="color: red; font-size: 16px;">Text</div>
<!-- GOOD: external CSS -->
<div class="error">Text</div>
<link rel="stylesheet" href="styles.css">

Button vs Link

버튼은 작업을 트리거합니다(저장, 삭제, 토글). 링크는 URL로 이동합니다. 작업에 링크를 사용하면 키보드 내비게이션(Space vs Enter)과 시맨틱이 깨집니다. 스크린 리더가 다르게 알립니다. 폼 제출을 방지하려면 type="button"을 사용하세요.

html
<!-- BAD: link for actions -->
<a href="#" onclick="save()">Save</a>
<!-- GOOD: button for actions -->
<button type="button" onclick="save()">Save</button>
<!-- Link for navigation -->
<a href="/about">About</a>

제목 계층

제목은 문서 개요를 만듭니다. 수준을 건너뛰지 마세요(h1에서 h3로). 페이지당 하나의 h1을 사용하세요(메인 제목). 스크린 리더 사용자는 제목으로 내비게이션합니다. 논리적 계층을 유지하세요. 시각적 스타일링에 제목 수준이 아닌 CSS를 사용하세요.

html
<!-- BAD: skip levels -->
<h1>Title</h1>
<h3>Subtitle</h3>  <!-- Skipped h2 -->
<!-- BAD: multiple h1 -->
<h1>Title</h1>
<h1>Another</h1>
<!-- GOOD: hierarchical -->
<h1>Main Title</h1>
<h2>Section</h2>
<h3>Subsection</h3>

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.