Пользовательские данные в HTML data-* атрибуты

05 января 2026
Автор

Олег Марков

Введение

Пользовательские атрибуты данных в 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-id
  • data-product-price
  • data-sort-order
  • data-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-iddataset.productId
  • data-product-namedataset.productName
  • data-product-pricedataset.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

Правило простое:

  1. Берем имя атрибута без data-, например user-id-number.
  2. Делим по дефисам: user, id, number.
  3. Первое слово оставляем как есть.
  4. Каждое следующее слово пишем с заглавной буквы: idId, numberNumber.
  5. Склеиваем: userIdNumber.

Примеры:

  • data-user-iddataset.userId
  • data-long-item-namedataset.longItemName
  • data-api-version2dataset.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-* подходят хорошо

  1. Связь разметки и JS-логики

    Когда вам нужно "прикрепить" к элементу какую-то служебную информацию, нужную JavaScript-коду:

    • идентификатор сущности в базе;
    • тип действия;
    • параметры конфигурации для конкретной кнопки или блока.
  2. Легкая конфигурация компонентов

    Вы можете передавать настройки прямо из HTML, не создавая отдельный JSON или конфиг-файл:

    <div
      class="slider"
      data-autoplay="true"
      data-delay="3000"
      data-loop="false"
    ></div>
    

    В JS вы просто считываете эти значения и инициализируете слайдер.

  3. Интеграция с сервером без сложной логики

    Шаблонизатор на сервере (Django, Laravel, Node.js-шаблоны и т. д.) может подставлять значения в data-*, а клиентский код будет их использовать.

  4. Отладка и временная передача данных

    Иногда удобно на этапе отладки "подложить" значения в data-*, чтобы быстро проверить логику.

Когда лучше не использовать data-* атрибуты

  1. Для хранения больших объемов данных

    Например, целых JSON-структур или длинных текстов. Такие данные лучше загружать через API или хранить в script type="application/json".

  2. Для секретных и чувствительных данных

    Все, что попадает в HTML, видно пользователю и может быть изменено. Не храните в data-*:

    • токены доступа;
    • приватные ключи;
    • пароли;
    • любую важную бизнес-логику, которая не должна быть на клиенте.
  3. Когда есть более подходящий стандартный атрибут

    Если можно использовать 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 - атрибут hidden и его практическое применениеАтрибут class в HTML - как правильно использовать и организовывать стилиСтрелочка вправо

Постройте личный план изучения Html до уровня Middle — бесплатно!

Html — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по Html

Тег section в HTML - семантическая разметка структуры страницыТег nav в HTML - полное руководство по семантической навигацииТег main в HTML - подробное руководство по использованиюТег header в HTML - полное практическое руководствоТег footer в HTML - назначение семантика и практические примерыТег figure в HTML - как правильно оформлять иллюстрации и подписиТег figcaption в HTML - подробное руководство с примерамиТег aside в HTML - назначение правильная семантика и примерыТег article в HTML - семантика и практическое использование
Текстовая область HTML textarea - практическое руководствоВыпадающий список HTML select - полное руководство для разработчиковОпция списка HTML option - как работает и как правильно использоватьАтрибут method в HTML - как правильно отправлять данные формыЗаголовок группы HTML legend - как правильно использовать и оформлятьТег input в HTML - типы атрибуты валидация и примерыТег формы form в HTMLГруппа полей HTML fieldsetАтрибут action в HTML - как правильно задавать адрес отправки формы
Открыть базу знаний

Лучшие курсы по теме

изображение курса

HTML и CSS

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.9
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий