Skip to content

HTML Hoja de referencia

El lenguaje de marcado estándar para crear páginas web.

01

Estructura del documento

Plantilla básica de página

Cada documento HTML5 comienza con <!DOCTYPE html>, seguido de <html> con un atributo lang para accesibilidad y SEO. El <head> contiene metadatos (charset, viewport, title), y <body> contiene el contenido visible. La meta tag viewport asegura el renderizado correcto en dispositivos móviles.

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>

Sección head y metadatos

El elemento <head> contiene metadatos legibles por máquina. charset declara la codificación de caracteres (UTF-8 cubre casi todos los caracteres). La meta tag description es crucial para SEO — los motores de búsqueda la muestran en resultados. Usa defer en scripts para cargarlos después del parsing 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>

Comentarios y comentarios condicionales

Los comentarios HTML comienzan con <!-- y terminan con -->. No se muestran en el navegador pero son visibles en el código fuente de la página. Los comentarios son útiles para dejar notas, marcar secciones (TODO, FIXME) y documentación. Los comentarios condicionales (solo IE) están obsoletos pero pueden aparecer en código legacy.

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]-->

Jerarquía de encabezados

HTML proporciona seis niveles de encabezado, h1 (el más alto) a h6 (el más bajo). Úsalos en orden jerárquico sin saltar niveles. Los motores de búsqueda usan encabezados para entender la estructura de la página. Mejor práctica: un h1 por página, seguido de h2 para secciones principales, h3 para subsecciones, etc.

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>

Contenedores div y span

<div> es un contenedor block-level usado para agrupar elementos y aplicar estilos. <span> es un contenedor inline para estilizar porciones pequeñas de texto. Ambos son elementos genéricos sin significado semántico — prefiere tags semánticos (header, nav, main, article) cuando sea apropiado para mejor accesibilidad y SEO.

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

Formato de texto

Negrita, cursiva y énfasis

<strong> indica texto importante (los screen readers lo enfatizan), mientras que <b> es negrita puramente visual. Similarmente, <em> indica énfasis de estrés (semántico), mientras que <i> es cursiva visual. Prefiere <strong> y <em> para accesibilidad. <b> y <i> son aceptables para propósitos estilísticos sin importancia semántica.

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 e insert

<mark> resalta texto como relevante (como un highlight de búsqueda). <del> marca texto eliminado, <ins> marca texto insertado — útil para mostrar ediciones. <s> tacha información desactualizada. <small> es para comentarios laterales o copyright. <u> debería usarse con cuidado ya que se parece a links.

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>

Superíndice y subíndice

<sub> crea texto subíndice (debajo de la línea base) para fórmulas químicas (H2O) y variables matemáticas. <sup> crea texto superíndice (encima de la línea base) para exponentes (x²), números ordinales (1st) y notas al pie. Ambos son elementos inline que ajustan posición vertical y tamaño de fuente.

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>

Citas y referencias

<blockquote> es para citas block-level (indentadas, multi-línea). <q> es para citas cortas inline (añade comillas automáticamente). El atributo cite proporciona la URL fuente. <cite> etiqueta el título de una obra o nombre del autor. Estos elementos mejoran el markup semántico para contenido citado.

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>

Código y texto preformateado

<code> marca snippets de código inline (fuente monospace). <pre> preserva whitespace y saltos de línea para texto preformateado. Combinar <pre><code> muestra bloques de código con indentación correcta. <kbd> representa input de teclado. <samp> es para output de programa, y <var> para variables en expresiones matemáticas.

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>

Saltos de línea y reglas horizontales

<br> crea un salto de línea dentro del texto (úsalo con moderación, prefiere CSS). <hr> crea una regla horizontal que representa un break temático entre secciones. <wbr> sugiere una oportunidad de salto de línea para palabras largas o URLs, permitiendo al navegador romper en puntos óptimos. Evita usar <br> para espaciado — usa margins CSS en su lugar.

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

Imágenes y multimedia

Imágenes

Siempre incluye alt text para accesibilidad y SEO — describe la imagen para screen readers y cuando la imagen falla al cargar. loading='lazy' difiere la carga de imágenes offscreen, mejorando la velocidad de página. srcset y sizes habilitan imágenes responsive — el navegador selecciona la mejor imagen basándose en resolución del dispositivo y viewport. Siempre especifica width y height para prevenir layout shift.

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"
>

Elemento picture

<picture> proporciona art direction y fallback de formato. Los elementos <source> ofrecen alternativas — el navegador elige el primer formato soportado (WebP/AVIF son más pequeños). El atributo media habilita diferentes imágenes para diferentes tamaños de pantalla. El <img> final es el fallback y siempre debe estar presente. Esto es superior a srcset para servir imágenes completamente diferentes.

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

<audio> embebe contenido de sonido. controls añade controles play/pause/volume. Múltiples elementos <source> proporcionan fallbacks de formato (MP3 es el más ampliamente soportado). autoplay está restringido por navegadores — muted autoplay generalmente está permitido. loop repite el audio. El texto dentro se muestra si el audio no es soportado. Siempre proporciona controles para la experiencia del usuario.

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

<video> embebe video con controles para play, pause, volume y fullscreen. poster establece un thumbnail antes de la reproducción. Múltiples sources proporcionan fallback de formato (MP4/H.264 es el más compatible). <track> añade subtítulos, captions o descripciones para accesibilidad. playsinline previene fullscreen forzado en iOS. Siempre incluye controles a menos que autoplay muted.

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 y figcaption

<figure> agrupa contenido autocontenido como imágenes, diagramas, listados de código o citas con su caption. <figcaption> proporciona un caption o leyenda. Este agrupamiento semántico mejora la accesibilidad — los screen readers anuncian la relación. Las figures pueden referenciarse desde el texto ('ver Figura 1') y moverse en el layout sin perder contexto.

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>

Embeds de iframe

<iframe> embebe contenido externo (videos, mapas, otras páginas). Siempre incluye un atributo title para accesibilidad. loading='lazy' mejora el rendimiento para iframes offscreen. El atributo allow especifica feature policies. El atributo sandbox restringe las capacidades del iframe por seguridad. Ten cuidado — los iframes pueden impactar rendimiento y seguridad.

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

Listas

Listas no ordenadas

<ul> crea una lista no ordenada (con viñetas). Cada elemento se envuelve en <li>. La propiedad CSS list-style-type cambia el estilo de viñeta (disc, circle, square, none). Las listas no ordenadas son para elementos donde el orden no importa. Usa list-style: none y styling personalizado para menús de navegación y listas de características.

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>

Listas ordenadas

<ol> crea una lista ordenada (numerada) para elementos secuenciales. El atributo start establece el número inicial. reversed muestra elementos en orden descendente. El atributo type cambia el estilo de numeración (1, A, a, I, i). Usa <ol> para pasos, rankings o cualquier secuencia donde el orden importa.

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>

