Atributos data-*: el puente entre HTML y JavaScript

Aprende a usar los atributos data-* para almacenar información personalizada en elementos HTML, acceder a ella desde JavaScript con dataset y combinarla con CSS.

Los atributos data-* permiten almacenar información personalizada directamente en los elementos HTML. Son el canal oficial entre el marcado y JavaScript: limpios, semánticos y sin necesidad de variables globales ni clases CSS con significado oculto.


Sintaxis básica

Cualquier atributo que empiece por data- seguido de al menos un carácter es un atributo de datos válido:

<article
  data-id="42"
  data-categoria="frontend"
  data-publicado="2026-03-20"
  data-destacado="true"
>
  <h2>HTML Intermedio</h2>
</article>

Reglas de nomenclatura

  • Solo letras minúsculas, números y guiones. Sin mayúsculas.
  • No pueden empezar por xml.
  • No pueden contener dos puntos (:).
<!-- ✅ Correcto -->
<div data-user-id="1" data-max-items="10">...</div>

<!-- ❌ Incorrecto -->
<div data-userId="1" data-Max-items="10">...</div>

Acceder a data-* desde JavaScript

La propiedad dataset del elemento expone todos sus atributos data-* como un objeto. Los guiones del nombre se convierten en camelCase automáticamente:

<article
  id="post-1"
  data-id="42"
  data-categoria="frontend"
  data-publicado="2026-03-20"
>
  Artículo
</article>

<script>
  const articulo = document.getElementById('post-1');

  // Leer
  console.log(articulo.dataset.id);         // "42"
  console.log(articulo.dataset.categoria);  // "frontend"
  console.log(articulo.dataset.publicado);  // "2026-03-20"

  // Escribir (modifica el atributo en el DOM)
  articulo.dataset.categoria = 'css';

  // Borrar
  delete articulo.dataset.publicado;

  // Comprobar si existe
  console.log('id' in articulo.dataset); // true
</script>

Conversión de nombres: guión → camelCase

Atributo HTMLdataset en JS
data-iddataset.id
data-user-namedataset.userName
data-max-itemsdataset.maxItems
data-is-activedataset.isActive

Caso práctico: lista de ítems filtrables

<div>
  <button data-filtro="todos">Todos</button>
  <button data-filtro="frontend">Frontend</button>
  <button data-filtro="backend">Backend</button>
</div>

<ul id="lista">
  <li data-categoria="frontend">HTML semántico</li>
  <li data-categoria="backend">Node.js y APIs</li>
  <li data-categoria="frontend">CSS Grid avanzado</li>
  <li data-categoria="backend">Bases de datos SQL</li>
</ul>

<script>
  const items = document.querySelectorAll('#lista li');
  const botones = document.querySelectorAll('[data-filtro]');

  botones.forEach((btn) => {
    btn.addEventListener('click', () => {
      const filtro = btn.dataset.filtro;
      items.forEach((item) => {
        const visible = filtro === 'todos' || item.dataset.categoria === filtro;
        item.hidden = !visible;
      });
    });
  });
</script>

Caso práctico: componente de tabs

<nav>
  <button data-tab="html" class="activo">HTML</button>
  <button data-tab="css">CSS</button>
  <button data-tab="js">JavaScript</button>
</nav>

<section data-panel="html">
  <p>Contenido de HTML.</p>
</section>
<section data-panel="css" hidden>
  <p>Contenido de CSS.</p>
</section>
<section data-panel="js" hidden>
  <p>Contenido de JavaScript.</p>
</section>

<script>
  const tabs = document.querySelectorAll('[data-tab]');
  const panels = document.querySelectorAll('[data-panel]');

  tabs.forEach((tab) => {
    tab.addEventListener('click', () => {
      const target = tab.dataset.tab;

      tabs.forEach((t) => t.classList.remove('activo'));
      tab.classList.add('activo');

      panels.forEach((panel) => {
        panel.hidden = panel.dataset.panel !== target;
      });
    });
  });
</script>

Usar data-* desde CSS

Los atributos data-* son accesibles desde CSS con el selector de atributos. Esto permite cambiar estilos basados en datos sin añadir clases:

<span data-estado="activo">Usuario</span>
<span data-estado="inactivo">Otro usuario</span>
[data-estado="activo"] {
  color: #22c55e;
}

[data-estado="inactivo"] {
  color: #ef4444;
}

También puedes mostrar el valor del atributo con attr() en la propiedad content:

/* Muestra el nombre del atajo de teclado junto al elemento */
[data-atajo]::after {
  content: ' (' attr(data-atajo) ')';
  font-size: 0.75em;
  opacity: 0.6;
}
<button data-atajo="Ctrl+S">Guardar</button>
<!-- Se renderiza como: "Guardar (Ctrl+S)" -->

Pasar configuración a componentes

Un patrón muy útil es usar data-* para configurar componentes JavaScript sin código extra:

<!-- Carrusel autoplay cada 3 segundos -->
<div
  class="carrusel"
  data-autoplay="true"
  data-intervalo="3000"
  data-loop="true"
>
  <img src="slide-1.jpg" alt="Slide 1" />
  <img src="slide-2.jpg" alt="Slide 2" />
</div>

<script>
  document.querySelectorAll('.carrusel').forEach((el) => {
    const { autoplay, intervalo, loop } = el.dataset;
    iniciarCarrusel(el, {
      autoplay: autoplay === 'true',
      intervalo: Number(intervalo),
      loop: loop === 'true',
    });
  });
</script>

Este patrón permite tener múltiples instancias con configuraciones distintas sin JavaScript adicional.


Alternativa a getAttribute

Hay dos formas de leer atributos data-*. dataset es la más moderna y recomendada:

const el = document.querySelector('[data-id]');

// Forma antigua
el.getAttribute('data-id');   // "42"
el.setAttribute('data-id', '99');

// Forma moderna (recomendada)
el.dataset.id;                // "42"
el.dataset.id = '99';

¿Qué hemos aprendido?

  • Los atributos data-* almacenan datos personalizados en los elementos HTML.
  • dataset los expone en JavaScript con nombres en camelCase.
  • Se pueden leer, escribir y borrar directamente desde dataset.
  • Son accesibles desde CSS con selectores de atributo.
  • Son ideales para configurar componentes y filtrar listas sin variables extra.

Siguiente paso

En la próxima lección veremos atributos HTML que deberías conocer: defer, async, rel, hidden, inert, contenteditable, tabindex y más.