Attributes, Values and the Boolean Kind
An attribute is extra information attached to an element, written inside the opening tag only and never in the closing tag. Most are written as name="value", separated from the tag name and from each other by spaces. An element can carry as many as it needs, and their order makes no difference.
Quoting is technically optional in some cases and you should ignore that entirely — always use double quotes. An unquoted value breaks the moment it contains a space, and the failure is not obvious: class=my card gives the element a class of my and an attribute called card. Quoting everything costs two characters and removes the whole category of problem.
Some attributes take no value at all. These are boolean attributes, and their mere presence means true — required, checked, disabled, readonly, multiple, hidden, reversed, autofocus. The trap here catches people who come from programming languages: writing disabled="false" does not enable the element. The value is ignored entirely; the attribute is present, so the element is disabled. To turn it off, remove the attribute.
Attribute names are case-insensitive, but the same convention applies as for tags: write them in lowercase. Their values are a different matter and are often case-sensitive, particularly ids, class names and file paths.
<!-- Several attributes on one element, order irrelevant -->
<img src="images/robot.jpg"
alt="Line-following robot on the test track"
width="800"
height="600"
loading="lazy">
<!-- Boolean attributes: presence is the whole message -->
<input type="email" name="email" required>
<input type="checkbox" name="terms" checked>
<button type="submit" disabled>Submit</button>
<!-- Does NOT enable the button — remove the attribute instead -->
<button type="submit" disabled="false">Submit</button>
<!-- Unquoted values break silently -->
<div class=my card>...</div> <!-- class is "my"; "card" became an attribute -->
<div class="my card">...</div> <!-- two classes, as intended --> - A disabled form control is skipped by the keyboard and is not submitted, so its value never reaches the server. If you want a field to be visible and submitted but not editable, the attribute you want is
readonly, notdisabled.
Global Attributes: Usable on Any Element
Most attributes belong to one element — href only makes sense on a link, src only on things that load a file. A smaller set, called global attributes, can be put on any element at all. These are the ones you will use constantly.
id and class are the two you will type most often, and they get their own section below. style applies CSS directly to one element. title produces a hover tooltip. lang declares the language of an element's content, which is useful on individual phrases as well as on <html>. hidden hides an element completely — not merely visually, so screen readers skip it too.
tabindex controls keyboard focus and deserves a warning. tabindex="0" puts an element into the natural tab order at its position in the document, which is how you make a custom control reachable. tabindex="-1" makes it focusable by script but not by Tab. Positive values such as tabindex="3" jump the element to the front of the entire page's tab order, which almost always creates a confusing sequence — avoid them and fix your source order instead.
<!-- Global attributes work on any element -->
<p id="intro" class="lead highlight" lang="en">Welcome to the club.</p>
<span lang="hi">namaste</span>
<p hidden>Not shown, and not read aloud either.</p>
<!-- Making a custom control keyboard-reachable -->
<div class="custom-control" tabindex="0" role="button">Toggle</div>
<!-- Avoid: this element now jumps ahead of everything else -->
<input type="text" tabindex="5"> id— a unique name for one element on the pageclass— one or more shared names, separated by spacesstyle— CSS applied inline to this element onlytitle— hover tooltip text; unreliable, never the only place information liveslang— the language of this element's contenthidden— removes the element from the page and from assistive technologytabindex— keyboard focus order; use0or-1, never positive numbersdata-*— your own custom data, described later in this lesson
id and class: What Actually Separates Them
id identifies one specific element. It must be unique across the whole page — no two elements may share it. class labels a kind of element, and any number of elements may carry the same class. That is the whole difference, and every practical consequence follows from it.
Because an id is unique, it can be a target. A link ending in #registration scrolls to the element with id="registration". A <label for="email"> finds the input with id="email". JavaScript's getElementById returns exactly one element. None of that works if you duplicate an id, and the failure is silent: the browser simply uses the first match, so your label points at the wrong field and your anchor link jumps to the wrong place, with no error anywhere.
Because a class is shared, it is the right tool for styling. Every project card on your page gets class="card" and one CSS rule styles all of them. An element can hold several classes separated by spaces — class="card featured" gives it both the shared card styling and an extra modifier — which is how component-based CSS is built.
The practical rule most projects settle on: use class for all styling, and reserve id for the things that genuinely need a unique target — form labels, anchor destinations, and elements a script must find. Styling by id works, but id selectors are much harder to override in CSS than class selectors, which turns into a real nuisance on any page bigger than a few screens.
<!-- id: unique, and used as a target -->
<h2 id="registration">Registration</h2>
<a href="#registration">Jump to registration</a>
<label for="email">Email</label>
<input type="email" id="email" name="email">
<!-- class: shared, and used for styling -->
<article class="card">...</article>
<article class="card featured">...</article>
<article class="card">...</article>
/* One rule styles every card; a second adds the modifier */
.card { border: 1px solid #ddd; padding: 16px; border-radius: 8px; }
.featured { border-color: #7a0060; }
<!-- Broken: two elements with the same id -->
<input id="email" name="personal_email">
<input id="email" name="college_email">
<!-- the label above now points at the first one, whichever you meant --> - Class and id values are case-sensitive, so
class="Card"andclass="card"are two different classes. Stick to lowercase with hyphens —project-card,error-message— and this never bites you.
The style Attribute, and Why to Avoid It
The style attribute lets you write CSS directly on an element. It works, it is occasionally the right answer, and using it as your normal way of styling will make a mess of your project.
There are three concrete problems. First, no reuse: styling twenty cards means writing the same declarations twenty times, and changing them means twenty edits. Second, inline styles beat almost everything in CSS's specificity rules, so a stylesheet rule cannot override one without !important — you have made your own future styling harder. Third, an inline style cannot contain a media query or a hover state, so it cannot respond to screen size or interaction at all.
There is also a maintenance cost that is easy to underestimate: styles scattered through your markup cannot be found by searching one file, so nobody, including you, can answer the question "where does this colour come from?" without reading the whole page.
Where it is genuinely reasonable: a value computed at run time by JavaScript, such as a progress bar's width; a one-off demonstration; and HTML email, where external stylesheets are unreliable and inline styles are the standard approach. Outside those, put it in a stylesheet.
<!-- Avoid: repeated, unoverridable, cannot respond to screen size -->
<div style="border: 1px solid #ddd; padding: 16px; border-radius: 8px;">...</div>
<div style="border: 1px solid #ddd; padding: 16px; border-radius: 8px;">...</div>
<!-- Prefer: one class, one rule, one place to change it -->
<div class="card">...</div>
<div class="card">...</div>
.card {
border: 1px solid #ddd;
padding: 16px;
border-radius: 8px;
}
/* And a class can do things an inline style cannot */
@media (max-width: 600px) {
.card { padding: 10px; }
}
<!-- Reasonable: a value only known at run time -->
<div class="progress-bar" style="width: 62%"></div> - The same argument applies to the old
onclick,onmouseoverand similar attributes. They work, but they scatter behaviour through your markup. Attaching handlers from a JavaScript file withaddEventListenerkeeps structure and behaviour in separate places.
Custom Data with data-* Attributes
Sometimes an element needs to carry a piece of information that has no standard attribute — a product's id, a card's category, a countdown's target date. Inventing your own attribute name makes the document invalid, and stashing the value in a class is a hack that falls apart quickly.
HTML5 provides data-* attributes for exactly this. Any attribute whose name begins with data- is valid, is ignored by the browser's own behaviour, and is available to your JavaScript through the element's dataset property. A hyphenated name becomes camelCase there: data-project-id is read as element.dataset.projectId.
Two limits worth knowing. The value is always a string, so a number read from dataset needs converting before you do arithmetic with it. And data-* is for data your own scripts use — it is not read by screen readers and carries no meaning to anyone else, so information the visitor needs must live in the visible content, not only in an attribute.
<article class="card"
data-project-id="118"
data-category="hardware"
data-year="2026">
<h3>Line-following robot</h3>
</article>
<script>
const card = document.querySelector('.card');
card.dataset.projectId; // "118" (a string, note the camelCase)
card.dataset.category; // "hardware"
Number(card.dataset.year) + 1; // 2027
</script>
/* CSS can read them too */
.card[data-category="hardware"] {
border-left: 4px solid #7a0060;
} - Do not store anything sensitive in a
data-*attribute. It is plain text in the page source, visible to anyone who opens View Source or Developer Tools, exactly like the rest of your HTML.
