Global Attribute
Custom data-* attributes allow you to store extra information on HTML elements without using non-standard attributes or extra DOM properties. They provide a standardized way to attach custom data to elements that can be easily accessed via JavaScript and CSS.
< element data-name = " value " > Content </ element >
< element data-custom-attribute = " value " > Content </ element >
Rule Description Example Start with data- Required prefix data-id, data-userLowercase Use lowercase letters data-userid (good), data-userID (avoid)Hyphens for multi-word Use hyphens, not camelCase data-user-id, not data-userIdNo uppercase XML/HTML compatibility data-name, not data-NameAlphanumeric + hyphens Only letters, numbers, hyphens data-item-123
Interactive code playground requires JavaScript. Here's the code:
<!DOCTYPE html>
<html>
<head>
<style>
.container { padding: 20px; }
.user-card {
border: 2px solid #e2e8f0;
border-radius: 8px;
padding: 20px;
margin: 15px 0;
background: white;
transition: all 0.3s;
}
.user-card:hover {
border-color: #3b82f6;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.user-info {
display: flex;
gap: 15px;
align-items: center;
}
.avatar {
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 24px;
font-weight: bold;
}
.details h3 { margin: 0 0 5px 0; }
.details p { margin: 0; color: #64748b; }
.actions {
margin-top: 15px;
display: flex;
gap: 10px;
}
button {
padding: 8px 16px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
}
.btn-view {
background: #3b82f6;
color: white;
}
.btn-message {
background: #10b981;
color: white;
}
#output {
background: #f1f5f9;
padding: 15px;
border-radius: 6px;
margin-top: 20px;
font-family: monospace;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<h3>User Cards with Data Attributes</h3>
<div class="user-card"
data-user-id="12345"
data-username="johndoe"
data-email="john@example.com"
data-role="admin"
data-active="true">
<div class="user-info">
<div class="avatar">JD</div>
<div class="details">
<h3>John Doe</h3>
<p>Administrator</p>
</div>
</div>
<div class="actions">
<button class="btn-view" onclick="showUserData(this.closest('.user-card'))">
View Data
</button>
<button class="btn-message">Send Message</button>
</div>
</div>
<div class="user-card"
data-user-id="67890"
data-username="sarahsmith"
data-email="sarah@example.com"
data-role="editor"
data-active="true">
<div class="user-info">
<div class="avatar">SS</div>
<div class="details">
<h3>Sarah Smith</h3>
<p>Editor</p>
</div>
</div>
<div class="actions">
<button class="btn-view" onclick="showUserData(this.closest('.user-card'))">
View Data
</button>
<button class="btn-message">Send Message</button>
</div>
</div>
<div id="output"></div>
</div>
<script>
function showUserData(card) {
const output = document.getElementById('output');
const data = card.dataset;
output.innerHTML = `
<strong>User Data:</strong><br>
ID: ${data.userId}<br>
Username: ${data.username}<br>
Email: ${data.email}<br>
Role: ${data.role}<br>
Active: ${data.active}
`;
}
</script>
</body>
</html>
Interactive code playground requires JavaScript. Here's the code:
<!DOCTYPE html>
<html>
<head>
<style>
.products {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
padding: 20px;
}
.product-card {
border: 2px solid #e2e8f0;
border-radius: 12px;
overflow: hidden;
background: white;
transition: transform 0.2s;
}
.product-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
}
.product-image {
width: 100%;
height: 200px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 48px;
}
.product-info {
padding: 15px;
}
.product-name {
font-weight: bold;
font-size: 18px;
margin: 0 0 5px;
}
.product-price {
color: #3b82f6;
font-size: 20px;
font-weight: bold;
margin: 10px 0;
}
.product-rating {
color: #f59e0b;
margin: 5px 0;
}
.badge {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
margin: 5px 5px 0 0;
}
.badge.sale { background: #fee; color: #b91c1c; }
.badge.new { background: #dcfce7; color: #166534; }
.badge.featured { background: #fef3c7; color: #92400e; }
.add-btn {
width: 100%;
padding: 10px;
background: #3b82f6;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
margin-top: 10px;
}
.add-btn:hover {
background: #2563eb;
}
.filters {
padding: 20px;
background: #f8fafc;
border-radius: 8px;
margin: 20px;
}
.filter-btn {
padding: 8px 16px;
margin: 5px;
border: 2px solid #cbd5e1;
background: white;
border-radius: 6px;
cursor: pointer;
}
.filter-btn.active {
background: #3b82f6;
color: white;
border-color: #3b82f6;
}
</style>
</head>
<body>
<div class="filters">
<strong>Filter by category:</strong><br>
<button class="filter-btn active" onclick="filterProducts('all')">All</button>
<button class="filter-btn" onclick="filterProducts('electronics')">Electronics</button>
<button class="filter-btn" onclick="filterProducts('clothing')">Clothing</button>
<button class="filter-btn" onclick="filterProducts('books')">Books</button>
</div>
<div class="products">
<div class="product-card"
data-product-id="101"
data-name="Wireless Headphones"
data-price="79.99"
data-category="electronics"
data-rating="4.5"
data-stock="15"
data-tags="sale,featured">
<div class="product-image">🎧</div>
<div class="product-info">
<h3 class="product-name">Wireless Headphones</h3>
<div class="product-rating">★★★★☆ (4.5)</div>
<div class="product-price">$79.99</div>
<span class="badge sale">Sale</span>
<span class="badge featured">Featured</span>
<button class="add-btn" onclick="addToCart(this)">Add to Cart</button>
</div>
</div>
<div class="product-card"
data-product-id="102"
data-name="Classic T-Shirt"
data-price="29.99"
data-category="clothing"
data-rating="4.0"
data-stock="50"
data-tags="new">
<div class="product-image">👕</div>
<div class="product-info">
<h3 class="product-name">Classic T-Shirt</h3>
<div class="product-rating">★★★★☆ (4.0)</div>
<div class="product-price">$29.99</div>
<span class="badge new">New</span>
<button class="add-btn" onclick="addToCart(this)">Add to Cart</button>
</div>
</div>
<div class="product-card"
data-product-id="103"
data-name="JavaScript Guide"
data-price="39.99"
data-category="books"
data-rating="5.0"
data-stock="8"
data-tags="featured">
<div class="product-image">📚</div>
<div class="product-info">
<h3 class="product-name">JavaScript Guide</h3>
<div class="product-rating">★★★★★ (5.0)</div>
<div class="product-price">$39.99</div>
<span class="badge featured">Featured</span>
<button class="add-btn" onclick="addToCart(this)">Add to Cart</button>
</div>
</div>
</div>
<script>
function filterProducts(category) {
const products = document.querySelectorAll('.product-card');
const buttons = document.querySelectorAll('.filter-btn');
// Update active button
buttons.forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
// Filter products
products.forEach(product => {
if (category === 'all' || product.dataset.category === category) {
product.style.display = 'block';
} else {
product.style.display = 'none';
}
});
}
function addToCart(button) {
const card = button.closest('.product-card');
const data = card.dataset;
alert(`Added to cart:
Product: ${data.name}
Price: $${data.price}
ID: ${data.productId}
Stock: ${data.stock}`);
}
</script>
</body>
</html>
Interactive code playground requires JavaScript. Here's the code:
<!DOCTYPE html>
<html>
<head>
<style>
.tabs-container {
max-width: 800px;
margin: 20px;
border: 2px solid #e2e8f0;
border-radius: 8px;
overflow: hidden;
}
.tabs {
display: flex;
background: #f8fafc;
border-bottom: 2px solid #e2e8f0;
}
.tab {
flex: 1;
padding: 15px;
text-align: center;
cursor: pointer;
border: none;
background: transparent;
font-size: 16px;
font-weight: bold;
transition: all 0.3s;
}
.tab:hover {
background: #f1f5f9;
}
.tab.active {
background: white;
color: #3b82f6;
border-bottom: 3px solid #3b82f6;
}
.tab-content {
padding: 30px;
display: none;
}
.tab-content.active {
display: block;
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.stat {
display: inline-block;
margin: 10px 20px;
text-align: center;
}
.stat-value {
font-size: 32px;
font-weight: bold;
color: #3b82f6;
}
.stat-label {
color: #64748b;
font-size: 14px;
}
</style>
</head>
<body>
<div class="tabs-container">
<div class="tabs" role="tablist">
<button class="tab active"
data-tab="overview"
onclick="switchTab(this)">
Overview
</button>
<button class="tab"
data-tab="analytics"
onclick="switchTab(this)">
Analytics
</button>
<button class="tab"
data-tab="settings"
onclick="switchTab(this)">
Settings
</button>
</div>
<div class="tab-content active" data-content="overview">
<h2>Overview</h2>
<p>Welcome to your dashboard! Here's a quick summary of your account.</p>
<div class="stat">
<div class="stat-value">1,234</div>
<div class="stat-label">Total Users</div>
</div>
<div class="stat">
<div class="stat-value">567</div>
<div class="stat-label">Active Today</div>
</div>
<div class="stat">
<div class="stat-value">89%</div>
<div class="stat-label">Satisfaction</div>
</div>
</div>
<div class="tab-content" data-content="analytics">
<h2>Analytics</h2>
<p>Detailed analytics and performance metrics.</p>
<div class="stat">
<div class="stat-value">45.2K</div>
<div class="stat-label">Page Views</div>
</div>
<div class="stat">
<div class="stat-value">2.5K</div>
<div class="stat-label">Conversions</div>
</div>
</div>
<div class="tab-content" data-content="settings">
<h2>Settings</h2>
<p>Configure your account preferences.</p>
<label>
<input type="checkbox" checked> Email notifications
</label><br>
<label>
<input type="checkbox"> SMS alerts
</label><br>
<label>
<input type="checkbox" checked> Marketing emails
</label>
</div>
</div>
<script>
function switchTab(button) {
const tabName = button.dataset.tab;
// Remove active class from all tabs and contents
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
});
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
// Add active class to clicked tab
button.classList.add('active');
// Show corresponding content
const content = document.querySelector(`[data-content="${tabName}"]`);
content.classList.add('active');
}
</script>
</body>
</html>
Interactive code playground requires JavaScript. Here's the code:
<!DOCTYPE html>
<html>
<head>
<style>
.status-badges {
padding: 20px;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.badge {
padding: 8px 16px;
border-radius: 6px;
font-weight: bold;
font-size: 14px;
}
/* Style based on data-status attribute */
.badge[data-status="success"] {
background: #dcfce7;
color: #166534;
}
.badge[data-status="warning"] {
background: #fef3c7;
color: #92400e;
}
.badge[data-status="error"] {
background: #fee;
color: #b91c1c;
}
.badge[data-status="info"] {
background: #dbeafe;
color: #1e40af;
}
/* Style based on data-priority */
.task[data-priority="high"] {
border-left: 4px solid #ef4444;
}
.task[data-priority="medium"] {
border-left: 4px solid #f59e0b;
}
.task[data-priority="low"] {
border-left: 4px solid #10b981;
}
.task {
padding: 15px;
margin: 10px 0;
background: white;
border: 1px solid #e2e8f0;
border-radius: 6px;
}
/* Use data attribute content in CSS */
.task::before {
content: attr(data-priority);
text-transform: uppercase;
font-size: 11px;
font-weight: bold;
padding: 2px 6px;
border-radius: 3px;
margin-right: 10px;
}
.task[data-priority="high"]::before {
background: #fee;
color: #b91c1c;
}
.task[data-priority="medium"]::before {
background: #fef3c7;
color: #92400e;
}
.task[data-priority="low"]::before {
background: #dcfce7;
color: #166534;
}
</style>
</head>
<body>
<div style="padding: 20px;">
<h3>Status Badges (CSS via data-status)</h3>
<div class="status-badges">
<span class="badge" data-status="success">Success</span>
<span class="badge" data-status="warning">Warning</span>
<span class="badge" data-status="error">Error</span>
<span class="badge" data-status="info">Info</span>
</div>
<h3 style="margin-top: 30px;">Tasks (CSS via data-priority)</h3>
<div class="task" data-priority="high">
Fix critical security vulnerability
</div>
<div class="task" data-priority="medium">
Update documentation
</div>
<div class="task" data-priority="low">
Refactor CSS styles
</div>
</div>
</body>
</html>
const element = document . getElementById ( ' myElement ' );
const userId = element . dataset . userId ; // data-user-id
const userName = element . dataset . userName ; // data-user-name
const isActive = element . dataset . active ; // data-active
element . dataset . userId = ' 12345 ' ;
element . dataset . userName = ' johndoe ' ;
element . dataset . status = ' online ' ;
// Delete data attributes
delete element . dataset . userId ;
// Check if data attribute exists
if ( ' userId ' in element . dataset ) {
console . log ( ' User ID exists ' );
HTML attributes with hyphens convert to camelCase in JavaScript:
const el = document . querySelector ( ' div ' );
// Hyphenated in HTML → camelCase in JavaScript
console . log ( el . dataset . userId ); // "123"
console . log ( el . dataset . firstName ); // "John"
console . log ( el . dataset . lastName ); // "Doe"
console . log ( el . dataset . isAdmin ); // "true" (string!)
data-settings = ' {"theme":"dark","lang":"en"} ' >
const config = document . getElementById ( ' config ' );
const settings = JSON . parse ( config . dataset . settings );
console . log ( settings . theme ); // "dark"
console . log ( settings . lang ); // "en"
settings . theme = ' light ' ;
config . dataset . settings = JSON . stringify ( settings );
[ data-tags ~= " featured " ] {
[ data-description *= " important " ] {
border : 2 px solid orange ;
/* Display data attribute content */
content : attr ( data-tooltip );
background-image : url ( attr(data-icon ));
<!-- Good: Track UI state -->
< button data-loading = " false " > Submit </ button >
< div data-expanded = " true " > Collapsible content </ div >
< input data-valid = " false " data-error-message = " Required field " >
<!-- Good: Reference other elements or API IDs -->
< button data-target-id = " modal-1 " > Open Modal </ button >
< article data-post-id = " 12345 " data-author-id = " 789 " > ... </ article >
<!-- BAD: Never do this -->
< div data-password = " secret123 " ></ div >
< div data-credit-card = " 4111111111111111 " ></ div >
< div data-api-key = " sk_live_abc123 " ></ div >
<!-- GOOD: Store references only -->
< div data-user-id = " 12345 " ></ div >
<!-- Good: Simple values -->
< div data-count = " 5 " ></ div >
< div data-status = " active " ></ div >
<!-- Okay: JSON for complex data -->
< div data-config = ' {"max":10,"min":0} ' ></ div >
<!-- Bad: Too complex -->
< div data-all-user-data = " ...megabytes of data... " ></ div >
<!-- Good: Clear, descriptive names -->
< button data-action = " delete " data-confirm-message = " Are you sure? " >
< div data-sort-order = " asc " data-sort-field = " name " >
<!-- Bad: Unclear abbreviations -->
< button data-a = " d " data-cm = " R u sure? " >
✓ UI state : Loading, expanded, selected, active
✓ Configuration : Settings, options, parameters
✓ Identifiers : IDs, references, keys (non-sensitive)
✓ Metadata : Author, date, category, tags
✓ Binding : Connect HTML elements to JavaScript logic
< div data-state = " loading " data-progress = " 75 " > ... </ div >
< div data-auto-refresh = " true " data-interval = " 5000 " > ... </ div >
< article data-published = " 2024-01-15 " data-category = " tech " > ... </ article >
✗ Standard attributes exist : Use id, class, title instead
✗ Accessibility : Use ARIA attributes for accessibility
✗ Semantics : Use semantic HTML elements when possible
✗ Large data : Use JavaScript variables for large datasets
✗ Frequently changing data : Use JavaScript state management
<!-- Bad: Standard attributes exist -->
< div data-id = " unique-id " >
<!-- Good: Use id attribute -->
<!-- Bad: Accessibility -->
< button data-role = " button " data-label = " Close " >
< button role = " button " aria-label = " Close " >
// Fast: Cache dataset reference
const dataset = element . dataset ;
const id = dataset . userId ;
const name = dataset . userName ;
// Slower: Multiple DOM queries
const id = element . dataset . userId ;
const name = element . dataset . userName ;
// Fastest: Read once, store in variable
const data = { ...element . dataset } ;
// Bad: Store large arrays in data attributes
element . dataset . items = JSON . stringify ( largeArray ); // Slow!
// Good: Store in JavaScript, reference by ID
element . dataset . dataId = ' 123 ' ;
const data = dataStore [ element . dataset . dataId ];
Caution
Watch out for:
Type coercion : All values are strings
Hyphen conversion : data-user-id becomes dataset.userId
Case sensitivity : Use lowercase in HTML
Visibility : Data is visible in source/DevTools
Performance : Don’t store large amounts of data
Search engines : Data attributes aren’t indexed for SEO
// All dataset values are strings!
element . dataset . count = 5 ;
console . log ( typeof element . dataset . count ); // "string"
console . log ( element . dataset . count === 5 ); // false
console . log ( element . dataset . count === " 5 " ); // true
// Convert types explicitly
const count = parseInt ( element . dataset . count );
const price = parseFloat ( element . dataset . price );
const active = element . dataset . active === ' true ' ;
Universal support across all modern browsers:
Feature Chrome Firefox Safari Edge data-* attributes All All All All dataset API 8+ 6+ 5.1+ 10+
getAttribute() - Generic attribute getter
setAttribute() - Generic attribute setter
hasAttribute() - Check if attribute exists
removeAttribute() - Remove attribute
// Using dataset API (preferred)
element . dataset . userId = ' 123 ' ;
// Using attribute methods (also works)
element . setAttribute ( ' data-user-id ' , ' 123 ' );
element . getAttribute ( ' data-user-id ' );
element . hasAttribute ( ' data-user-id ' );
element . removeAttribute ( ' data-user-id ' );