По стандарту HTML любой атрибут, имя которого начинается с префикса data-, валиден на любом элементе. Это официальный ответ на давнюю потребность разработчиков: положить рядом с элементом служебную информацию – идентификатор, состояние, настройку компонента, – не изобретая нестандартные атрибуты и не пряча данные в имена классов. Браузер такие атрибуты не отображает и никак не интерпретирует: они существуют для скриптов, стилей и автотестов. В отличие от атрибутов ARIA, которые описывают элемент для вспомогательных технологий, data-* – канал «для себя»: разработчик сам решает, что и зачем там хранить.
Как это работает
Имя после префикса пишут в kebab-case: латиница в нижнем регистре, цифры, дефисы – data-user-id, data-index-number. Стандарт допускает также точку, двоеточие и подчёркивание, но на практике держатся kebab-case: именно дефис участвует в преобразовании имени для JavaScript. В JavaScript такие атрибуты доступны через свойство element.dataset – объект DOMStringMap, в котором префикс data- отброшен, а дефисы превращены в camelCase: data-index-number читается как dataset.indexNumber. Преобразование работает в обе стороны: запись el.dataset.someDataAttr = "x" создаёт в разметке атрибут data-some-data-attr="x", причём dataset и HTML синхронны – изменение одного сразу видно в другом.
<article id="post" data-columns="3" data-index-number="12314">...</article>
const article = document.querySelector("#post");
article.dataset.columns; // "3" – всегда строка
article.dataset.indexNumber; // "12314" (data-index-number → indexNumber)
article.dataset.columns = 5; // запишет data-columns="5"
delete article.dataset.indexNumber; // удалит атрибут из HTML
Три правила, о которые спотыкаются чаще всего:
- Значения – всегда строки. Число 42 станет строкой "42", null – строкой "null". Числа приводят через Number(), сложные структуры хранят как JSON-строку и разбирают через JSON.parse.
- Проверка наличия – через
'columns' in el.datasetили сравнение с undefined: отсутствующий атрибут не бросает ошибку, а просто возвращает undefined. - Поиск по атрибуту –
document.querySelectorAll('[data-columns]')находит все элементы с таким атрибутом, а'[data-columns="3"]'– только с конкретным значением.
getAttribute и dataset – в чём разница
| Критерий | element.dataset | getAttribute / setAttribute |
|---|---|---|
| Имя | camelCase без префикса: dataset.userId | Как в разметке: getAttribute("data-user-id") |
| Доступ | Объектный: чтение, запись, delete | Три отдельных метода: get, set, removeAttribute |
| Область применения | Только data-атрибуты | Любые атрибуты элемента |
| Когда выбирать | Основная работа с data-*: читаемее и короче | Имена без преобразований, универсальный код |
Для единичных обращений разница в производительности на практике не имеет значения – выбирают по читаемости кода.
Зачем это нужно
- JS-хуки – скрипт находит элементы по data-атрибуту, а не по классу: классы остаются стилям, и правка вёрстки не ломает JavaScript. Отдельный случай – хуки для автотестов вида
data-testid. - Параметры для обработчиков – при делегировании событий один слушатель на контейнере читает из
datasetкликнутого элемента, с каким объектом работать:data-user-id="42"вместо отдельного обработчика на каждую кнопку. - Состояния и варианты для CSS – селекторы атрибутов вида
[data-variant="warning"]заменяют россыпь модификаторов-классов, аcontent: attr(data-tooltip)выводит значение атрибута в псевдоэлементе – так делают чистые CSS-тултипы. - Мост между JS и CSS – например, скрипт определяет тёмную тему через window.matchMedia() и записывает результат в data-атрибут на корневом элементе, а стили реагируют селектором по этому атрибуту.
Когда data-атрибуты не подходят
Data-* – канал для служебных, а не смысловых данных. Скринридеры и другие вспомогательные технологии могут не иметь доступа к значениям data-атрибутов, поэтому видимый или важный по смыслу контент должен лежать в обычной разметке – это базовое правило доступности. Для описания элементов ассистивным технологиям есть отдельные атрибуты, например aria-label. Поисковые краулеры тоже могут не индексировать значения data-атрибутов – контент, который должен попасть в поиск, в них прятать нельзя. Наконец, data-атрибуты – не база данных: большие и сложные структуры держат в состоянии приложения или получают с сервера, а в разметке оставляют только ключи и короткие параметры.
Пример
Классика делегирования: список пользователей, у каждого кнопка «Открыть». Вместо обработчика на каждую кнопку – один на документе, а параметры лежат в разметке:
<button data-user-id="42">Открыть</button>
<button data-user-id="43">Открыть</button>
document.addEventListener("click", (e) => {
const btn = e.target.closest("[data-user-id]");
if (btn) {
openUser(Number(btn.dataset.userId)); // dataset вернёт строку "42"
}
});
CSS-часть – вариант оформления и тултип из атрибута:
.callout[data-variant="warning"] {
border-color: rgb(235 15 15); /* значение селектора – в кавычках, даже число */
}
.hint::after {
content: attr(data-tooltip);
}
На Битрикс-проектах data-атрибуты – основной способ передать данные из PHP-шаблона компонента в JavaScript: идентификаторы элементов инфоблока, параметры для целей Метрики, настройки слайдеров. Шаблон выводит атрибуты в вёрстку, скрипт читает dataset – без глобальных переменных и инлайн-скриптов на каждый элемент. Такой код переживает редизайн: пока имена data-атрибутов на месте, JS-логика не зависит от классов и структуры вёрстки, и правки дизайнера не превращаются в тикеты про сломанные скрипты.