Listas de descripción

<dl> crea una lista de descripción emparejando términos (<dt>) con descripciones (<dd>). Esto es ideal para glosarios, páginas FAQ y pares término-definición. Múltiples <dd> pueden describir un <dt>, y viceversa. Las listas de descripción proporcionan significado semántico que las tablas de definición carecen, mejorando accesibilidad.

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>

Listas anidadas

Las listas pueden anidarse colocando un <ul> o <ol> dentro de un <li>. Los navegadores indentan automáticamente las listas anidadas y pueden cambiar estilos de viñeta. El anidamiento crea estructuras jerárquicas como tabla de contenidos, árboles de archivos o menús multi-nivel. Mantén el anidamiento razonable (2-3 niveles) para usabilidad. Las listas profundamente anidadas se vuelven difíciles de leer.

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>

Lista con links (navegación)

Las listas de links dentro de <nav> crean menús de navegación semánticos. Los elementos <ul> anidados forman submenús dropdown. Esta estructura es accesible para screen readers y fácilmente estilizable con CSS. Usa atributos aria (aria-expanded, aria-haspopup) para dropdowns interactivos. La navegación basada en listas es el patrón estándar para menús accesibles.

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

Tablas

Tabla básica

<table> crea datos tabulares. <thead> agrupa filas de header, <tbody> agrupa filas de body, <tfoot> agrupa filas de footer. <tr> es una fila de tabla, <th> es una celda de header (negrita, centrada), <td> es una celda de datos. Usar thead/tbody/tfoot mejora accesibilidad y habilita mejor estilización. Las tablas deberían usarse para datos, no para layout.

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>

Celdas que abarcan

colspan hace que una celda abarque múltiples columnas (merge horizontal). rowspan hace que una celda abarque múltiples filas (merge vertical). Estos atributos crean layouts de tabla complejos como headers que agrupan múltiples columnas. Ten cuidado con spanning — puede hacer las tablas más difíciles de parsear para screen readers. Siempre testea accesibilidad.

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 y scope de tabla

<caption> proporciona un título para la tabla, mejorando accesibilidad. El atributo scope en <th> dice a los screen readers si un header aplica a una columna (scope='col') o fila (scope='row'). Esto es esencial para tablas complejas. Para tablas aún más complejas, usa atributos id y headers para asociar explícitamente celdas con sus 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>

Grupos de columnas

<colgroup> agrupa columnas para estilización. Los elementos <col> dentro aplican estilos (especialmente width) a columnas específicas. Esto es más eficiente que establecer width en cada celda. Los grupos de columnas también soportan el atributo span para estilizar múltiples columnas a la vez. Úsalo para anchos de columna consistentes en todas las filas.

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>

Estilización de tablas

border-collapse: collapse fusiona bordes adyacentes en uno. Usa :nth-child(even) para filas con rayas de cebra (mejora legibilidad). :hover resalta filas en mouseover. El estilizado de th diferencia headers. Mantén el estilizado de tabla limpio y legible — evita bordes excesivos. Las tablas responsive pueden necesitar overflow-x: auto en un wrapper para pantallas pequeñas.

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

Formularios e input

Estructura de formulario

<form> envuelve controles de input. action especifica la URL de submission, method es GET (visible en URL) o POST (oculto). enctype='multipart/form-data' se requiere para subidas de archivos. <fieldset> agrupa campos relacionados, <legend> proporciona un caption. Siempre asocia <label> con inputs usando matching for/id para accesibilidad.

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>

Tipos de input

HTML5 introdujo muchos tipos de input con validación integrada y teclados mobile-friendly. type='email' valida formato de email. type='number' proporciona spinners. type='date' y 'time' muestran pickers nativos. type='color' abre un color picker. type='range' crea un slider. type='file' con accept restringe tipos de archivo. type='hidden' almacena datos no mostrados a usuarios.

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 y textarea

<select> crea un dropdown. <optgroup> agrupa opciones relacionadas con un label. <option> define opciones; el atributo selected pre-selecciona una. <textarea> es para input de texto multi-línea. rows y cols establecen el tamaño visible. maxlength limita el conteo de caracteres. placeholder proporciona una pista. Ambos soportan atributos required y 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>

Checkboxes y radio buttons

Los checkboxes permiten múltiples selecciones; los radio buttons permiten solo uno (cuando comparten el mismo name). El atributo checked pre-selecciona una opción. Siempre envuelve inputs en <label> para texto clicable. Usa <fieldset> y <legend> para agrupar opciones relacionadas para accesibilidad. El valor se envía al servidor solo si está seleccionado.

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>

Validación de formulario

HTML5 proporciona validación client-side integrada. required previene submission si está vacío. minlength/maxlength limitan la longitud del texto. pattern usa regex para validación personalizada (el atributo title guía a los usuarios). type='email' y type='url' validan formato. min/max funcionan con números y fechas. Añade novalidate al formulario para deshabilitar validación.

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>

Buttons y datalist

type='submit' envía el formulario, type='reset' limpia todos los campos, type='button' es un botón personalizado. <datalist> proporciona sugerencias de autocomplete para elementos <input> — vincúlalos con list/id. A diferencia de <select>, los usuarios pueden ingresar valores personalizados. Esto combina la flexibilidad del input de texto con la conveniencia de sugerencias.

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 semántico

Header y navegación

<header> representa contenido introductorio — típicamente un logo, encabezado y navegación. Puede aparecer en la parte superior de una página o dentro de un <article> o <section>. <nav> identifica links de navegación, ayudando a los screen readers a saltar a la navegación. Una página puede tener múltiples headers (ej., uno por sección). Header no es lo mismo que head — es contenido visible.

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 y article

<main> envuelve el contenido dominante de la página (solo uno por página). <article> representa contenido autocontenido que podría distribuirse independientemente (blog post, news article, forum post). <time> con datetime proporciona fechas legibles por máquina para SEO. <section> agrupa contenido temáticamente relacionado con un encabezado. Estos tags mejoran el outline del documento y accesibilidad.

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 y aside

<section> agrupa contenido relacionado con un encabezado — úsalo cuando el contenido tiene un encabezado natural. <aside> representa contenido tangencialmente relacionado al contenido principal (sidebars, pull quotes, ads, links relacionados). Ambos mejoran la estructura del documento. Las sections pueden anidarse. Los asides típicamente se estilizan como sidebars pero son semánticos, no presentacionales.

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> contiene contenido de footer para una página o sección — copyright, links a documentos relacionados, info de contacto, sitemap. Una página puede tener múltiples footers (ej., uno por sección de article). Los footers no se requieren estar visualmente al fondo, pero semánticamente representan contenido conclusivo. Usa &copy; para el símbolo de copyright.

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 y summary

