PLAINFINCH learn by building

Real elements, real meaning

You will rebuild a page made of plain boxes into a real list, a real table and a real button that people can hear and use.

Why does the element you choose matter?

Every HTML element tells the browser what a piece of content is. A <button> is a control that does something. An <a href> is a link to somewhere. A <ul> is a group of related items. A <table> is data in rows and columns. The browser turns those meanings into roles in the accessibility tree, and screen readers announce them, count them and let people jump between them.

A <div> and a <span> mean nothing. They exist for styling and for grouping things in your code. With CSS you can make a <div> look exactly like a button, a list or a table, and on screen nobody can tell. In the accessibility tree it is still a plain box of text. The starting bakery page shows this: in the Page view it has a list, a price table and a brown button, and in the Screen reader view it is eleven loose lines of text.

What do native elements give you for free?

Native elements come with behaviour that would take dozens of lines of JavaScript to copy. A <button> is in the Tab order, shows a focus ring, fires its click handler on Enter and Space, can be disabled with one attribute, and submits a form when it is inside one. A link opens with Enter, appears in the browser’s context menu with “Open in new tab”, and shows its address on hover.

Form controls bring keyboard support, mobile keyboards, autofill and validation. A <details> element opens and closes, and tells screen readers whether it is expanded. A <dialog> opened with showModal() traps focus and closes with Escape. All of this works the same way across browsers and screen readers, and keeps working when browsers improve.

This is the main reason the course prefers native HTML over ARIA. ARIA can add a role to a <div>, so a screen reader says “button”, and it adds nothing else. You still have to make it focusable, handle both keys and style its focus, and it is easy to miss one. No ARIA is better than bad ARIA, and a real element is better than both.

Why does a div with onclick fail keyboard users?

A click handler on a <div> responds to a mouse click and a tap. It does not respond to the keyboard, because a <div> can’t receive focus unless you give it tabindex, and even then the browser won’t turn Enter or Space into a click. The Keyboard view on the starting page has no stops at all: the only control on the page is unreachable.

A screen reader user may find the text Reserve a loaf while reading, and hear nothing to say it can be pressed. Some screen readers can send a click to it, and many users wouldn’t think to try. This fails 2.1.1 Keyboard and 4.1.2 Name, Role, Value. Changing the <div> to <button type="button"> fixes all of it at once, and you keep the same onclick. Reset the button’s default styles in CSS if you need it to look the same as before.

<button type="button" class="order-btn"
  onclick="document.getElementById('note').hidden = false">
  Reserve a loaf
</button>

What does a screen reader say about a list?

When a screen reader reaches a <ul> or <ol>, it says that a list is starting and how many items it has: “list, 3 items”. Many also say each item’s position, “1 of 3”, and announce when the list ends. Users can skip a whole list with one key, which helps a great deal with a navigation menu of twenty links they have already heard.

Use <ul> when the order doesn’t matter, like the breads on the bakery page, and <ol> when it does, like the steps for the bird count. Don’t type the numbers yourself with paragraphs: the list element numbers them, keeps the numbers right when you add a step, and tells a screen reader how many there are.

One real-world catch: one major browser and screen reader pairing stops announcing a list as a list when its CSS removes the bullets with list-style: none. Its makers did this deliberately, because many sites use lists for layout. If the count matters, such as for a set of steps, you can add role="list" back to the <ul>. The Screen reader view here always announces the list, as most screen readers do.

<ul class="breads">
  <li>Sourdough</li>
  <li>Seeded rye</li>
  <li>Cinnamon knots</li>
</ul>
FigureThree breads in divs are read as three loose lines. The same breads in a ul are read as list, 3 items, Sourdough 1 of 3, Seeded rye 2 of 3, Cinnamon knots 3 of 3, end of list.Three breads in divs are read as three loose lines. The same breads in a ul are read as list, 3 items, Sourdough 1 of 3, Seeded rye 2 of 3, Cinnamon knots 3 of 3, end of list.
The breads look the same either way. Only the ul tells a listener that a list starts, how many items it has, and where it ends.

When is a table the right element?

Use a table when the data has two dimensions, where each value means something because of both its row and its column. The bakery price list qualifies: £4.20 is the price of a sourdough loaf. A bird count with species and numbers qualifies too. A grid of product cards does not, because the cards aren’t related across columns, so lay those out with CSS grid.

