Олег Марков
Пользовательские данные в HTML data-* атрибуты
Введение
Пользовательские атрибуты данных в HTML, те самые data-*, позволяют хранить произвольную информацию прямо в разметке. Это удобный способ "прикрепить" к элементу дополнительные данные, связанные с логикой интерфейса, не нарушая стандартов и не придумывая свои невалидные атрибуты.
Раньше разработчики нередко использовали нестандартные атрибуты вроде myattr="...", но это ломало валидность HTML и могло конфликтовать с будущими стандартами. Спецификация HTML5 ввела официальный механизм — пользовательские атрибуты данных, которые:
- валидны с точки зрения стандарта;
- не влияют на отображение в браузере;
- легко читаются и изменяются из JavaScript;
- безопасны с точки зрения будущих обновлений спецификаций.
Сейчас вы увидите, как их правильно использовать, как к ним обращаться из JavaScript, где они полезны, а где лучше выбрать другой инструмент.
Что такое пользовательские data-* атрибуты
Основная идея
Атрибуты data-* — это любые атрибуты, имя которых начинается с data-. Например:
data-id="123"data-user-name="alex"data-product-price="199.90"
Браузер не использует эти значения для рендеринга страницы. Они нужны вам и вашему JavaScript-коду.
С точки зрения спецификации:
- все, что начинается с
data-, считается пользовательскими данными; - они доступны через специальный интерфейс
datasetу DOM-элемента; - имена после префикса
data-должны быть вkebab-case(слова через дефис).
Синтаксис и правила именования
Базовый синтаксис
Общий шаблон:
<div data-имя="значение"></div>
Пример:
<button
data-user-id="42"
data-role="admin"
data-is-active="true"
>
Открыть профиль
</button>
Комментарии к примеру:
data-user-id— хранит числовой идентификатор пользователя;data-role— роль пользователя в системе;data-is-active— флаг, который ваш код может трактовать как булево значение.
Браузеру все равно, что означают эти данные. Интерпретация полностью на вашей стороне.
Правила именования
Смотрите, как это работает:
- имя атрибута обязательно начинается с
data-; - после
data-можно использовать:- латинские буквы (a–z, A–Z);
- цифры (0–9);
- дефисы
-;
- нельзя использовать пробелы и спецсимволы вроде
@,$,!.
Корректные примеры:
data-user-iddata-product-pricedata-sort-orderdata-api-version2
Некорректные:
data-имя(кириллица)data-user id(пробел)data-$status(символ$)
Браузер может это "простить", но такие атрибуты нарушают спецификацию и могут работать непредсказуемо при доступе через JavaScript.
Регистр и стиль имен
В HTML атрибуты не чувствительны к регистру, но по соглашению используются строчные буквы. Именно такой стиль (kebab-case) удобно и прозрачно мапится в JavaScript в camelCase. Об этом поговорим дальше.
Связь data-* с JavaScript и свойством dataset
Интерфейс Element.dataset
У любого DOM-элемента есть свойство dataset. Это специальный объект, который содержит все data-* атрибуты этого элемента.
Давайте разберемся на примере:
<button
id="buy-btn"
data-product-id="1001"
data-product-name="Laptop"
data-product-price="899.99"
>
Купить
</button>
<script>
// Получаем DOM-элемент
const button = document.getElementById('buy-btn')
// Доступ ко всем data-* атрибутам через dataset
console.log(button.dataset)
// Выведет объект, похожий на
// {
// productId: "1001",
// productName: "Laptop",
// productPrice: "899.99"
// }
// Читаем отдельные значения
const id = button.dataset.productId
const name = button.dataset.productName
const price = button.dataset.productPrice
// Здесь мы выводим значения в консоль для наглядности
console.log(id, name, price)
</script>
Обратите внимание, как имена преобразуются:
data-product-id→dataset.productIddata-product-name→dataset.productNamedata-product-price→dataset.productPrice
То есть:
- дефисы
-преобразуются в границы слов; - каждое слово после дефиса начинается с заглавной буквы (camelCase);
- префикс
data-отбрасывается.
Чтение значений из dataset
Читать значения можно как через точку, так и через квадратные скобки.
<div id="user" data-user-id="77" data-user-name="alex"></div>
<script>
const userEl = document.getElementById('user')
// Через точку
const userId = userEl.dataset.userId
const userName = userEl.dataset.userName
// Через квадратные скобки
const userId2 = userEl.dataset['userId']
// Выводим для проверки
console.log(userId, userName)
console.log(userId2)
</script>
Все значения в dataset по умолчанию строки. Если вам нужно число или булево значение, преобразуйте их явно.
// Число
const idNumber = Number(userEl.dataset.userId)
// Булево (простая схема)
const isActive = userEl.dataset.isActive === 'true'
Запись и изменение значений
Вы не только читаете, но и записываете значения через dataset. Смотрите, я покажу вам, как это работает:
<div id="item" data-count="1"></div>
<script>
const item = document.getElementById('item')
// Читаем старое значение
console.log(item.dataset.count) // "1"
// Изменяем через dataset
item.dataset.count = '2'
// Проверяем результат
console.log(item.dataset.count) // "2"
console.log(item.getAttribute('data-count')) // "2"
</script>
Важно: при изменении dataset браузер автоматически синхронизирует соответствующий HTML-атрибут.
Добавление новых data-* атрибутов
Можно добавлять новые пользовательские атрибуты динамически:
<div id="user-card"></div>
<script>
const card = document.getElementById('user-card')
// Добавляем новое свойство
card.dataset.userId = '555'
// Еще одно
card.dataset.userRole = 'editor'
// Теперь в HTML появятся атрибуты:
// data-user-id="555" data-user-role="editor"
console.log(card.outerHTML)
</script>
Вы также можете использовать setAttribute, но в этом случае ручное преобразование имени остается на вас:
card.setAttribute('data-user-id', '555')
В обоих случаях важно соблюдать синхронизацию форматов:
- через
dataset.userId— camelCase; - в HTML —
data-user-id(kebab-case).
Удаление data-* атрибутов
Удалить пользовательский атрибут можно двумя способами:
<div id="block" data-state="open" data-mode="full"></div>
<script>
const block = document.getElementById('block')
// Через delete
delete block.dataset.state
// Или через removeAttribute
block.removeAttribute('data-mode')
// Проверяем, что получилось
console.log(block.dataset) // Остальные свойства, без state и mode
</script>
Преобразование имен из HTML в JavaScript и обратно
Этот момент часто вызывает вопросы, давайте разберем его отдельно.
Из kebab-case в camelCase
Правило простое:
- Берем имя атрибута без
data-, напримерuser-id-number. - Делим по дефисам:
user,id,number. - Первое слово оставляем как есть.
- Каждое следующее слово пишем с заглавной буквы:
id→Id,number→Number. - Склеиваем:
userIdNumber.
Примеры:
data-user-id→dataset.userIddata-long-item-name→dataset.longItemNamedata-api-version2→dataset.apiVersion2
Обратное преобразование
Если вы задаете значение через dataset в JavaScript, браузер автоматически создаст HTML-атрибут с дефисами:
element.dataset.someValue = '1'→ в DOM появитсяdata-some-value="1";element.dataset.userName = 'Alex'→data-user-name="Alex".
Избегайте заглавных букв в самом HTML, лучше придерживаться одного стиля: атрибуты в разметке — только в kebab-case, свойства в JS — в camelCase.
Где уместно использовать data-* атрибуты
Для чего data-* подходят хорошо
Связь разметки и JS-логики
Когда вам нужно "прикрепить" к элементу какую-то служебную информацию, нужную JavaScript-коду:
- идентификатор сущности в базе;
- тип действия;
- параметры конфигурации для конкретной кнопки или блока.
Легкая конфигурация компонентов
Вы можете передавать настройки прямо из HTML, не создавая отдельный JSON или конфиг-файл:
<div class="slider" data-autoplay="true" data-delay="3000" data-loop="false" ></div>В JS вы просто считываете эти значения и инициализируете слайдер.
Интеграция с сервером без сложной логики
Шаблонизатор на сервере (Django, Laravel, Node.js-шаблоны и т. д.) может подставлять значения в
data-*, а клиентский код будет их использовать.Отладка и временная передача данных
Иногда удобно на этапе отладки "подложить" значения в
data-*, чтобы быстро проверить логику.
Когда лучше не использовать data-* атрибуты
Для хранения больших объемов данных
Например, целых JSON-структур или длинных текстов. Такие данные лучше загружать через API или хранить в
script type="application/json".Для секретных и чувствительных данных
Все, что попадает в HTML, видно пользователю и может быть изменено. Не храните в
data-*:- токены доступа;
- приватные ключи;
- пароли;
- любую важную бизнес-логику, которая не должна быть на клиенте.
Когда есть более подходящий стандартный атрибут
Если можно использовать
href,src,value,id,for,typeи т. д., лучше использовать их. Не заменяйте стандартное семантическое поведение наdata-*без необходимости.
Практические примеры использования
Пример 1. Кнопки действий со связанными данными
Представьте список товаров, где каждой кнопке "Купить" нужен идентификатор товара.
<ul>
<li>
Ноутбук
<button class="buy-btn" data-product-id="101">Купить</button>
</li>
<li>
Смартфон
<button class="buy-btn" data-product-id="102">Купить</button>
</li>
</ul>
<script>
// Получаем все кнопки
const buttons = document.querySelectorAll('.buy-btn')
// Навешиваем обработчик на каждую
buttons.forEach(button => {
button.addEventListener('click', event => {
// Здесь мы читаем идентификатор товара
const productId = event.currentTarget.dataset.productId
// Здесь могла бы быть отправка запроса на сервер
console.log('Покупка товара с id =', productId)
})
})
</script>
Как видите, этот код выполняет простую задачу: берет id товара прямо из HTML-разметки. Серверный шаблон может легко подставлять значения data-product-id при генерации списка.
Пример 2. Настройка виджета через data-* атрибуты
Допустим, вы пишете универсальный модуль подсказок (tooltip) и хотите настраивать его через HTML.
<button
class="tooltip-trigger"
data-tooltip-text="Сохранить изменения"
data-tooltip-position="top"
>
Сохранить
</button>
<script>
// Здесь мы инициализируем все элементы с подсказками
const triggers = document.querySelectorAll('.tooltip-trigger')
triggers.forEach(trigger => {
// Читаем настройки из data-атрибутов
const text = trigger.dataset.tooltipText
const position = trigger.dataset.tooltipPosition || 'top'
// Здесь могла бы быть функция initTooltip(trigger, { text, position })
console.log('Инициализация tooltip:', text, 'позиция:', position)
})
</script>
Давайте посмотрим, что происходит в этом примере:
- HTML описывает поведение: текст и позицию подсказки;
- JavaScript читает эти данные, превращая HTML в "настраиваемый компонент";
- при изменении текста подсказки не нужно править JS-код — достаточно обновить HTML.
Пример 3. Хранение состояния на элементе
Иногда удобно хранить кусочек состояния прямо на DOM-элементе, если оно нужно только ему.
<button id="toggle" data-state="closed">
Открыть
</button>
<script>
const toggleBtn = document.getElementById('toggle')
toggleBtn.addEventListener('click', () => {
// Здесь мы читаем текущее состояние
const state = toggleBtn.dataset.state
if (state === 'closed') {
// Меняем состояние на открытое
toggleBtn.dataset.state = 'open'
toggleBtn.textContent = 'Закрыть'
} else {
// Возвращаем обратно
toggleBtn.dataset.state = 'closed'
toggleBtn.textContent = 'Открыть'
}
// Для отладки выводим текущее состояние
console.log('Текущее состояние:', toggleBtn.dataset.state)
})
</script>
Такое решение подходит для мелких интерфейсных задач, где нет сложного глобального состояния и фреймворков. Но при масштабировании проекта лучше перенести состояние в JS-логику или использовать фреймворк.
Пример 4. Передача сложных значений
Иногда вы хотите передать не просто строку, а, скажем, массив или объект. Формально data-* хранят только строки, но вы можете кодировать в них JSON.
<div
id="chart"
data-config='{"type":"bar","color":"#ff0000","labels":["Янв","Фев"]}'
></div>
<script>
const chartEl = document.getElementById('chart')
// Здесь мы читаем строку
const configStr = chartEl.dataset.config
// Превращаем строку в объект
const config = JSON.parse(configStr)
// Теперь config можно использовать для инициализации графика
console.log(config)
</script>
Обратите внимание:
- значение в HTML — валидная JSON-строка;
- в JS вы явно делаете
JSON.parse.
Минус подхода: такая строка сложнее поддерживать, особенно если шаблонизатор автоматически экранирует кавычки. Иногда проще хранить только ключ, а полную конфигурацию держать в JS.
Валидация, безопасность и подводные камни
Все значения строковые
Даже если вы пишете data-count="10", для dataset.count это строка "10". Если вы сразу используете это значение в арифметике, могут возникнуть неожиданные результаты.
<div id="item" data-count="10"></div>
<script>
const item = document.getElementById('item')
// Здесь мы забыли привести значение к числу
const count = item.dataset.count
console.log(count + 5) // "105" - конкатенация строк
</script>
Лучше всегда явно приводить тип:
const count = Number(item.dataset.count)
console.log(count + 5) // 15
Пользователь может изменить data-* атрибуты
Любой атрибут, в том числе data-*, можно изменить через инструменты разработчика в браузере. Поэтому:
- никогда не доверяйте значениям
data-*как "истине"; - на сервере всегда перепроверяйте права доступа и корректность данных;
- не используйте
data-*как единственный источник критически важной информации.
При клике по кнопке с data-user-id="5" пользователь может подменить значение на "6" и попробовать выполнить действие от имени другого пользователя. Сервер должен сам проверять, кому доступно действие, а не полагаться на data-*.
Производительность и избыток данных
Если вы храните слишком много данных в data-*:
- HTML становится тяжелым и грузится дольше;
- браузер тратит больше памяти на DOM;
- работа с
datasetможет замедляться при большом количестве элементов.
Старайтесь держать в data-* только то, что действительно нужно непосредственно на клиенте и связано с конкретным элементом.
Отличие от нестандартных атрибутов
Раньше было распространено использовать что-то вроде:
<div user-id="10"></div>
Сейчас лучше всегда использовать data-user-id. Причины:
- это валидно по HTML5;
- браузеры и инструменты разработчика понимают, что это "пользовательские данные";
datasetумеет автоматически собирать эти атрибуты и удобно их выдавать.
Практические рекомендации по стилю и архитектуре
Выберите единый стиль именования
Для HTML:
- используйте только
kebab-caseвdata-*:data-user-id,data-item-type;
Для JS:
- используйте
camelCaseпри обращении черезdataset:dataset.userId,dataset.itemType.
Это сделает код предсказуемым и понятным коллегам.
Не смешивайте бизнес-логику и разметку чрезмерно
data-* удобны, но если вы начинаете хранить в них половину бизнес-логики приложения, становится сложно поддерживать код. Подход простой:
- мелкие настройки и идентификаторы — в
data-*; - сложные сценарии, правила и вычисления — в JavaScript-коде.
Используйте data-* для "клея" между слоями
Хороший вариант применения:
- сервер генерирует HTML и подставляет
data-id,data-role,data-config-id; - клиент находит элементы по классам или атрибутам и читает
data-*как входные параметры.
Таким образом, data-* становятся "клеем" между серверной и клиентской частью.
Заключение
Пользовательские атрибуты данных data-* — это простой и стандартный способ прикрепить дополнительные данные к любому HTML-элементу. Они:
- позволяют хранить служебную информацию прямо в разметке;
- удобно читаются и изменяются через
element.datasetв JavaScript; - не нарушают валидность HTML и поддерживаются всеми современными браузерами.
Вы увидели, как правильно именовать атрибуты, как они преобразуются в свойства dataset, как читать, записывать и удалять значения. Мы также разобрали ситуации, где data-* особенно полезны, а где лучше выбрать другой подход.
Если вы используете data-* аккуратно — только для тех данных, которые действительно относятся к элементу и не являются конфиденциальными, — это делает код чище и понятнее и помогает разделить структуру, поведение и конфигурацию интерфейса.
Частозадаваемые технические вопросы
Как получить все элементы с определенным data-* атрибутом
Вы можете использовать CSS-селекторы по атрибутам:
// Здесь мы ищем все элементы, у которых есть атрибут data-user-id
const elements = document.querySelectorAll('[data-user-id]')
Если нужно отфильтровать по значению:
// Здесь мы выбираем элементы с конкретным значением атрибута
const elements = document.querySelectorAll('[data-user-id="10"]')
Как безопасно парсить JSON из data-* атрибута
Лучше оборачивать JSON.parse в try/catch и проверять наличие атрибута:
const raw = element.dataset.config
let config = {}
if (raw) {
try {
config = JSON.parse(raw)
} catch (e) {
console.error('Некорректный JSON в data-config', e)
}
}
Так вы избежите падения скрипта при некорректных данных.
Можно ли использовать data-* в CSS селекторах
Да, data-* — обычные атрибуты, их можно использовать в CSS:
/* Здесь мы выбираем элементы, у которых data-state="active" */
[data-state="active"] {
background-color: #e0ffe0;
}
Но не стоит строить на этом сложную логику показа/скрытия. Для динамики все равно лучше использовать классы из JS.
Как отличить отсутствие атрибута от пустого значения
Проверяйте наличие атрибута через hasAttribute:
if (element.hasAttribute('data-user-id')) {
// Атрибут есть, даже если он пустой
const value = element.dataset.userId // может быть ""
} else {
// Атрибута нет
}
dataset.userId вернет undefined, если атрибута нет вообще.
Почему не видно data-* в node.dataset в старых браузерах
В очень старых браузерах (IE до 11) dataset может отсутствовать. В таких случаях используют getAttribute и setAttribute:
// Получение
const value = element.getAttribute('data-user-id')
// Установка
element.setAttribute('data-user-id', '10')
В современных браузерах dataset поддерживается нативно, и для новых проектов можно опираться именно на него.
Постройте личный план изучения Html до уровня Middle — бесплатно!
Html — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Html
Лучшие курсы по теме

HTML и CSS
Антон Ларичев
TypeScript с нуля
Антон Ларичев