HTML Attributes
Attributes provide additional information about HTML elements. They appear inside the opening tag as name-value pairs and control element behavior, appearance, and semantics. Every HTML element can have attributes, and they are essential for building interactive, accessible, and well-structured web pages.
HTML5 introduced several new attributes and standardized many that were previously informal. Understanding the full set of available attributes — from global ones that work on every element to element-specific ones — is fundamental to writing correct, maintainable markup.
Attributes always appear in the opening tag of an element. The general syntax uses an attribute name followed by an equals sign and a quoted value. Boolean attributes can be written without a value:
| 1 | <!-- Standard attribute with a value --> |
| 2 | <a href="https://example.com" target="_blank">Visit</a> |
| 3 | |
| 4 | <!-- Attribute with no value needed (boolean) --> |
| 5 | <input type="text" disabled> |
| 6 | |
| 7 | <!-- Single-quoted values are also valid --> |
| 8 | <img src='image.png' alt='A photo'> |
| 9 | |
| 10 | <!-- Attribute names are case-insensitive in HTML5, but always use lowercase --> |
| 11 | <IMG SRC="logo.png" ALT="Logo"> <!-- valid but discouraged --> |
| 12 | |
| 13 | <!-- Self-closing elements still use attributes --> |
| 14 | <br /> |
| 15 | <img src="photo.jpg" alt="Photo" width="300" /> |
| 16 | <input type="email" name="user-email" /> |
| 17 |
info
Global attributes can be used on any HTML element. They provide common functionality like identification, styling hooks, event handlers, and accessibility metadata. The most important global attributes are:
| Attribute | Purpose | Example |
|---|---|---|
| class | Assigns CSS classes for styling and selection | class="btn primary" |
| id | Unique identifier for the element | id="main-nav" |
| style | Inline CSS styles | style="color: red" |
| title | Advisory information (tooltip on hover) | title="Close dialog" |
| lang | Language of the element's content | lang="es" |
| dir | Text direction (ltr, rtl, auto) | dir="rtl" |
| tabindex | Tab order for keyboard navigation | tabindex="0" |
| contenteditable | Makes element content editable | contenteditable="true" |
| draggable | Enables drag-and-drop | draggable="true" |
| hidden | Removes element from rendering | hidden |
| translate | Whether content should be translated | translate="no" |
| spellcheck | Enable browser spell checking | spellcheck="false" |
| 1 | <!-- class: multiple CSS classes separated by spaces --> |
| 2 | <div class="card card--dark card--featured"> |
| 3 | <h2 class="card__title">Featured Card</h2> |
| 4 | </div> |
| 5 | |
| 6 | <!-- id: must be unique per page --> |
| 7 | <section id="pricing"> |
| 8 | <h2>Pricing</h2> |
| 9 | </section> |
| 10 | |
| 11 | <!-- style: inline CSS (avoid in production) --> |
| 12 | <p style="color: #00FF41; font-weight: bold;">Highlighted text</p> |
| 13 | |
| 14 | <!-- lang: changes language context --> |
| 15 | <p lang="fr">Ceci est un paragraphe en français.</p> |
| 16 | |
| 17 | <!-- dir: text direction for RTL languages --> |
| 18 | <p dir="rtl" lang="ar">هذا نص بالعربية</p> |
| 19 | |
| 20 | <!-- tabindex: keyboard navigation order --> |
| 21 | <div tabindex="0" role="button">Focusable element</div> |
| 22 | |
| 23 | <!-- contenteditable: inline editing --> |
| 24 | <div contenteditable="true" spellcheck="false"> |
| 25 | Click here and start typing... |
| 26 | </div> |
| 27 | |
| 28 | <!-- hidden: removes from DOM rendering --> |
| 29 | <div hidden>This content is not displayed</div> |
| 30 | |
| 31 | <!-- title: tooltip on hover --> |
| 32 | <abbr title="HyperText Markup Language">HTML</abbr> |
best practice
The class and id attributes are the most frequently used global attributes. Classes are reusable and support multiple values, while IDs are unique and used for anchoring, JavaScript targeting, and ARIA references:
| 1 | <!-- Class: multiple classes, space-separated, reusable --> |
| 2 | <button class="btn btn--primary btn--large">Submit</button> |
| 3 | <button class="btn btn--secondary">Cancel</button> |
| 4 | <button class="btn btn--ghost" disabled>Disabled</button> |
| 5 | |
| 6 | <!-- BEM naming convention --> |
| 7 | <div class="sidebar sidebar--collapsed"> |
| 8 | <nav class="sidebar__nav"> |
| 9 | <a class="sidebar__link sidebar__link--active" href="/dashboard"> |
| 10 | Dashboard |
| 11 | </a> |
| 12 | <a class="sidebar__link" href="/settings">Settings</a> |
| 13 | </nav> |
| 14 | </div> |
| 15 | |
| 16 | <!-- ID: unique per page, used for anchors and JS --> |
| 17 | <header id="main-header"> |
| 18 | <nav id="primary-nav"> |
| 19 | <a href="#pricing">Jump to Pricing</a> |
| 20 | <a href="#contact">Jump to Contact</a> |
| 21 | </nav> |
| 22 | </header> |
| 23 | |
| 24 | <!-- ARIA: id referenced by aria-labelledby, aria-describedby --> |
| 25 | <section aria-labelledby="section-title"> |
| 26 | <h2 id="section-title">Welcome</h2> |
| 27 | <p id="section-desc">This section contains important information.</p> |
| 28 | <div aria-labelledby="section-title" aria-describedby="section-desc"> |
| 29 | Content with explicit ARIA relationships |
| 30 | </div> |
| 31 | </section> |
| 32 | |
| 33 | <!-- Skip navigation link using id anchor --> |
| 34 | <a href="#main-content" class="skip-link">Skip to content</a> |
| 35 | <main id="main-content"> |
| 36 | <p>Main page content here</p> |
| 37 | </main> |
warning
Boolean attributes represent a state that is either true or false. When the attribute is present, the value is true. When absent, it is false. You do not need to specify a value — the presence of the attribute name alone is sufficient:
| 1 | <!-- Correct: presence = true, absence = false --> |
| 2 | <input type="text" disabled> |
| 3 | <button type="submit" disabled>Submit</button> |
| 4 | |
| 5 | <!-- Also valid but redundant — same as above --> |
| 6 | <input type="text" disabled="disabled"> |
| 7 | <input type="text" disabled="true"> |
| 8 | |
| 9 | <!-- Common boolean attributes --> |
| 10 | <a href="/page" target="_blank" rel="noopener">External Link</a> |
| 11 | <img src="photo.jpg" alt="Photo" loading="lazy" decoding="async"> |
| 12 | <script src="app.js" defer></script> |
| 13 | <script src="analytics.js" async></script> |
| 14 | <input type="file" multiple accept="image/*"> |
| 15 | <form novalidate> |
| 16 | <input type="email" required> |
| 17 | <input type="text" readonly value="Fixed value"> |
| 18 | </form> |
| 19 | <video controls autoplay muted loop poster="thumb.jpg"> |
| 20 | <source src="video.mp4" type="video/mp4"> |
| 21 | </video> |
| 22 | <details open> |
| 23 | <summary>Click to collapse</summary> |
| 24 | <p>This content is visible by default.</p> |
| 25 | </details> |
| 26 | <option selected>Pick me</option> |
| 27 | <input type="text" autocomplete="off" autofocus> |
| 28 | <iframe sandbox="allow-scripts" allowfullscreen></iframe> |
| Attribute | Elements | Purpose |
|---|---|---|
| disabled | input, button, select, textarea | Prevents interaction |
| required | input, select, textarea | Field must be filled before submit |
| readonly | input, textarea | User cannot modify value |
| checked | input (checkbox, radio) | Pre-selects the control |
| multiple | input, select | Allows multiple values |
| autoplay | audio, video | Starts playback immediately |
| loop | audio, video | Repeats when reaching the end |
| muted | audio, video | Audio starts muted |
| controls | audio, video | Shows browser default controls |
| defer | script | Executes after DOM parsing |
| async | script | Executes as soon as loaded |
| novalidate | form | Disables browser validation |
| autofocus | input, select, textarea, button | Gains focus on page load |
best practice
Custom data attributes let you store extra information on any HTML element using the data-* naming convention. They are accessible from JavaScript via the dataset property and from CSS via attribute selectors:
| 1 | <!-- Data attributes: data-* naming convention --> |
| 2 | <div |
| 3 | class="product-card" |
| 4 | data-product-id="42" |
| 5 | data-category="electronics" |
| 6 | data-price="299.99" |
| 7 | data-in-stock="true" |
| 8 | > |
| 9 | <h3>Wireless Headphones</h3> |
| 10 | <p>$299.99</p> |
| 11 | <button data-action="add-to-cart" data-quantity="1"> |
| 12 | Add to Cart |
| 13 | </button> |
| 14 | </div> |
| 15 | |
| 16 | <!-- Complex data stored as JSON strings --> |
| 17 | <div |
| 18 | data-config='{"theme":"dark","lang":"en","analytics":true}' |
| 19 | data-permissions='["read","write","admin"]' |
| 20 | ></div> |
| 21 | |
| 22 | <!-- Testing and analytics selectors --> |
| 23 | <button data-testid="checkout-button" data-track-click="checkout"> |
| 24 | Checkout |
| 25 | </button> |
| 26 | |
| 27 | <!-- State tracking for UI components --> |
| 28 | <div class="tabs" data-active-tab="overview"> |
| 29 | <button data-tab="overview">Overview</button> |
| 30 | <button data-tab="details">Details</button> |
| 31 | <button data-tab="reviews">Reviews</button> |
| 32 | </div> |
| 1 | // Accessing data attributes in JavaScript |
| 2 | const card = document.querySelector('.product-card'); |
| 3 | |
| 4 | // Via dataset property (camelCase conversion) |
| 5 | console.log(card.dataset.productId); // "42" |
| 6 | console.log(card.dataset.category); // "electronics" |
| 7 | console.log(card.dataset.price); // "299.99" |
| 8 | console.log(card.dataset.inStock); // "true" (string, not boolean!) |
| 9 | |
| 10 | // Parse values appropriately |
| 11 | const price = parseFloat(card.dataset.price); |
| 12 | const inStock = card.dataset.inStock === 'true'; |
| 13 | const config = JSON.parse(card.dataset.config); |
| 14 | |
| 15 | // Via getAttribute (always returns string or null) |
| 16 | console.log(card.getAttribute('data-product-id')); // "42" |
| 17 | console.log(card.getAttribute('data-missing')); // null |
| 18 | |
| 19 | // Modify data attributes |
| 20 | card.dataset.price = "199.99"; |
| 21 | card.dataset.lastUpdated = new Date().toISOString(); |
ARIA (Accessible Rich Internet Applications) attributes make web content more accessible to assistive technologies like screen readers. There are two types: roles (via the role attribute) and properties/states (via aria-* attributes):
| 1 | <!-- ARIA roles: define what an element is --> |
| 2 | <div role="button" tabindex="0" onclick="submit()">Submit</div> |
| 3 | <nav role="navigation" aria-label="Main menu"> |
| 4 | <ul> |
| 5 | <li><a href="/">Home</a></li> |
| 6 | <li><a href="/about">About</a></li> |
| 7 | </ul> |
| 8 | </nav> |
| 9 | |
| 10 | <!-- ARIA properties: describe element semantics --> |
| 11 | <div role="alert" aria-live="polite" aria-atomic="true"> |
| 12 | Your changes have been saved. |
| 13 | </div> |
| 14 | |
| 15 | <!-- ARIA states: describe current condition --> |
| 16 | <button |
| 17 | aria-expanded="false" |
| 18 | aria-controls="dropdown-menu" |
| 19 | onclick="toggleMenu(this)" |
| 20 | > |
| 21 | Options |
| 22 | </button> |
| 23 | <ul id="dropdown-menu" role="menu" hidden> |
| 24 | <li role="menuitem">Edit</li> |
| 25 | <li role="menuitem">Delete</li> |
| 26 | </ul> |
| 27 | |
| 28 | <!-- Common ARIA patterns --> |
| 29 | <div role="tablist" aria-label="Content tabs"> |
| 30 | <button role="tab" aria-selected="true" aria-controls="panel-1"> |
| 31 | Tab 1 |
| 32 | </button> |
| 33 | <button role="tab" aria-selected="false" aria-controls="panel-2"> |
| 34 | Tab 2 |
| 35 | </button> |
| 36 | </div> |
| 37 | <div role="tabpanel" id="panel-1" aria-labelledby="tab-1"> |
| 38 | <p>Tab 1 content</p> |
| 39 | </div> |
| 40 | |
| 41 | <!-- Progress indicator --> |
| 42 | <progress aria-label="Upload progress" value="70" max="100"></progress> |
| 43 | |
| 44 | <!-- Labeling for screen readers --> |
| 45 | <label for="email-input">Email address</label> |
| 46 | <input |
| 47 | id="email-input" |
| 48 | type="email" |
| 49 | aria-describedby="email-hint email-error" |
| 50 | aria-invalid="false" |
| 51 | required |
| 52 | > |
| 53 | <span id="email-hint">We never share your email.</span> |
| 54 | <span id="email-error" role="alert" hidden>Please enter a valid email.</span> |
info
Event handler attributes allow you to execute JavaScript in response to events directly in HTML. While inline handlers are generally discouraged in favor of addEventListener, they are useful for simple interactions and prototyping:
| 1 | <!-- Inline event handlers --> |
| 2 | <button onclick="alert('Hello!')">Click Me</button> |
| 3 | <input type="text" onfocus="this.style.borderColor='#00FF41'"> |
| 4 | <input type="text" onblur="this.style.borderColor=''"> |
| 5 | <img src="photo.jpg" onerror="this.src='/placeholder.png'"> |
| 6 | <form onsubmit="return handleSubmit(event)"> |
| 7 | <input type="text" name="q" required> |
| 8 | <button type="submit">Search</button> |
| 9 | </form> |
| 10 | |
| 11 | <!-- Common event handler attributes --> |
| 12 | <input oninput="validateField(this)" type="email"> |
| 13 | <select onchange="updatePreview(this.value)"> |
| 14 | <option>Option 1</option> |
| 15 | </select> |
| 16 | <div onmouseenter="highlight(this)" onmouseleave="unhighlight(this)"> |
| 17 | Hover me |
| 18 | </div> |
| 19 | <body onkeydown="handleKeyboard(event)"></body> |
| 20 | |
| 21 | <!-- Better pattern: addEventListener in JS --> |
| 22 | <button id="action-btn">Action</button> |
| 23 | <script> |
| 24 | document.getElementById('action-btn').addEventListener('click', () => { |
| 25 | alert('This is the preferred approach.'); |
| 26 | }); |
| 27 | </script> |
warning
HTML5 allows any attribute on any element, but non-standard attributes should follow the data- prefix convention. Attributes without the data- prefix that are not part of the HTML specification will cause validator warnings, though browsers will still render them:
| 1 | <!-- Standard: data-* attributes for custom data --> |
| 2 | <div data-tooltip="Click to expand" data-tooltip-position="top"> |
| 3 | Hoverable card |
| 4 | </div> |
| 5 | |
| 6 | <!-- Non-standard: works but generates validator warnings --> |
| 7 | <div custom-attr="value"> |
| 8 | This works but is not valid HTML |
| 9 | </div> |
| 10 | |
| 11 | <!-- Useful custom attribute patterns --> |
| 12 | <div data-controller="dropdown" data-action="click->dropdown#toggle"> |
| 13 | Interactive component |
| 14 | </div> |
| 15 | |
| 16 | <div data-react-component="PriceDisplay" data-props='{"price":29.99,"currency":"USD"}'> |
| 17 | React mount point |
| 18 | </div> |
| 19 | |
| 20 | <!-- Framework-specific data attributes --> |
| 21 | <div |
| 22 | data-testid="user-profile" |
| 23 | data-cy="profile-section" |
| 24 | data-qa="user-info" |
| 25 | > |
| 26 | <h2>Profile</h2> |
| 27 | </div> |
| 28 | |
| 29 | <!-- Validation configuration via data attributes --> |
| 30 | <input |
| 31 | type="text" |
| 32 | data-validate="required|minLength:3|maxLength:50|pattern:^[a-zA-Z]+$" |
| 33 | data-error-message="Please enter 3-50 letters only" |
| 34 | name="username" |
| 35 | > |
info
The following demo shows global and boolean attributes in action. Click the button to toggle the disabled state, and hover over elements to see title tooltips:
All global attributes discussed on this page are supported in every modern browser. The following table covers the more advanced attributes:
| Attribute | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| data-* | ✓ | ✓ | ✓ | ✓ |
| contenteditable | ✓ | ✓ | ✓ | ✓ |
| draggable | ✓ | ✓ | ✓ | ✓ |
| spellcheck | ✓ | ✓ | ✓ | ✓ |
| tabindex | ✓ | ✓ | ✓ | ✓ |
| ARIA attributes | ✓ | ✓ | ✓ | ✓ |