Topic 04

Classes, Attributes and Data

JavaScriptCSS

Say a script should open a panel of extra details. It could set the panel's display, its spacing and its border one by one from JavaScript. There is a cleaner way. Write what an open panel looks like in the stylesheet, as a rule for a class, and let the script do nothing but add or remove that class. The look stays in CSS, where the rest of the design lives, and the script only decides when.

A light switch is the model. The wiring is already in the walls, and the switch only closes or opens the circuit. The CSS rule is the wiring; adding the class is flipping the switch. Unlike a real switch, an element can carry several classes at once, so a script can flip several independent switches on the same element.

classList: Flipping the Switch

Every element has a classList property with four methods you will use constantly. add puts a class on, remove takes it off, toggle adds it if it is missing and removes it if it is there, and contains answers true or false. Other classes on the element are left alone.

To try it, add a small details panel and its button to the main of index.html, and two rules to the stylesheet. The first rule hides the panel; the second shows it whenever it also has the class is-open. This is practice, so take the button, the panel and both rules out again when you finish the topic.

index.html
<button type="button" class="details-toggle">Show details</button>
<div class="details">
  <p>Built with HTML, CSS and a little JavaScript.</p>
</div>
css/style.css
.details {
  display: none;
}

.details.is-open {
  display: block;
}

Save both and the page shows the button but no panel, because the first rule hides it. The button does nothing yet: answering clicks is the next chapter's subject. For now the Console plays the part of the button.

Console
const details = document.querySelector(".details");
details.classList.contains("is-open")
details.classList.toggle("is-open")
details.classList.toggle("is-open")
Console output
undefined
false
true
false

The first line finds the panel. The second asks whether it has the class, and the answer is false. The first toggle adds the class, answers true, and the panel appears with its sentence. The second toggle removes it, answers false, and the panel disappears again. Watch the Elements panel while you do this: the class attribute on the panel changes on each line.

Why Not Set the Style Directly?

Every element also has a style property, and setting it works. The trouble shows up a moment later.

Console
details.style.display = "block";
details.classList.remove("is-open");

The first line opens the panel, and the Elements panel shows why: the element now carries a style attribute saying display block. The second line removes the class, which should close the panel, and the panel stays open. Chapter 3 explained the reason: a style attribute on the element beats every normal rule in the stylesheet. The script and the stylesheet now disagree, and the script wins silently.

That is the real cost. Once a script sets styles, the design lives in two places, and changing the look means searching both. There is one fair exception: a value only the script can know, such as the position of something being dragged, belongs in style. Everything that has a fixed look belongs in a class. Reload the page to clear the style attribute.

Reading and Setting Attributes

Classes are one attribute. For any other, elements offer getAttribute and setAttribute, plus removeAttribute and hasAttribute. Attribute values are always strings, even when they look like numbers or true and false.

Some attributes change what an element does. hidden removes an element from view without any CSS. disabled on a button stops it being clicked or reached with the Tab key. Many attributes are also properties you can set directly, so button.disabled = true and setting the attribute do the same thing.

Other attributes change only what assistive technology, such as a screen reader, announces. aria-expanded tells a screen reader whether the thing a button controls is open or closed. It changes nothing on the screen, and Chapter 9 keeps it in step with the panel.

Console
const toggle = document.querySelector(".details-toggle");
toggle.setAttribute("aria-expanded", "false");
toggle.getAttribute("aria-expanded")
toggle.disabled = true;
Console output
undefined
undefined
'false'
true

The second line gives the button an aria-expanded attribute with the value false, which the Elements panel shows at once. The third reads it back and gets the text false, in quotes, because attribute values are strings. The last line disables the button: it looks faded in the default style, ignores the mouse, and the Tab key skips over it. Reload to undo it.

data- Attributes: Your Own Information

HTML lets you invent attributes, as long as their names start with data-. The browser ignores them; they exist so that the HTML can tell your script something. The script reads them through the element's dataset property, using the part of the name after the dash.

Fragment
<button type="button" data-tag="css">CSS</button>
Console
const cssButton = document.querySelector("button[data-tag]");
cssButton.dataset.tag
cssButton.dataset.tag = "html";

With that button added to the page, the first line finds the first button that has a data-tag attribute. The second reads its value, the string css. The third changes the value, and the Elements panel shows the attribute now says html. The button's visible label, CSS, is untouched: the data belongs to the script, the label belongs to the visitor.

One naming rule to know. In HTML, write data- names in lowercase with dashes between words. In dataset, each dash followed by a letter becomes that letter in capitals, so an attribute called data-tag-name is read as dataset.tagName. The site's project filter in Chapter 9 uses data-tag on each button, so the script knows which tag a button means without reading its label.

Who does what when a panel opens
The script
Toggles the class is-open on the panel. It decides when.
The stylesheet
Says what .is-open looks like. It decides how.
Common Confusions
  • "Changing the style in JavaScript is the direct way." It works, but the look ends up in two places, and the style attribute overrules the stylesheet. Toggle a class and keep the look in CSS.
  • "dataset.tagName reads an attribute called data-tagName." It reads data-tag-name. Dashes in the attribute become capital letters in dataset.
  • "ARIA attributes change what the element does." They change only what assistive technology announces. aria-expanded opens nothing; your script must open the panel and update the attribute to match, as Chapter 12 explains in full.
Why It Matters
  • Keeping the look in CSS keeps the site's design in one place, where one edit changes it everywhere.
  • data- attributes let the HTML tell the script what each element means, so the script does not depend on visible text that may be reworded.

Knowledge Check

A panel has no is-open class. What does panel.classList.toggle("is-open") do?

  • Removes every other class from the panel
  • Answers false and leaves the panel alone
  • Throws an error, since the class is missing
  • Adds the class and answers true

Why is toggling a class better than setting element.style?

  • The look stays in the stylesheet, in one place
  • Classes work in more browsers than styles do
  • Stylesheet rules always beat a style attribute
  • Setting a style takes many more lines of code

An element has the attribute data-project-id="7". How does a script read it?

  • With element.dataset.project-id
  • With element.dataset.projectId
  • With element.dataset.dataProjectId
  • With element.projectId directly

A script sets aria-expanded="true" on a button. What changes?

  • The panel the button controls opens by itself
  • The button changes colour to show it is open
  • What a screen reader announces for the button
  • The button stops responding until it is reset

You got correct