<details> crea un widget de disclosure interactivo — el contenido está oculto hasta que el usuario hace clic en <summary>. El atributo open lo muestra expandido por defecto. Esto es HTML nativo, sin necesidad de JavaScript. Genial para FAQs, secciones colapsables y progressive disclosure. Los details anidados crean acordeones multi-nivel. Estiliza con el selector de atributo [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

Meta tags y SEO

Charset y viewport

charset='UTF-8' declara la codificación de caracteres — esencial para mostrar todos los caracteres correctamente. La meta tag viewport es crítica para diseño responsive: width=device-width coincide con el ancho del dispositivo, initial-scale=1.0 establece el nivel de zoom. Sin ella, los navegadores móviles renderizan páginas con ancho de desktop. Estas dos meta tags deberían estar en cada documento 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 y keywords

La meta tag description es la más importante para SEO — los motores de búsqueda la muestran bajo tu título en resultados (mantenla bajo 160 caracteres). keywords es ampliamente ignorada por los motores de búsqueda modernos. robots controla indexación: index/noindex, follow/nofollow. author acredita al creador del contenido. Escribe descriptions atractivas para mejorar las tasas de clic.

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)

Las meta tags Open Graph (og:) controlan cómo aparece tu página cuando se comparte en Facebook, LinkedIn y otras plataformas. og:title, og:description, og:image y og:url son las más importantes. og:type puede ser article, website, product, etc. La imagen debería ser de al menos 1200x630 píxeles. Testea con Facebook Sharing Debugger antes de deployar.

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

Las meta tags de Twitter Card controlan cómo aparecen los links en Twitter/X. twitter:card puede ser summary, summary_large_image o player. La large image card es la más atractiva. twitter:site y twitter:creator enlazan a cuentas de Twitter. Si hay tags Open Graph presentes, Twitter recurre a ellos, pero los tags Twitter explícitos dan más control. Testea con 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 y structured data

El canonical link tag dice a los motores de búsqueda la URL preferida para una página, previniendo penalizaciones por contenido duplicado. Los structured data JSON-LD ayudan a los motores de búsqueda a entender tu contenido y habilitan rich snippets (star ratings, breadcrumbs, FAQ accordions en resultados de búsqueda). Usa tipos de schema.org como Article, Product, Event o FAQPage. Valida con 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 y Canvas

Fundamentos de SVG

SVG (Scalable Vector Graphics) crea gráficos resolution-independent que escalan sin pérdida de calidad. El SVG inline puede estilizarse con CSS y manipularse con JavaScript. Formas comunes: <circle>, <rect>, <line>, <ellipse>, <polygon>, <path>. SVG es ideal para iconos, logos y diagramas. A diferencia de las imágenes raster, los SVGs son nítidos a cualquier tamaño.

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>

Formas SVG

Las formas SVG usan atributos basados en coordenadas. <rect> necesita x, y, width, height; rx/ry crean esquinas redondeadas. <circle> necesita cx, cy (centro) y r (radio). <ellipse> usa rx y ry para diferentes radios x/y. <line> conecta x1,y1 a x2,y2. <polygon> toma una lista de points. fill establece color interior, stroke establece outline.

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>

Paths SVG

<path> es el elemento SVG más potente, usando un atributo d con comandos: M (moveto), L (lineto), H/V (línea horizontal/vertical), C (cubic Bezier), Q (quadratic Bezier), A (arc), Z (close path). Mayúsculas = coordenadas absolutas, minúsculas = relativas. viewBox define el sistema de coordenadas. Los iconos SVG usan paths para iconos escalables y estilizables.

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>

Fundamentos de Canvas

<canvas> es una superficie de dibujo bitmap manipulada vía JavaScript. Obtén el contexto 2D con getContext('2d'). A diferencia de SVG, canvas es pixel-based (no escalable) y no es parte del DOM. Canvas es mejor para animaciones complejas, juegos y procesamiento de imágenes. SVG es mejor para gráficos estáticos, iconos y charts interactivos. Elige basándote en tus necesidades.

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>

Iconos SVG inline

SVG <symbol> define iconos reutilizables que se referencian con <use href='#id'>. Esta es la técnica SVG sprite — define iconos una vez, úsalos en cualquier lugar. El symbol está oculto (display:none) y se instancia vía <use>. Los iconos heredan color del CSS 'color' vía fill='currentColor'. Este enfoque es eficiente (una definición, muchos usos) y totalmente estilizable.

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

Accesibilidad (ARIA)

Roles ARIA

Los roles ARIA definen qué hace un elemento para los screen readers. Los roles landmark (banner, navigation, main, complementary, contentinfo) dejan a los usuarios de screen reader saltar entre regiones de la página. Los elementos semánticos HTML5 (<header>, <nav>, <main>) tienen roles ARIA implícitos, así que añadir atributos role a menudo es redundante — pero útil para navegadores antiguos. Siempre prefiere HTML semántico sobre ARIA cuando sea posible.

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 y aria-labelledby

aria-label define un nombre accesible cuando no existe label visible (ej., botones solo icono). aria-labelledby referencia el ID de elemento(s) de texto visible que etiquetan el control — útil cuando un label visible ya está en pantalla. Cuando se listan múltiples IDs, su texto se concatena en orden. Prefiere aria-labelledby cuando existe texto visible, ya que mantiene los labels sincronizados.

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 y tooltips

aria-describedby vincula un elemento a texto descriptivo adicional vía ID de elemento. Los screen readers anuncian esta descripción después del label. Es ideal para texto de ayuda, hints y mensajes de error. Usa role='alert' en mensajes de error para que se anuncien inmediatamente cuando aparecen. Establece aria-invalid='true' en campos con errores para que los screen readers indiquen el estado inválido.

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>

Live regions (aria-live)

Las live regions anuncian cambios de contenido dinámico a los usuarios de screen reader. aria-live='polite' espera una pausa antes de anunciar (bueno para updates de estado). aria-live='assertive' interrumpe inmediatamente (para errores críticos). aria-atomic='true' lee el contenido entero de la región en lugar de solo la porción cambiada. aria-relevant especifica qué tipos de cambios (additions, removals, text) disparan anuncios. Esencial para SPAs, chat apps y contenido auto-actualizable.

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>

Gestión de focus y tabindex

tabindex controla el comportamiento del focus de teclado. tabindex='0' hace elementos no interactivos (divs, spans) focusables en el orden DOM natural — úsalo para widgets personalizados. tabindex='-1' hace elementos focusables solo programáticamente vía JS .focus() — útil para modals y skip targets. Nunca uses valores tabindex positivos ya que sobrescriben el orden natural de tab y crean confusión. Siempre proporciona skip links como el primer elemento focusable en una página.

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>

Contenido oculto y solo para screen reader

