Elements and Attributes
How HTML elements work, what attributes are, and how to read and write them correctly.
HTML communicates with the browser through two mechanisms: elements and attributes. Elements define what a piece of content is. Attributes provide additional information about it. Understanding both is necessary before you can write any meaningful HTML.
Elements
An HTML element is the combination of an opening tag, content, and a closing tag.
<p>This is a paragraph.</p><p>is the opening tag. It tells the browser "a paragraph starts here."This is a paragraph.is the content.</p>is the closing tag. It tells the browser "the paragraph ends here."
The tag name (p, h1, a, etc.) is what the browser reads to understand the type of content. It determines how the browser renders the element, what keyboard behaviour it gets, and what role it has for assistive technology.
What happens when you forget the closing tag
The browser does not throw an error when you forget a closing tag. It tries to fix the problem. For some elements, the repair is harmless. For others, it breaks the layout in ways that are difficult to diagnose.
<!-- Missing closing </p> tags -->
<div>
<p>First paragraph
<p>Second paragraph
<p>Third paragraph
</div>Browsers typically handle unclosed <p> tags because the spec defines when a <p> implicitly closes (when the next block element starts). But consider a different case:
<ul>
<li>Item one
<li>Item two
<!-- The browser closes the first <li> when it sees the second <li> -->
<!-- This happens to work for simple lists -->
</ul>
<!-- This one does not work out cleanly: -->
<div>
<div>
Column one
</div>
<!-- Missing </div> for outer div -->
<div>Column two</div>
</div>With a missing </div>, the browser tries to figure out where the element ends. The result depends on what follows, and it often does not match what you intended. Two columns collapse into one. A sidebar absorbs the main content. The visual output may look fine in one browser and broken in another.
Write closing tags. The browser's repair mechanism is not a substitute for correct HTML.
Block and inline elements
HTML elements fall into two display categories. Understanding these two categories explains why some elements start on a new line and others flow with text.
Block elements start on a new line and take up the full available width. They create a visible break in the flow of content. Examples: <div>, <p>, <h1> through <h6>, <ul>, <ol>, <li>, <section>, <article>, <main>, <header>, <footer>.
Inline elements flow within the surrounding text. They do not start on a new line and only take up as much width as their content. Examples: <span>, <a>, <strong>, <em>, <img>, <button>.
<!-- Block elements: each starts on its own line -->
<p>This is a paragraph.</p>
<p>This is a second paragraph.</p>
<!-- Inline elements: flow within a paragraph -->
<p>This text is <strong>bold</strong> and this is <em>italic</em>.</p>The wrong nesting of block and inline causes layout problems. Block elements inside inline elements are invalid HTML:
<!-- Invalid: a block element inside an inline element -->
<span>
<p>This is wrong. A block element cannot live inside a span.</p>
</span>
<!-- Valid: inline elements inside a block element -->
<p>
<span>This is fine.</span>
</p>Browsers often try to repair invalid nesting by pulling the block element out, which changes the structure in surprising ways.
Void elements
Some elements have no content. There is nothing to put between opening and closing tags because they represent self-contained things: an image, an input field, a line break, a horizontal rule.
These are called void elements. You do not write a closing tag for them.
<img src="profile.jpg" alt="A photo of the author" />
<input type="email" />
<br />
<hr />
<meta charset="UTF-8" />
<link rel="stylesheet" href="styles.css" />The trailing slash (/>) is optional in HTML5. Both forms are valid:
<img src="photo.jpg" alt="Mountain at sunrise">
<img src="photo.jpg" alt="Mountain at sunrise" />Pick one style and use it consistently throughout a project. The slash form makes the self-closing nature explicit, which is useful when you are learning and when working alongside developers from XML backgrounds.
What happens if you try to add content to a void element
The browser ignores any attempt to put content inside a void element. The closing tag is treated as stray markup, not as a pair with the opening tag.
<!-- This does not work. The "Caption" text is not inside the img. -->
<img src="photo.jpg" alt="Mountain">Caption here</img>The browser sees </img> as an unknown closing tag and discards it. The text "Caption here" ends up as loose text outside the image. Use <figure> and <figcaption> instead (covered in the Images lesson).
Attributes
Attributes are name-value pairs written inside the opening tag. They provide additional information about the element.
<a href="https://example.com" target="_blank">Visit example.com</a>Here href and target are attribute names. "https://example.com" and "_blank" are their values. Attribute values go inside quotes. Both single and double quotes are valid, but double quotes are the standard.
Attributes only ever appear in the opening tag, not in the closing tag.
<!-- Wrong: attribute on closing tag -->
<a href="https://example.com">Link</a href="https://example.com">
<!-- Correct -->
<a href="https://example.com">Link</a>Boolean attributes
Some attributes do not take a value. They are either present or absent. Their presence means true. Their absence means false. These are called boolean attributes.
<input type="text" required />
<button disabled>Submit</button>
<input type="checkbox" checked />There are three equally valid ways to write a boolean attribute:
<!-- All three are identical: the input is disabled -->
<input disabled>
<input disabled="">
<input disabled="disabled">The value is ignored. The browser only checks whether the attribute exists.
This catches many beginners. The following does not enable the input:
<!-- Wrong: this still disables the input. -->
<input disabled="false">disabled="false" has the disabled attribute present with a value of "false". The string "false" is irrelevant. The attribute's presence is all that matters. To enable the input, remove the attribute entirely:
<!-- Correct: no disabled attribute means the input is enabled -->
<input type="text">The id attribute
The id attribute gives an element a unique identifier on the page. No two elements on the same page may share the same id.
<section id="about">
<h2>About This Project</h2>
<p>...</p>
</section>Three things use id values:
In-page links. An anchor tag with href="#about" scrolls the browser to the element with id="about".
<a href="#about">Jump to About section</a>
<!-- Somewhere further down the page: -->
<section id="about">...</section>Form label associations. A <label> element uses for to point at the id of its input. This is covered in detail in the Forms lesson, but it is important: clicking a label focuses the associated input, and screen readers announce the label text when the input is focused.
<label for="email-address">Email address</label>
<input id="email-address" type="email" />JavaScript. document.getElementById('about') returns the element with that id.
What happens when two elements share an id
The browser does not throw an error for duplicate IDs. It silently uses the first matching element and ignores the rest.
<section id="pricing">Monthly plans</section>
<!-- ... a lot of content ... -->
<section id="pricing">Annual plans</section>A link like <a href="#pricing"> scrolls to the first section. The second section is unreachable via the fragment link. A label for="pricing" associates with the first input that has that id. document.getElementById('pricing') returns the first match.
Duplicate IDs cause silent, intermittent bugs. Because the browser does not warn you, they can go unnoticed until a user reports that a link scrolls to the wrong place.
The class attribute
The class attribute marks an element with one or more labels. Unlike id, multiple elements can share the same class, and one element can have multiple classes.
<article class="card">...</article>
<article class="card featured">...</article>
<article class="card">...</article>Classes are the primary mechanism for targeting elements with CSS. The CSS selector .card matches any element with card in its class list. .featured matches the second article specifically.
An element with multiple classes has them space-separated in the attribute value:
<button class="btn btn-primary large">Submit</button>Order does not matter for CSS matching, but many teams use a consistent order (base class first, modifier classes after) for readability.
The data-* attribute
HTML allows you to store custom data on any element using the data-* attribute pattern. Replace * with any name you choose.
<ul>
<li data-user-id="291" data-role="admin">Alice</li>
<li data-user-id="308" data-role="member">Bob</li>
<li data-user-id="415" data-role="member">Carol</li>
</ul>The browser ignores data-* attributes when rendering. They are invisible to the user. JavaScript can read them via the dataset property:
const listItem = document.querySelector('[data-user-id="291"]');
console.log(listItem.dataset.userId); // "291"
console.log(listItem.dataset.role); // "admin"Notice the naming: data-user-id in HTML becomes dataset.userId in JavaScript. Hyphens in the attribute name are converted to camelCase in the dataset object.
data-* attributes are useful when you need to pass information from server-rendered HTML to JavaScript without using hidden form inputs or separate API calls. A product listing rendered by the server can include data-product-id, data-price, and data-in-stock on each card. A single JavaScript event handler can then read whatever it needs directly from the element.
<button
class="add-to-cart"
data-product-id="sku-4892"
data-product-name="Wireless Headphones"
data-price="79.99"
>
Add to cart
</button>Do not use data-* attributes for accessibility information or for data that CSS needs to display. There are proper attributes for those purposes.
Common mistakes
Typos in attribute names. The browser silently ignores unknown attributes. If you write href as herf, the link will not work, and no error will appear. Always check spelling when a link, input, or image is not behaving as expected.
<!-- Wrong: typo in attribute name, link silently broken -->
<a herf="https://example.com">Broken link</a>
<!-- Correct -->
<a href="https://example.com">Working link</a>Using id on multiple elements. Browsers silently use the first match. Everything after it is unreachable through the id.
Confusing id and class. id is for uniquely identifying one element. class is for grouping elements that share a style or behaviour. If you find yourself giving the same id to multiple elements, you need a class.
Boolean attribute with a "false" value. The presence of the attribute is true. To turn it off, remove it.
<!-- Wrong: still disabled -->
<button disabled="false">Click me</button>
<!-- Correct: remove the attribute -->
<button>Click me</button>Unquoted attribute values. Unquoted values work if the value contains no spaces or special characters, but one space breaks the parse:
<!-- Wrong: everything after the space is treated as a separate attribute -->
<img alt=A photo of the author>
<!-- Correct -->
<img alt="A photo of the author">Exercise
Build a section of a product page using what you have learned. The task has three parts.
- Create a
<section>element withid="headphones"anddata-category="audio". Inside it, add an<h2>heading with the product name. - Add an
<img>with a placeholder image (src="https://placehold.co/300x200") and descriptive alt text. Give the image the classproduct-image. - Add two
<p>elements with different classes (descriptionandprice). Put some product description text in the first and a price in the second.