HTML Custom Data Attributes

โ–ถ Try It Yourself

HTML Custom Data Attributes

1. Introduction

Custom data attributes โ€” written as data-* โ€” allow you to store arbitrary private data directly in HTML elements without polluting the DOM with non-standard attributes. Introduced in HTML5, they bridge the gap between HTML markup and JavaScript logic: a clean, standardised way to annotate elements with information that scripts can read, modify, and act upon. Any attribute prefixed with data- followed by at least one character is valid in any HTML element.

2. Concept

data-* vs Other Approaches

Method Valid HTML5? Accessible to JS? Problem
data-* attributes Yes Yes (via dataset) None โ€” use this
Custom attributes (e.g. mydata) No Yes (via getAttribute) Fails HTML validation
Stuffing data in class names Yes but misuse Complex parsing required Confuses styling logic
Hidden input fields Yes Via form serialisation Adds to form submission
Note: Data attribute names are case-insensitive in HTML but the dataset API converts hyphenated names to camelCase automatically: data-product-id becomes element.dataset.productId in JavaScript.
Tip: Data attributes are perfect for JavaScript hooks โ€” they cleanly separate the data layer from styling concerns. Your CSS class names describe appearance; your data-* attributes hold state and configuration. Never mix the two responsibilities.
Warning: Do not store sensitive information (passwords, tokens, PII) in data attributes. They are visible to anyone who inspects the page’s HTML source. Use server-side sessions or encrypted cookies for sensitive state.

3. Basic Example

<!DOCTYPE html>
<html lang="en">
  <head><meta charset="UTF-8"><title>Data Attributes Demo</title></head>
  <body>

    <!-- Single data attribute -->
    <button
      type="button"
      data-action="add-to-cart"
      data-product-id="SKU-4821"
      data-quantity="1"
    >
      Add to Cart
    </button>

    <!-- List items with metadata -->
    <ul id="task-list">
      <li data-task-id="1" data-status="complete" data-priority="high">
        Design homepage wireframe
      </li>
      <li data-task-id="2" data-status="pending" data-priority="medium">
        Write API documentation
      </li>
      <li data-task-id="3" data-status="pending" data-priority="low">
        Set up CI/CD pipeline
      </li>
    </ul>

    <p id="output"></p>

    <script>
      // Reading via dataset API
      var btn = document.querySelector('[data-action="add-to-cart"]');

      btn.addEventListener('click', function() {
        var id  = btn.dataset.productId;   // "SKU-4821"
        var qty = btn.dataset.quantity;    // "1"
        document.getElementById('output').textContent =
          'Added product ' + id + ' (qty: ' + qty + ') to cart.';
      });

      // Filtering tasks by status
      var pending = document.querySelectorAll('[data-status="pending"]');
      console.log('Pending tasks:', pending.length);  // 2

      // Updating a data attribute
      pending[0].dataset.status = 'in-progress';
      console.log(pending[0].dataset.status);  // "in-progress"
    </script>

  </body>
</html>

4. How It Works

Step 1 โ€” Defining data-* Attributes

Any attribute on any HTML element prefixed with data- is valid. The name after the prefix must contain at least one character and consist of lowercase letters, digits, hyphens, underscores, or dots. Example: data-product-id, data-user.role.

Step 2 โ€” Reading with dataset

element.dataset is a DOMStringMap. Each data-* attribute appears as a property with the prefix stripped and hyphens converted to camelCase. data-product-id โ†’ dataset.productId. Reading returns a string โ€” use Number() or JSON.parse() for typed values.

Step 3 โ€” Writing and Updating

Assign to dataset.propertyName to set or update the attribute. This immediately reflects in the DOM as data-property-name="value". You can also remove an attribute with delete element.dataset.propertyName.

Step 4 โ€” CSS Attribute Selectors

CSS can target data attributes with attribute selectors: [data-status="complete"] { opacity: 0.5; }. This allows style variations driven by HTML state without adding/removing CSS classes โ€” a clean separation of concerns.

5. Real-World Example

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Product Catalogue</title>
    <style>
      .product-card[data-in-stock="false"] { opacity: 0.5; }
      .product-card[data-in-stock="false"] .add-btn { cursor: not-allowed; }
      [data-category="sale"] .price::before { content: "SALE "; color: #dc2626; font-weight: bold; }
      :focus-visible { outline: 3px solid #0070f3; outline-offset: 2px; }
    </style>
  </head>
  <body>
    <h1>Our Products</h1>

    <div id="catalogue">

      <article class="product-card"
        data-product-id="PB15"
        data-category="laptop"
        data-in-stock="true"
        data-price="1299.00"
      >
        <h2>ProBook 15</h2>
        <p class="price">ยฃ1,299.00</p>
        <button class="add-btn" type="button">Add to Cart</button>
      </article>

      <article class="product-card"
        data-product-id="M4K"
        data-category="sale"
        data-in-stock="true"
        data-price="249.00"
      >
        <h2>4K Monitor</h2>
        <p class="price">ยฃ249.00</p>
        <button class="add-btn" type="button">Add to Cart</button>
      </article>

      <article class="product-card"
        data-product-id="KBD"
        data-category="accessory"
        data-in-stock="false"
        data-price="89.00"
      >
        <h2>Mechanical Keyboard</h2>
        <p class="price">ยฃ89.00</p>
        <button class="add-btn" type="button" disabled>Out of Stock</button>
      </article>

    </div>

    <div id="cart" aria-live="polite"></div>

    <script>
      document.querySelectorAll('.add-btn:not([disabled])').forEach(function(btn) {
        btn.addEventListener('click', function() {
          var card = btn.closest('[data-product-id]');
          var id    = card.dataset.productId;
          var name  = card.querySelector('h2').textContent;
          var price = card.dataset.price;
          document.getElementById('cart').textContent =
            'Added: ' + name + ' (' + id + ') โ€” ยฃ' + price;
        });
      });
    </script>
  </body>
</html>

6. Common Mistakes

Using non-standard custom attributes (fails validation)

<button productid="SKU-123">Buy</button>

Always use the data-* prefix for custom attributes

<button data-product-id="SKU-123">Buy</button>

Storing sensitive data in data attributes (visible in page source)

<div data-api-token="secret123"></div>

Use server-side sessions for sensitive information

<div data-user-id="42" data-session-ref="public-ref-only"></div>

7. Try It Yourself

▶ Try It Yourself

8. Quick Reference

Operation Syntax Example
Define in HTML data-name=”value” <div data-user-id=”42″>
Read in JS element.dataset.name el.dataset.userId โ†’ “42”
Write in JS element.dataset.name = val el.dataset.status = “active”
Delete in JS delete element.dataset.name delete el.dataset.status
Read in CSS attr(data-name) content: attr(data-label)
CSS selector [data-name=”value”] [data-status=”complete”]
Hyphen โ†’ camelCase data-product-id โ†’ dataset.productId Automatic in dataset API

9. Quiz

🧠 Test Yourself

How does the dataset API expose the attribute data-product-id in JavaScript?





โ–ถ Try It Yourself