aria-hidden='true' remueve elementos del accessibility tree mientras los mantiene visibles — úsalo para iconos decorativos, texto redundante o contenido off-screen. El patrón .sr-only oculta texto visualmente pero lo mantiene legible por screen readers — esencial para botones solo icono. Nunca uses aria-hidden en elementos focusables, ya que esto crea una desconexión entre la navegación de teclado y screen reader. La técnica clip es el método visually-hidden más robusto.

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

APIs de HTML5 (Geolocation, Storage, Drag & Drop)

API de Geolocation

La API de Geolocation te permite solicitar la ubicación física del usuario. Los navegadores siempre piden permiso — el usuario debe otorgar acceso explícitamente. getCurrentPosition toma callbacks de success y error más un objeto de opciones (enableHighAccuracy, timeout, maximumAge). Para tracking continuo, usa watchPosition() que devuelve un ID que puedes pasar a clearWatch(). Siempre maneja errores elegantemente (permiso denegado, posición no disponible, timeout). Geolocation requiere HTTPS en navegadores modernos.

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>

Web Storage (localStorage y sessionStorage)

Web Storage proporciona almacenamiento key-value en el cliente. localStorage persiste indefinidamente; sessionStorage se limpia cuando se cierra la pestaña. Ambos almacenan solo strings — usa JSON.stringify/parse para objetos. El límite de almacenamiento es ~5-10MB por origin. A diferencia de las cookies, los datos de storage no se envían con cada HTTP request. Ten en cuenta: el storage es síncrono y bloquea el main thread, y es accesible por cualquier script en el mismo origin (no apto para datos sensibles).

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 de Drag and Drop

La API de HTML5 Drag and Drop habilita interacciones de drag nativas. Establece draggable='true' en el elemento fuente. ondragstart se dispara cuando comienza el dragging — usa dataTransfer.setData() para almacenar los datos arrastrados. ondragover en el drop target DEBE llamar preventDefault() para permitir el drop. ondrop maneja el drop real — recupera datos con dataTransfer.getData(). Puedes arrastrar archivos del OS al navegador usando event.dataTransfer.files. Para apps complejas, considera librerías como SortableJS para mejor soporte cross-browser.

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>

API de Page Visibility

La API de Page Visibility te dice si la página es visible para el usuario (no minimizada, no en una pestaña de fondo). Úsala para pausar animaciones costosas, playback de video o polling cuando el usuario no está mirando — ahorrando batería y CPU. document.hidden es un booleano; document.visibilityState devuelve 'visible', 'hidden' o 'prerender'. El evento visibilitychange se dispara en transiciones. Esto es más confiable que blur/focus para detectar visibilidad real.

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>

API de Fullscreen

La API de Fullscreen te permite mostrar cualquier elemento en modo fullscreen. requestFullscreen() debe ser disparado por un gesto de usuario (click/keypress). Diferentes navegadores pueden necesitar vendor prefixes (webkit, moz, ms). El evento fullscreenchange se disampa al entrar o salir de fullscreen. document.fullscreenElement referencia el elemento fullscreen actual (null si no está en fullscreen). Usa la pseudo-clase CSS :fullscreen para estilizar elementos fullscreen de forma diferente.

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

Web Components

Custom Elements

Los Custom Elements te permiten crear tags HTML reutilizables con comportamiento encapsulado. La clase extiende HTMLElement (o subclases de HTMLElement). connectedCallback se dispara cuando el elemento se añade al DOM — úsalo para rendering. attributeChangedCallback reacciona a cambios de atributo, pero solo para atributos listados en observedAttributes. Los nombres deben contener un guion (ej., 'my-greeting') para evitar conflictos con HTML nativo. customElements.define() registra el elemento. Evita usar Shadow DOM a menos que necesites encapsulación de estilos.

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 proporciona encapsulación de estilos y DOM — los estilos dentro del shadow tree no se filtran afuera, y los estilos de página no se filtran adentro. attachShadow({mode:'open'}) crea un shadow root accesible vía element.shadowRoot. mode:'closed' niega acceso externo (raramente usado). Los elementos <slot> son placeholders donde se proyectan los light DOM children. Los named slots (<slot name='title'>) coinciden children con atributos slot coincidentes. ::slotted() estiliza contenido proyectado. Shadow DOM es clave para construir componentes reutilizables autocontenidos.

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 templates

El elemento <template> contiene HTML que no se renderiza cuando la página carga — su contenido es inerte (los scripts no se ejecutan, las imágenes no cargan, los estilos no aplican). Accede al contenido vía .content (un DocumentFragment) y clónalo con cloneNode(true) para instanciarlo. Los templates son ideales para estructuras repetitivas generadas por JavaScript. A diferencia de innerHTML, los templates se parsean una vez y pueden clonarse repetidamente sin re-parsing. Combinados con Custom Elements y Shadow DOM, forman el estándar de Web Components.

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>

Lifecycle callbacks

Los Custom Elements tienen cuatro lifecycle callbacks. constructor() se ejecuta cuando se crea el elemento (evita trabajo pesado aquí). connectedCallback() se dispara al añadirse al DOM — ideal para setup y rendering. disconnectedCallback() se dispara al removerse — úsalo para limpieza (event listeners, timers). adoptedCallback() se dispara al moverse a un nuevo documento vía adoptNode() (raro). attributeChangedCallback() se dispara solo para atributos observados. El orden de upgrade es siempre: constructor → connectedCallback. Los cambios de atributo pueden dispararse antes de 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>

Customized built-in elements

Los customized built-in elements extienden elementos HTML nativos (como button, input, div) para heredar su comportamiento integrado. Usa el atributo 'is' en lugar de un tag name personalizado. El tercer argumento a customElements.define() especifica qué elemento nativo se está extendiendo. Esto es útil cuando quieres mejorar elementos existentes (ej., añadir validación a <input>) en lugar de crear completamente nuevos. Nota: Safari no soporta customized built-in elements — considera autonomous custom elements para compatibilidad cross-browser.

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

Elementos media (audio y video)

Elemento video

El elemento <video> embebe video sin plugins. El atributo controls muestra controles integrados play/pause/volume. poster especifica una imagen de preview antes de la reproducción. Múltiples tags <source> proporcionan fallbacks de formato — el navegador usa el primero que soporta (MP4/H.264 es el más ampliamente soportado, WebM es abierto y eficiente). Los elementos <track> añaden subtítulos, captions o descripciones vía archivos WebVTT. Siempre incluye texto de fallback para navegadores muy antiguos. Para autoplay, añade el atributo muted — la mayoría de navegadores bloquean autoplay con sonido.

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>

Elemento audio

El elemento <audio> embebe sonido. controls muestra la UI integrada del player. Sin controls, el elemento es invisible — controla la reproducción vía JavaScript (play(), pause(), volume, currentTime). preload='auto' sugiere que el navegador debería buffer el archivo; 'metadata' carga solo duración/info; 'none' no carga nada hasta play. MP3 tiene soporte universal; OGG Vorbis es abierto pero no soportado en Safari. Para juegos o timing preciso, usa la Web Audio API en lugar de <audio> para menor latencia y efectos.

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 y srcset (imágenes responsive)

