Skip to content
Carl Victor Fontanos
Carl Victor Fontanos

Carl Victor Fontanos

Software Engineer

I build web applications and share what I learn along the way.

© 2026

data-* Attributes: The Contract Between Your HTML, CSS and JS

C
Carlo Fontanos
· 2 min read

The humble data attribute is the closest thing frontend has to a universal interface: the server renders it, JavaScript reads and writes it, CSS styles against it - and none of the three needs to know the others exist.

The JS side: dataset

<button data-product-id="42" data-action="add-to-cart" data-stock="3">Add</button>
const btn = e.target.closest('[data-action="add-to-cart"]');
btn.dataset.productId;         // "42" - note the camelCase conversion
btn.dataset.stock = '2';       // writes data-stock="2" back to the DOM

Two things everyone trips on once: kebab-case attributes become camelCase properties (data-product-id → dataset.productId), and everything is a string. dataset.stock is "3", so Number() it before math - "3" - 1 works by accident, "3" + 1 gives "31" by betrayal.

Delegation plus data-action (as above) is my favorite jQuery-free event architecture: one listener on the container, behavior declared in markup, works for AJAX-injected elements automatically - the delegation pattern with the config living in HTML.

The CSS side: selectors and state

[data-stock="0"]        { opacity: 0.5; pointer-events: none; }
[data-theme="dark"]     { --bg: #0f172a; --text: #e2e8f0; }
[data-loading="true"] .label { visibility: hidden; }

Toggling el.dataset.loading = 'true' from JS and letting CSS own the visual consequences beats classList juggling when the state has multiple values - data-state="idle|loading|error|done" with four attribute selectors reads like a state machine, because it is one.

The party trick: attr() puts them on screen

.tooltip-host::after {
    content: attr(data-tooltip);
    position: absolute;
    bottom: 100%; left: 50%;
    translate: -50% -6px;
    padding: 4px 10px;
    background: #1e293b; color: #fff;
    border-radius: 6px; font-size: 12px;
    opacity: 0;
    transition: opacity 0.15s;
    pointer-events: none;
}
.tooltip-host:hover::after,
.tooltip-host:focus-visible::after { opacity: 1; }
<button class="tooltip-host" data-tooltip="Download as CSV">⬇</button>

A complete tooltip system with zero JavaScript - change the attribute, the tooltip changes. (Note: in content, attr() returns strings only; the typed attr() for other properties is still rolling out, so keep this trick to content.) Also note real tooltips for critical info deserve aria-label too; this pattern shines for supplementary hints.

The philosophy underneath: data attributes decouple. The PHP template declares facts; JS adds behavior to facts it finds; CSS styles facts it can see. Any of the three can change independently - which is exactly what "maintainable" means in practice.

C
Written by Carlo Fontanos

Full-stack web developer sharing practical tutorials and building tools that ship.

Got something on your mind?

My inbox is open - no forms disappearing into the void here.

  • Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
  • Hire me for a project Have something custom in mind? Let's talk scope and timelines.
  • Product support Bought something here? I'll help you get it running.

I usually reply within 1-2 business days.

Message sent!

Your details are only used to reply to you.

Keep reading