Articulo publicado el
Aprende a usar los atributos data-* para guardar información en elementos HTML y accederla desde JavaScript de forma segura y semántica
Cuando trabajas con JavaScript interactivo, con frecuencia necesitas asociar información adicional a elementos del DOM. Quizás necesitas almacenar un ID de producto en un botón de compra, el estado abierto o cerrado de un accordion, o configuraciones personalizadas para un componente.
Antes de que existieran los atributos data-*, los desarrolladores usaban soluciones improvisadas: clases CSS con información codificada, variables globales, o peor aún, modificaban propiedades directas de los elementos del DOM. Todo eso era frágil, poco semántico y difícil de mantener.
Los atributos data-* solucionan este problema de forma estándar. Son atributos HTML personalizados que te permiten guardar datos arbitrarios en cualquier elemento, accesibles desde JavaScript sin modificar el árbol del DOM ni la semántica del elemento.
Los atributos
data-*son un mecanismo oficial del estándar HTML5 para incrustar datos personalizados en elementos. Están disponibles en todos los navegadores modernos y son una mejor práctica recomendada por WHATWG (Web Hypertext Application Technology Working Group).
La sintaxis es sencilla: cualquier atributo que comience con data- seguido de un nombre en minúsculas (con guiones opcionales) es un atributo de datos válido.
<!-- Ejemplos válidos -->
<button data-id="123">Comprar</button>
<div data-status="active">Activo</div>
<article data-published-date="2026-07-07">...</article>
<section data-config-timeout="5000" data-config-retry="3">...</section>
La especificación tiene pocas restricciones, pero las convenciones ayudan a mantener el código legible:
data-.data-value si puedes ser específico: usa data-product-id en su lugar.data-accordion-multiple, data-modal-trigger.<!-- Mal: demasiado genérico -->
<div data-value="open"></div>
<!-- Bien: específico y descriptivo -->
<div data-accordion-state="open"></div>
Una vez que has almacenado datos en atributos data-*, necesitas recuperarlos. JavaScript ofrece dos formas principales: el atributo dataset (recomendado) y getAttribute().
La propiedad dataset convierte automáticamente los atributos data-* en propiedades JavaScript accesibles. Los guiones se transforman en camelCase.
<button id="btn" data-user-id="42" data-action="submit">Enviar</button>
const btn = document.getElementById("btn");
// Acceso mediante dataset (camelCase)
console.log(btn.dataset.userId); // "42"
console.log(btn.dataset.action); // "submit"
// Modificar datos
btn.dataset.status = "loading"; // Añade data-status="loading"
// Eliminar datos
delete btn.dataset.userId; // Remueve el atributo data-user-id
Ventajas de dataset:
Para casos donde necesites controlar exactamente el nombre del atributo, getAttribute() sigue siendo útil:
const btn = document.getElementById("btn");
// Acceso directo al atributo completo
console.log(btn.getAttribute("data-user-id")); // "42"
// Modificar
btn.setAttribute("data-status", "loading");
// Eliminar
btn.removeAttribute("data-user-id");
Los atributos data-* brillan cuando necesitas asociar datos a elementos HTML sin crear propiedades en JavaScript o modificar el árbol del DOM.
Muchos componentes reutilizables necesitan opciones personalizables. Los atributos data-* permiten expresar esa configuración directamente en HTML:
La clave es que el componente JavaScript pueda detectar automáticamente esos atributos y usarlos para su inicialización:
<!-- Accordion configurado por atributos data-* -->
<section id="faq" data-accordion data-accordion-multiple="false" data-accordion-open="0">
<article data-accordion-item>
<h3>
<button type="button" data-accordion-trigger>¿Qué es esto?</button>
</h3>
<div data-accordion-panel>
<p>Contenido...</p>
</div>
</article>
</section>
El JavaScript puede detectar automáticamente estos atributos y configurar el componente sin necesidad de pasar opciones adicionales:
class Accordion {
constructor(rootElement) {
this.root = rootElement;
this.multiple = this.root.dataset.accordionMultiple === "true";
this.initialOpen = parseInt(this.root.dataset.accordionOpen, 10) || null;
this.init();
}
init() {
// Usar this.multiple y this.initialOpen
}
}
// Inicializar automáticamente todos los accordions
document.querySelectorAll("[data-accordion]").forEach((el) => {
new Accordion(el);
});
Cuando necesitas vincular elementos del DOM con datos en tu aplicación (IDs de base de datos, SKUs de productos, etc.), los atributos data-* son el lugar natural:
<li data-product-id="1024">
<h4>Laptop Pro</h4>
<p data-price="1299.99">$1,299.99</p>
<button data-action="add-to-cart">Añadir al carrito</button>
</li>
document.querySelectorAll("[data-action='add-to-cart']").forEach((btn) => {
btn.addEventListener("click", (e) => {
const li = e.target.closest("li");
const productId = li.dataset.productId;
const price = li.querySelector("[data-price]").dataset.price;
console.log(`Añadiendo producto ${productId} a $${price}`);
addToCart(productId, price);
});
});
Para estados visuales o de lógica que cambien dinámicamente, los atributos data-* son perfectos porque el estado vive en el DOM, donde pertenece:
<div id="modal" data-state="closed">
<h2>Diálogo</h2>
<button data-action="close">Cerrar</button>
</div>
const modal = document.getElementById("modal");
function openModal() {
modal.dataset.state = "open";
}
function closeModal() {
modal.dataset.state = "closed";
}
// CSS reactivo al estado
// [data-state="open"] { display: block; }
// [data-state="closed"] { display: none; }
En interfaces con muchos elementos similares (listas de items, tablas), los atributos data-* eliminan la necesidad de listeners duplicados gracias a event delegation:
<table>
<tbody>
<tr data-row-id="101">
<td>Producto A</td>
<td><button data-action="edit">Editar</button></td>
</tr>
<tr data-row-id="102">
<td>Producto B</td>
<td><button data-action="edit">Editar</button></td>
</tr>
</tbody>
</table>
document.querySelector("table").addEventListener("click", (e) => {
if (e.target.dataset.action === "edit") {
const rowId = e.target.closest("tr").dataset.rowId;
editRow(rowId);
}
});
Un único listener maneja todos los botones, accediendo a datos específicos de cada fila sin necesidad de closures o listeners individuales.
Los atributos data-* son visibles en el HTML origen. Cualquiera puede inspeccionar la página y verlos. Nunca guardes:
// ❌ NUNCA: tokens, contraseñas o datos sensibles
<div data-api-token="sk_live_abcd1234..."></div>
<button data-user-password="micontraseña123"></button>
// ✅ SI: identificadores públicos o configuración no sensible
<div data-user-id="42"></div>
<button data-action="delete" data-item-id="99"></button>
Los atributos data-* son texto sin procesar. Si usas los datos de forma dinámica (como selectores CSS, URLs o código), debes validar y sanitizar:
<div data-redirect-url="https://example.com/page">Click aquí</div>
// ❌ Inseguro: inyección potencial
element.addEventListener("click", () => {
window.location = element.dataset.redirectUrl;
});
// ✅ Seguro: valida la URL
element.addEventListener("click", () => {
const url = new URL(element.dataset.redirectUrl, window.location.origin);
if (url.origin === window.location.origin) {
window.location = url.href;
}
});
La propiedad dataset es más legible que getAttribute(). Aunque ambas tienen rendimiento similar, el código es la principal consideración:
A continuación, una comparación clara entre ambas aproximaciones:
// Legible
element.dataset.productId;
// Menos legible
element.getAttribute("data-product-id");
Cuando sea posible, usa CSS para reaccionar a atributos data-* en lugar de inspeccionar el DOM desde JavaScript:
/* Mejor: CSS maneja la presentación */
[data-state="loading"] {
pointer-events: none;
opacity: 0.6;
}
// En lugar de inspeccionar el estado desde JS cada vez
element.dataset.state = "loading";
// No necesitas hacer element.classList.add("loading") o similar
Cuando un elemento tenga múltiples datos relacionados, usa un prefijo común para claridad:
<!-- Datos agrupados lógicamente -->
<div data-modal-id="form" data-modal-size="lg" data-modal-dismissible="true">
<!-- Mejor que: data-id, data-size, data-dismissible -->
</div>
Los atributos data-* almacenan texto. Si necesitas estructuras complejas, considera usar data- JSON:
<!-- ❌ Difícil de mantener -->
<div data-config-a="1" data-config-b="2" data-config-c="3"></div>
<!-- ✅ Más claro: JSON válido en un único atributo -->
<div data-config='{"timeout": 5000, "retry": 3, "debug": true}'></div>
const config = JSON.parse(element.dataset.config);
console.log(config.timeout); // 5000
No todo problema requiere atributos data-*. Considera alternativas en estos casos:
Datos que cambian frecuentemente: Si un valor se actualiza constantemente (posición del ratón, scroll, frame count), no lo almacenes en atributos; usa variables en JavaScript.
Lógica compleja: Si el dato requiere transformaciones o cálculos, mantenlo en estado de JavaScript, no en HTML.
Datos privados de componentes: Si un componente necesita estado interno que la interfaz HTML no debe exponer, usa propiedades de la clase o WeakMaps.
// ❌ Evitar: estado frágil y expuesto
element.dataset.internalCounter = "0";
// ✅ Mejor: estado privado
class Component {
#counter = 0;
increment() {
this.#counter++;
}
}
Los atributos data-* son un estándar elegante y eficaz para vincular datos a elementos HTML. Son especialmente útiles en componentes reutilizables, manejo de eventos delegados y expresar configuración en HTML de forma legible. Úsalos como puente entre el marcado HTML y la lógica JavaScript, pero recuerda: no son un almacenamiento permanente, no son seguros para datos sensibles, y no reemplazan el estado adecuado de JavaScript cuando la lógica lo requiere. Combinados con CSS, selectores y listeners delegados, los atributos data-* crean interfaces más robustas, mantenibles y semánticas.
Estos articulos pueden interesarte si te gusto este articulo.
Diseña un componente modal sólido, accesible y configurable sin depender de frameworks, usando clases utilitarias de Tailwind CSS.
Aprende a construir un sistema de tooltips flexible, accesible y controlable sin depender de librerías externas.
Aprende a construir un sistema de modales flexible, reutilizable y controlable sin depender de librerías externas.