Las imágenes responsive mejoran el rendimiento sirviendo imágenes de tamaño apropiado. srcset con descriptores de ancho (480w, 800w) más hints de sizes dejan al navegador elegir la mejor imagen — esto es preferido para resolution switching. <picture> con media queries habilita art direction (crops diferentes para diferentes pantallas). <source> con atributos type proporciona fallbacks de formato moderno (WebP, AVIF) con fallback JPG/PNG. Siempre incluye un <img> regular como último child de <picture> para fallback y accesibilidad.

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>

Embedding con iframe

Los iframes embeben otro documento dentro de la página actual. El atributo title es esencial para accesibilidad — los screen readers lo anuncian. El atributo sandbox restringe las capacidades del iframe por seguridad: valor vacío bloquea todo; añade tokens (allow-scripts, allow-forms, allow-same-origin) para re-habilitar características específicas. loading='lazy' difiere la carga hasta cerca del viewport. allow especifica feature policies (camera, microphone, autoplay). Ten cuidado al embeber contenido no confiable — sandboxéalo. Los iframes cross-origin no pueden accederse vía 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 y object

<embed> y <object> son métodos de embedding antiguos, ampliamente reemplazados por <iframe> y <video>. <embed> es self-closing y simple pero no ofrece contenido de fallback. <object> es más flexible — el contenido dentro de los tags sirve como fallback si el recurso embebido no puede mostrarse. Usa <object> para PDFs y SVGs donde necesitas fallback. Para desarrollo web moderno, prefiere <iframe> para páginas externas, <video>/<audio> para media, e <svg> inline para gráficos vectoriales. <embed> se usa principalmente para contenido de plugin (Flash, ahora deprecado).

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

Tipos de input en profundidad

Inputs de fecha y hora

Los tipos de input de fecha/hora de HTML5 proporcionan pickers nativos sin librerías JavaScript. type='date' da un calendario; type='time' da un time picker; type='datetime-local' combina ambos. type='month' y type='week' seleccionan meses y semanas. min/max limitan el rango seleccionable. step define granularidad (ej., step='1800' para intervalos de 30 minutos en segundos). El formato mostrado varía por navegador/locale, pero el valor enviado siempre está en formato ISO 8601 (YYYY-MM-DD). No todos los navegadores soportan todos los tipos igualmente — testea cross-browser.

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">

Inputs de número y range

type='number' proporciona un control spinner con flechas arriba/abajo. min, max y step limitan valores. Para moneda, usa step='0.01'. type='range' renderiza un slider — siempre párealo con un <output> o label visible mostrando el valor actual, ya que el valor no se muestra por defecto. Los inputs numéricos filtran input no numérico pero aún permiten algunos caracteres inválidos; valida server-side. Para cantidades que no necesitan spinner, considera type='text' con inputmode='numeric' y validación pattern.

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>

Inputs de color y archivo

type='color' abre un color picker nativo y devuelve un valor hex (#rrggbb). type='file' deja a los usuarios seleccionar archivos. El atributo accept filtra por tipo MIME o extensión (image/*, .pdf, image/png). El atributo multiple permite seleccionar múltiples archivos. Accede a los archivos seleccionados vía la propiedad files (un FileList) en JavaScript. Usa URL.createObjectURL() para crear una URL de preview local para imágenes. Para subidas de archivos grandes, considera chunked uploads o la File API para tracking de progreso. Siempre valida tipos y tamaños de archivo server-side.

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 (sugerencias de input)

<datalist> proporciona sugerencias de autocomplete para campos de input. Vincúlalo a un input vía el atributo list que coincide con el id del datalist. A diferencia de <select>, los usuarios aún pueden escribir cualquier valor — las sugerencias son opcionales. Funciona con inputs text, number, date, color y range. El navegador muestra sugerencias coincidentes mientras el usuario escribe. Esta es una alternativa ligera a librerías de autocomplete JavaScript para casos de uso simples. Nota: estilizar opciones dentro de datalist no está soportado — se renderizan con defaults del navegador.

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>

Atributos de validación de formulario

HTML5 proporciona validación client-side integrada vía atributos. required previene submission si está vacío. type='email'/'url' validan formato automáticamente. minlength/maxlength limitan longitud de texto. min/max limitan números y fechas. pattern usa regex para validación personalizada. El navegador muestra bubbles de error default. Personaliza mensajes con setCustomValidity() — pero siempre límpialo en input (setCustomValidity('')) para evitar errores pegajosos. La validación client-side mejora UX pero debe emparejarse con validación server-side por seguridad, ya que puede saltarse.

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

Service Worker y PWA

Registro de service worker

Un service worker es un archivo JavaScript que se ejecuta en segundo plano, separado de la página web, habilitando soporte offline, push notifications y background sync. Regístralo desde la página principal — debe servirse sobre HTTPS. La ubicación del archivo del service worker determina su scope (controla páginas en su directorio y subdirectorios). El registro es asíncrono y solo ocurre una vez por scope. Después de la primera visita, el SW se activa en cargas de página subsiguientes. Usa el evento load para retrasar el registro hasta después de que la página sea interactiva.

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>

Service worker: caching

El lifecycle del service worker tiene tres fases: install (cachear assets core), activate (limpiar caches viejos) y fetch (interceptar network requests). La estrategia cache-first mostrada aquí sirve respuestas cacheadas inmediatamente, recurriendo a network. Otras estrategias incluyen network-first (intentar network, recurrir a cache) y stale-while-revalidate (servir cache, actualizar en background). Sube CACHE_NAME para disparar updates de cache. La Cache API almacena pares Request/Response. Los service workers solo se ejecutan cuando se necesitan y pueden ser terminados por el navegador para ahorrar memoria.

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))
      )
    )
  );
});

Web App Manifest

El Web App Manifest es un archivo JSON que hace una web app instalable (Add to Home Screen). name es el nombre completo; short_name aparece en el icono de home screen. display:'standalone' oculta la UI del navegador, haciéndola parecer una app nativa. theme_color afecta el color del chrome del navegador. icons debe incluir al menos tamaños 192px y 512px. 'purpose':'maskable' deja a Android adaptar el icono a diferentes formas. Un manifest válido más un service worker registrado son los requisitos mínimos para una PWA. Testea con Lighthouse para compliance PWA.

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"
    }
  ]
}

Push notifications

Las push notifications te permiten re-engaged a usuarios incluso cuando la pestaña está cerrada. Primero pide permiso con Notification.requestPermission(). Luego suscríbete vía PushManager — el objeto de suscripción contiene una URL endpoint y claves. Envía esta suscripción a tu servidor, que la usa para enviar push messages vía la Web Push API (con claves VAPID para autenticación). El event handler push del service worker muestra la notificación. userVisibleOnly:true significa que cada push debe mostrar una notificación (sin pushes silenciosos). Requiere HTTPS y un service worker.

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"
    })
  );
});