Screen readers give tables special commands. On entering one, they announce its size, “table, 3 rows, 2 columns”. Users then move cell by cell with table keys, and when they move to a new column the screen reader reads that column’s header first. That is the whole point: hearing “Price, £3.80” instead of a list of unexplained numbers.

This only works with header cells. Mark the first row’s cells as <th>, and add scope="col" to be clear, or scope="row" for headers down the side. A <caption> as the first child gives the table a name, which a screen reader reads on entry and which helps when a page has several tables. Don’t use tables for page layout. Screen readers try to read them as data, which turns a layout into a puzzle.

<table>
  <caption>Saturday prices</caption>
  <tr><th scope="col">Bread</th><th scope="col">Price</th></tr>
  <tr><td>Sourdough loaf</td><td>£4.20</td></tr>
</table>

Which elements should you reach for most?

Most pages need only a handful. Use headings for section titles and paragraphs for text. Use <a href> for anything that takes people somewhere and <button> for anything that does something on the page. Use lists for groups of similar things and tables for data. Use <label> with every form field, and <fieldset> with <legend> around groups of radio buttons. Use the landmark elements, <header>, <nav>, <main> and <footer>, for the main regions of the page, which the next lesson covers.

Text-level elements matter less to screen readers than you might expect. Most screen readers don’t announce <strong> or <em> by default, so never rely on bold alone to carry meaning such as “sold out”. Write the meaning in words. <abbr>, <time> and <code> are good HTML and mostly silent to assistive technology.

  • Does it go somewhere? <a href>
  • Does it do something here? <button>
  • Is it a group of similar things? <ul> or <ol>
  • Does each value depend on a row and a column? <table> with <th>

How do you test whether the structure is real?

Read the Screen reader view and look for the words list, table, button and link. If you can see a list on screen and the view shows only lines of text, the structure is fake. Open the Keyboard view and check that every control you can click with a mouse appears there. Then press Tab through the page with a real keyboard and press Enter and Space on each button.

In your browser, turn off CSS, or use the reader view. A page with real structure still has bullets, table borders and buttons that look like buttons. The accessibility pane in the developer tools shows the role of whatever you select. Anything that should be interactive and shows “generic” needs a real element.

What do AI tools get wrong about semantic HTML?

Code generators, and the component libraries they learned from, love <div> elements. Common results are clickable cards built from <div onclick>, menus made of <div> elements styled as links, and tables of data drawn with CSS grid. Each looks fine and fails the keyboard and the Screen reader view.

Sometimes the tool notices and patches it with ARIA: <div role="button" tabindex="0">. That gets the role and focus right, and usually forgets the Enter and Space keys, the disabled state and the form behaviour. When you see role="button", role="link" or role="list" on a <div>, ask whether the matching native element would do the job. It nearly always would.

Field notes

  • The element you choose tells the browser what the content is.
  • A <button> works with Tab, Enter and Space without extra code.
  • A <div> with a click handler ignores the keyboard.
  • Use a table with header cells when data has rows and columns.

Easy to get wrong

A <div> can’t be reached with Tab, doesn’t respond to Enter or Space, and is announced as plain text. A <button> does all three with no extra code.

Typed numbers are only text. An <ol> tells screen readers it is a list of steps and how many there are, and keeps the numbers right when the steps change.

Bold cells look like headers and aren’t. Without <th>, a screen reader can’t tell people which column a number belongs to.

Questions people ask

Is it wrong to use divs at all?

No. A <div> is the right choice for a wrapper you need only for layout or styling, and most pages have many. The problem is using one in place of an element that has a meaning, such as a button, a list or a table.

Can I make a link look like a button?

Yes. Style the element however the design needs. Choose the element by what it does: if it goes to another page, it is a link, even when it looks like a big button. The Buttons and links lesson goes further into this.

Do I need scope on every th?

For a simple table with one row of headers, browsers and screen readers work it out without scope. Adding scope="col" or scope="row" makes the intent explicit, costs nothing, and helps with headers down the left side.

Should I add role="list" to every styled list?

Only where hearing the list and its count really helps, such as numbered steps or a short set of options. For navigation menus, the navigation landmark already tells people where they are, and many users prefer less chatter.

Keep going