Background sync

Background Sync te permite diferir acciones hasta que el usuario tenga conectividad estable. Cuando registras un sync event, el navegador lo dispara cuando se restaura la conectividad — incluso si el usuario ha cerrado la pestaña. El sync event handler (en el service worker) realiza el trabajo diferido. Si el trabajo lanza un error, el navegador reintenta automáticamente con exponential backoff. e.tag identifica el tipo de sync — solo un sync por tag se queuea. Usa IndexedDB para almacenar datos pendientes. Esto es ideal para apps de mensajería, form submissions y data sync. Periodic Sync (para updates regulares) está disponible en Chrome pero no en todos los navegadores.

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

Meta tags en profundidad (Open Graph y Twitter)

Tags Open Graph (Facebook)

Los tags Open Graph (OG) controlan cómo aparece tu página cuando se comparte en Facebook, LinkedIn y la mayoría de plataformas sociales. og:title es el headline; og:description es el resumen; og:image es la imagen de preview (recomendado 1200x630px). og:type (website, article, video.movie) afecta el renderizado. Para articles, añade article:published_time, article:author y article:section para previews más ricas. Siempre especifica dimensiones de og:image para renderizado más rápido. Testea con Facebook Sharing Debugger. Los tags OG son esenciales para marketing en social media y tasas de clic.

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>

Tags de Twitter Card

Los tags de Twitter Card controlan cómo aparecen los links en X (Twitter). El tipo twitter:card puede ser 'summary' (imagen pequeña), 'summary_large_image' (imagen grande sobre el texto), 'player' (video/audio) o 'app'. twitter:site es el @handle del sitio; twitter:creator es el @handle del autor. La imagen debería ser de al menos 300x157px (summary) o 600x314px (large image). twitter:image:alt mejora accesibilidad. Testea con Twitter Card Validator. Incluso si tienes tags OG, añadir tags Twitter-specific te da más control sobre previews de 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 y meta mobile

La meta tag viewport es crítica para diseño responsive — sin ella, los navegadores móviles renderizan páginas con ancho de desktop y hacen zoom out. width=device-width coincide con el ancho del dispositivo; initial-scale=1 establece el nivel de zoom. Evita deshabilitar el zoom del usuario (user-scalable=no) ya que viola las guidelines de accesibilidad WCAG. apple-mobile-web-app-capable hace la app full-screen cuando se añade a la home screen de iOS. theme-color afecta el color de la barra de direcciones del navegador en mobile Chrome y Safari. Estas tags mobile-specific mejoran significativamente la experiencia PWA y mobile.

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 y charset

Las meta tags http-equiv simulan HTTP headers directamente en HTML. charset debe ser el primer elemento en <head> y dentro de los primeros 1024 bytes para prevenir ataques XSS basados en encoding. http-equiv='refresh' redirige después de un delay — prefiere redirects server-side para SEO. Content-Security-Policy vía meta es limitado (no report-uri, no frame-ancestors) — prefiere HTTP headers. Cache-Control vía meta es poco confiable — usa HTTP headers en su lugar. X-UA-Compatible fuerza a IE a usar su último rendering engine. Siempre prefiere HTTP headers reales sobre http-equiv cuando sea posible.

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>

Structured data (JSON-LD)

JSON-LD (JavaScript Object Notation for Linked Data) es el formato recomendado por Google para structured data. Ayuda a los motores de búsqueda a entender tu contenido, habilitando rich snippets (star ratings, fechas de eventos, breadcrumbs, FAQ accordions) en resultados de búsqueda. Tipos comunes incluyen Article, Product, Recipe, Event, LocalBusiness y FAQPage. Coloca el script en el <head> con type='application/ld+json'. Valida con Google Rich Results Test. Los structured data no garantizan rich snippets pero te hacen elegible. Es una herramienta SEO potente para aumentar las tasas de clic desde resultados de búsqueda.

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 semántico en profundidad

Article vs section vs div

Elegir el elemento contenedor correcto importa para accesibilidad y SEO. <article> es para contenido que podría distribuirse independientemente (blog posts, news, comments, product cards). <section> agrupa contenido temáticamente relacionado — siempre debería tener un encabezado. <div> es el último recurso para layout/styling cuando ningún elemento semántico encaja. Los elementos <article> anidados (como comments dentro de un post) son válidos. La regla general: si el contenido podría sindicarse vía RSS, usa <article>; si es un capítulo temático, usa <section>; si es puramente para layout, usa <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 y figcaption

<figure> representa contenido autocontenido referenciado desde el texto principal — imágenes, diagramas, listados de código o citas. <figcaption> proporciona un caption/título. A diferencia de un <img> plano, figure/figcaption vincula semánticamente lo visual a su descripción. Los screen readers anuncian figures con sus captions. Las figures pueden posicionarse en cualquier lugar relativo al texto que las referencia. Usa <figure> para cualquier contenido que tenga un caption y sea referenciado por número (Figura 1, Listado 1). Para imágenes puramente decorativas, usa un <img> plano con alt vacío.

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 y summary (disclosure)

<details> y <summary> crean secciones colapsables (acordeón) nativas sin ningún JavaScript. Hacer clic en el <summary> togglea el contenido. El atributo open lo hace expandido por defecto. El evento toggle se dispara al expandir/colapsar. Esto es excelente para FAQs, paneles de settings y progressive disclosure. Los screen readers lo anuncian como un widget de disclosure. Puedes estilizar el marker triangular default con CSS (summary::-webkit-details-marker o list-style). Para interacciones complejas, puede que aún necesites JavaScript, pero para toggles simples, esta solución nativa es ideal.

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>

Elementos time y mark

<time> envuelve fechas/horas legibles por humanos con un atributo datetime legible por máquina en formato ISO 8601. Esto ayuda a los motores de búsqueda, calendarios y tecnologías asistivas a parsear fechas correctamente. datetime soporta fechas (YYYY-MM-DD), horas (HH:MM), datetimes con timezone y duraciones (PT2H30M). <mark> representa texto resaltado por relevancia, como coincidencias de términos de búsqueda. A diferencia de <strong> o <em> (que indican importancia/énfasis), <mark> indica relevancia contextual. Ambos elementos mejoran el significado semántico y 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>

Elemento dialog

El elemento <dialog> proporciona diálogos modales y no-modales nativos sin librerías. showModal() lo abre como modal (con backdrop, bloquea interacción de página); show() lo abre no-modalmente. close() lo cierra. form method='dialog' cierra el dialog en submit, con el value del botón como returnValue. El pseudo-elemento ::backdrop estiliza el overlay modal. La tecla ESC cierra dialogs modales automáticamente. El rendering de top layer significa que el dialog aparece sobre todo el contenido independientemente del z-index. El evento close se dispara después de cerrar. Esto ahora está bien soportado y reemplaza muchas librerías de modal 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

Formularios avanzados e input

Fieldset y legend

<fieldset> agrupa controles de formulario relacionados, y <legend> proporciona un caption para el grupo. Esto es crucial para accesibilidad — los screen readers anuncian el legend antes de cada control en el grupo, proporcionando contexto. El atributo disabled en un fieldset deshabilita todos los controles dentro. Los fieldsets también mejoran la organización visual con un borde default. Para radio buttons, fieldset/legend es la forma recomendada de etiquetar el grupo. Evita anidar fieldsets demasiado profundo, ya que puede confundir a los usuarios de screen reader.

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>

Elementos output y progress

<output> muestra el resultado de un cálculo — tiene una relación live con inputs de formulario (vía el atributo for). Es semánticamente más significativo que un span para valores computados. <progress> representa completación de una tarea (value/max); sin un value, muestra un spinner indeterminado. <meter> representa un valor escalar dentro de un rango conocido (como espacio en disco o puntajes de test) — los atributos low, high y optimum definen umbrales que afectan el color (verde/amarillo/rojo). Ambos progress y meter tienen estilización integrada que varía por navegador pero puede personalizarse con 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 de formulario

El atributo autocomplete ayuda a los navegadores a llenar formularios usando datos almacenados del usuario. Usa tokens estandarizados: 'name', 'email', 'tel', 'street-address', 'address-level2' (city), 'postal-code', 'country'. Para tarjetas de crédito: 'cc-name', 'cc-number', 'cc-exp'. autocomplete='off' deshabilita autofill (aunque los navegadores pueden ignorar esto para campos no sensibles). autocomplete='one-time-code' dispara autofill de código SMS en móvil. Los tokens autocomplete adecuados mejoran dramáticamente las tasas de completado de formulario y la experiencia del usuario. También ayudan a los password managers a identificar campos correctamente.

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>

Métodos de submission de formulario

GET añade datos de formulario a la URL (visible, bookmarkable, longitud limitada) — úsalo para búsquedas y filtros. POST envía datos en el body del request (no visible, sin límite de longitud) — úsalo para crear/actualizar datos. enctype='multipart/form-data' se requiere para subidas de archivos. El par name/value del botón se incluye en la submission — múltiples botones submit pueden disparar diferentes acciones vía el mismo name con diferentes values. Para submission AJAX, usa el objeto FormData y fetch(). Siempre usa POST para datos sensibles, ya que los datos GET aparecen en el historial del navegador y logs del servidor.

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 y spellcheck

contenteditable='true' hace cualquier elemento directamente editable en el navegador — la base de los rich text editors. spellcheck='true' habilita el spell checking del navegador (subrayado rojo para misspellings). Para snippets de código, establece spellcheck='false' para evitar falsos positivos. El atributo contenteditable puede heredarse — los elementos child son editables a menos que se establezca en 'false'. Guardar contenido editable requiere JavaScript (ej., almacenar en localStorage en input). Construir un rich text editor completo con contenteditable es complejo (manejar paste, formateo, posición del cursor) — considera librerías como Quill, TipTap o ProseMirror para uso en producción.

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

Meta tags SEO

Tags Open Graph

Los tags Open Graph (creados por Facebook) controlan cómo aparece tu página cuando se comparte en social media. og:image debería ser 1200x630px. Sin estos tags, las plataformas inventan previews desde el contenido de la página, a menudo pobremente.

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

Los tags de Twitter Card controlan cómo se renderizan los links en X/Twitter. summary_large_image muestra una imagen prominente sobre el título; summary muestra una miniatura cuadrada más pequeña. El tag twitter:site vincula la card a tu cuenta.

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 y robots

rel="canonical" previene penalizaciones por contenido duplicado apuntando a la URL master. La meta tag robots dirige crawlers: noindex remueve la página de resultados, nofollow bloquea el link-following.

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">

Structured data (JSON-LD)

Los structured data JSON-LD ayudan a los motores de búsqueda a entender el contenido y habilitan rich results (star ratings, breadcrumbs, info de eventos). Schema.org define el vocabulario. Coloca el script tag dentro de head o body.

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 y viewport

La meta tag description influye en la tasa de clic — mantenla bajo 160 caracteres. La tag viewport es obligatoria para diseño responsive en móvil. theme-color estiliza la barra UI del navegador. charset debe ser el primer elemento en 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

Fundamentos de SVG

Formas básicas

SVG dibuja con elementos XML. Las formas incluyen rect, circle, ellipse, line, polyline y polygon. A diferencia de las imágenes raster, los SVGs escalan sin pérdida de calidad y pueden estilizarse con CSS. fill establece color interior, stroke establece outline.

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

El elemento path es la primitiva SVG más potente. Comandos: M (move), L (line), H/V (horizontal/vertical), C (cubic bezier), Q (quadratic), A (arc), Z (close). Mayúsculas = coords absolutas, minúsculas = relativas.

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>

Grupos y reutilización

Agrupa elementos con g para aplicar atributos compartidos o transformarlos juntos. Define formas reutilizables dentro de defs y referéncialas con use href="#id". Esto mantiene los SVGs DRY y más pequeños.

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>

Texto y estilización

El texto SVG es texto real — seleccionable, buscable y nítido a cualquier escala. font-family, font-size, font-weight reflejan CSS. text-anchor controla la alineación horizontal (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>

Gradientes y patrones

Los gradientes se definen en defs y se referencian vía fill="url(#id)". linearGradient transiciona a lo largo de una línea; radialGradient irradia desde un punto central. Cada stop define un color en un offset de porcentaje.

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

Dibujo de formas

La Canvas API dibuja píxeles en un elemento canvas vía un 2D rendering context. fillRect/strokeRect dibujan rectángulos; los paths (beginPath, arc, lineTo) construyen formas complejas antes de fill() o stroke(). Canvas es raster — escalar difumina el dibujo.

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 y líneas

Los paths construyen formas punto por punto. moveTo inicia un sub-path nuevo; lineTo añade segmentos rectos; closePath conecta al inicio. bezierCurveTo y quadraticCurveTo dibujan curvas con puntos de control.

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();

Dibujo de imágenes

drawImage pinta una imagen, video u otro canvas. La forma de 9 argumentos recorta un rectángulo fuente y lo pinta en un rectángulo destino — útil para sprite sheets. Espera onload antes de dibujar.

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
};

Loop de animación

requestAnimationFrame programa draw() una vez por repaint del navegador (~60fps), pausando cuando la pestaña está oculta. clearRect limpia el canvas cada frame para prevenir trails. Para física, multiplica el movimiento por delta time para velocidad consistente.

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);

Manipulación de píxeles

getImageData devuelve un Uint8ClampedArray de valores de píxeles RGBA crudos (0-255 por canal). El acceso directo a píxeles habilita filtros y efectos. putImageData escribe el buffer modificado de vuelta. Esto es lento para canvases grandes.

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

Iframes y embeds

Fundamentos de iframe

Un iframe embebe otro documento dentro de la página actual. El atributo title es requerido para accesibilidad. loading="lazy" difiere la carga hasta cerca del viewport. Siempre establece width y height para prevenir layout shift.

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

Atributo sandbox

El atributo sandbox restringe las capacidades de un iframe. Un valor vacío bloquea todo. Añade tokens para re-habilitar características: allow-scripts, allow-forms, allow-same-origin, allow-popups. Nunca combines allow-scripts con allow-same-origin para contenido no confiable.

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 y object

embed y object son elementos legacy. object soporta contenido de fallback mostrado cuando el recurso falla. Los elementos HTML5 modernos (video, audio, picture) son preferidos. Usa iframe para contenido HTML, video para 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>

Comunicación postMessage

postMessage es la única forma de comunicarse a través de boundaries de iframe (orígenes diferentes). Siempre especifica el target origin en el tercer argumento. En el lado receptor, verifica e.origin antes de confiar en el mensaje.

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>

Iframes responsive

Para hacer iframes responsive (ej., video 16:9), envuélvelos en un contenedor con padding-bottom igual al porcentaje de aspect ratio (56.25% para 16:9). Posiciona el iframe absolutamente para llenar el contenedor.

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

Optimización de rendimiento

Resource hints

Los resource hints le dicen al navegador que prepare conexiones o fetch assets antes de que se necesiten. preconnect calienta DNS/TCP/TLS. preload fetcha assets críticos de la página actual temprano. prefetch fetcha recursos de la próxima página durante tiempo idle.

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">

Lazy loading de imágenes

loading="lazy" difiere la carga de imágenes offscreen hasta que el usuario hace scroll cerca de ellas. Siempre establece width y height para prevenir layout shift. El elemento picture sirve formatos modernos (WebP/AVIF) con fallback JPG.

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>

Estrategias de carga de scripts

Sin atributos, un script bloquea el parsing HTML. async descarga en paralelo pero se ejecuta inmediatamente cuando está listo — orden no garantizado. defer descarga en paralelo y se ejecuta en orden después de que el DOM se parsea — mejor para scripts principales.

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>

Critical CSS

Inlinear critical CSS (estilos necesitados para contenido above-the-fold) elimina requests CSS render-blocking, acelerando first paint. El CSS restante carga asíncronamente vía el truco preload. Herramientas como Critical extraen critical CSS automáticamente.

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>

Medición de rendimiento

La Performance API mide timing real de usuario. Métricas clave: FCP (First Contentful Paint), LCP (Largest Contentful Paint), CLS (Cumulative Layout Shift), INP (Interaction to Next Paint). Usa Lighthouse para datos de lab.

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

Formularios avanzados

Tipos de input

Los tipos de input HTML5 proporcionan validación y UI integradas. email/url validan formato. number/date proporcionan pickers. color/range tienen UI especializada. accept filtra tipos de archivo. Los navegadores manejan validación automáticamente.

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/*">

Validación de formulario

required previene submission si está vacío. minlength/maxlength limitan longitud. pattern usa validación regex. El navegador previene submission y muestra errores. Personaliza con setCustomValidity y el evento 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 y legend

fieldset agrupa campos de formulario relacionados. legend proporciona un caption. disabled en fieldset deshabilita todos los campos. Mejora accesibilidad agrupando inputs relacionados. Los screen readers anuncian el legend para cada campo.

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 proporciona sugerencias de autocomplete para input. Los usuarios pueden seleccionar o escribir libremente. A diferencia de select, permite valores personalizados. El atributo list vincula input a datalist por id. Útil para búsqueda y tags.

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

Elemento output

output muestra resultados computados. El atributo for vincula a IDs de input. Se actualiza live con cambios de formulario. No se necesita JavaScript para cálculos simples. Accesible a screen readers como live region.

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

Accesibilidad (a11y)

Labels ARIA

aria-label proporciona nombres accesibles para elementos sin texto visible. aria-expanded indica estado toggle. aria-controls vincula al elemento controlado. Usa ARIA solo cuando la semántica HTML es insuficiente.

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>

Roles landmark

Los roles landmark ayudan a los usuarios de screen reader a navegar. La mayoría de elementos semánticos tienen roles implícitos. Añade roles explícitos solo cuando se necesite. role="search" en un form crea un landmark de búsqueda. Evita redundancia de roles con elementos semánticos.

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>

Gestión de focus

Gestiona el focus para usuarios de teclado. Al abrir modals, mueve el focus adentro. Al cerrar, devuelve el focus al trigger. Usa tabindex="-1" para hacer elementos focusables programáticamente. Nunca remuevas outlines de focus sin reemplazo.

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>

Skip links

Los skip links dejan a los usuarios de teclado bypassar navegación repetitiva. Ocultos visualmente hasta que se enfocan. El href apunta al ID del contenido principal. Esencial para compliance de accesibilidad (WCAG). Mejora la eficiencia de navegación.

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 text

El alt text describe imágenes para screen readers. Alt vacío="" marca imágenes decorativas (ignoradas). Describe el contenido y propósito, no apariencia. Para imágenes complejas, proporciona una descripción más larga en otro lugar. Nunca uses alt para tooltips (usa 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

Pitfalls comunes

Alt text faltante

El alt text faltante rompe accesibilidad. Los screen readers leen el filename. "image" o "photo" no son útiles. Describe el contenido y propósito. Alt vacío="" marca imágenes decorativas. Nunca saltes el atributo alt completamente.

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 soup

Usar divs para todo remueve significado semántico. Los screen readers no pueden navegar. SEO no puede entender la estructura del contenido. Usa elementos semánticos: header, nav, main, article, section, aside, footer. Reserva div para agrupar sin intención semántica.

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>

Estilos inline

Los estilos inline mezclan contenido y presentación, haciendo el mantenimiento difícil. Tienen alta especificidad, sobrescribiendo stylesheets. No pueden cachease o reusarse. Usa classes y stylesheets externos. Reserva estilos inline para valores dinámicos.

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

Los botones disparan acciones (save, delete, toggle). Los links navegan a URLs. Usar links para acciones rompe la navegación de teclado (Space vs Enter) y la semántica. Los screen readers los anuncian de forma diferente. Usa type="button" para prevenir submission de formulario.

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>

Jerarquía de encabezados

Los encabezados crean un outline del documento. No saltes niveles (h1 a h3). Usa un h1 por página (título principal). Los usuarios de screen reader navegan por encabezados. Mantén jerarquía lógica. Usa CSS para estilizado visual, no niveles de encabezado.

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.