Buttons
Buttons are how people do things on a page: add to cart, open a menu, send a form, play a video. HTML gives you a real <button> element that already works with the mouse, the keyboard, touch screens and screen readers. Many websites throw that away by building "buttons" out of <div> or <a> elements, and then struggle to make them accessible.
In this lesson you will learn the button types, how to choose between a button and a link, what disabled really does, how to give every button a clear name, and how buttons behave inside forms. Styling buttons is covered separately in CSS Buttons.
The button element
A <button> can contain text and other inline content, such as an icon or a <strong> word. Its type attribute decides what happens when it is pressed.
type | What it does | Use it for |
|---|---|---|
submit | Submits the form it belongs to | "Register", "Place order" |
button | Nothing on its own; JavaScript decides | "Show password", "Add one more", "Open menu" |
reset | Puts every field in the form back to its starting value | Rarely a good idea |
If you leave type out, a button inside a form is a submit button. A button outside any form does nothing until a script handles its click. Write type every time: the next person reading your code then knows what you meant.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Chai order</title>
<style>
button { font-size: 18px; min-width: 44px; min-height: 44px; }
output { display: inline-block; width: 40px; text-align: center; font-size: 20px; }
</style>
</head>
<body>
<h1>Kabir's Chai Stall</h1>
<form action="#">
<p>
Cups of masala chai:
<button type="button" id="less">−</button>
<output id="cups">1</output>
<button type="button" id="more">+</button>
</p>
<button type="submit">Place order</button>
</form>
<script>
const cups = document.querySelector("#cups");
document.querySelector("#more").addEventListener("click", () => {
cups.textContent = Number(cups.textContent) + 1;
});
document.querySelector("#less").addEventListener("click", () => {
cups.textContent = Math.max(1, Number(cups.textContent) - 1);
});
</script>
</body>
</html>Use only the keyboard: press Tab to move between the buttons and press Enter or Space to activate them. Now delete type="button" from the + button and click it: the form submits, because a typeless button in a form is a submit button. This is one of the most common button bugs.
Button or link?
The rule is short: a link goes somewhere, a button does something. If pressing it changes the URL (another page, or a section of this page), use <a href>. If it acts on the current page or sends a form, use <button>. The two are not interchangeable: screen readers announce them differently, a link can be opened in a new tab, and a button responds to both Enter and Space while a link responds to Enter only.
A real <button> gives you all of this for free. Compare it with a <div> that only has a click handler:
| Feature | <button> | <div> with a click handler |
|---|---|---|
| Reachable with Tab | Yes | No |
| Works with Enter and Space | Yes | No |
| Announced as "button" by screen readers | Yes | No |
| Can be disabled | Yes, with disabled | No |
| Submits a form | Yes | No |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Real and fake buttons</title>
<style>
.fake, button { display: inline-block; padding: 10px 16px; background: #1d4ed8; color: white;
border: 0; border-radius: 6px; font: 16px Arial, sans-serif; cursor: pointer; }
</style>
</head>
<body>
<div class="fake" id="fake">Fake button (div)</div>
<button type="button" id="real">Real button</button>
<p id="log">Press Tab, then Enter or Space.</p>
<script>
const log = document.querySelector("#log");
document.querySelector("#fake").addEventListener("click", () => { log.textContent = "The div was clicked."; });
document.querySelector("#real").addEventListener("click", () => { log.textContent = "The button was pressed."; });
</script>
</body>
</html>Both look the same and both respond to the mouse. Only one of them can be used without a mouse. Making the <div> equal would need role="button", tabindex="0" and extra keyboard code, and it is still easy to get wrong. Just use <button>.
Two more rules: never put a button inside a link or a link inside a button (interactive elements must not be nested), and never use <a href="#"> with a click script as a fake button.
Giving every button a name
A screen reader reads a button's accessible name, which normally comes from the text inside it. "Place order" is a good name. "+", "×" or an icon on its own is not: it may be read as "plus", "times" or nothing at all.
For an icon-only button, add the name yourself:
aria-label="Close"on the button gives it a name that is not shown on screen.- Or put hidden text inside it with a "visually hidden" CSS class that keeps text readable by screen readers but invisible on screen.
- Hide the icon itself from screen readers with
aria-hidden="true", so they do not try to read it too.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Icon buttons</title>
<style>
button { min-width: 44px; min-height: 44px; font-size: 18px; margin: 2px; }
svg { width: 20px; height: 20px; vertical-align: middle; }
#story { font-size: 16px; }
</style>
</head>
<body>
<p>
<button type="button" id="smaller" aria-label="Decrease text size">A−</button>
<button type="button" id="bigger" aria-label="Increase text size">A+</button>
<button type="button" id="close" aria-label="Close story">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 5 19 19M19 5 5 19" stroke="currentColor" stroke-width="3"/></svg>
</button>
</p>
<p id="story">Isha cycled from Pune to Lonavala in the monsoon, stopping for vada pav twice.</p>
<script>
const story = document.querySelector("#story");
const resize = (step) => {
story.style.fontSize = parseInt(getComputedStyle(story).fontSize) + step + "px";
};
document.querySelector("#smaller").addEventListener("click", () => resize(-2));
document.querySelector("#bigger").addEventListener("click", () => resize(2));
document.querySelector("#close").addEventListener("click", () => { story.hidden = true; });
</script>
</body>
</html>Sighted users see "A+" and a cross. Screen reader users hear "Increase text size, button" and "Close story, button". When a button has visible words, make its accessible name start with those same words, so that people who control the computer by voice can say what they see. Do not rely on the title attribute: phone and keyboard users usually never see its tooltip.
Give buttons enough room for a finger, too. Accessibility guidelines ask for targets of at least 24 by 24 CSS pixels, and 44 by 44 is more comfortable on a phone, which is why the examples set min-height: 44px.
Disabled buttons
The boolean disabled attribute switches a button off. It cannot be clicked or focused with Tab, it looks faded, and it never submits a form. Putting disabled on a <fieldset> switches off every control inside it.
<button type="submit" disabled>Pay ₹1,299</button>
<fieldset disabled>
<legend>Delivery slot (choose an address first)</legend>
<button type="button">Morning</button>
<button type="button">Evening</button>
</fieldset>Disabled buttons have a weakness: users cannot find out why they are disabled, and keyboard users may not discover them at all. Two friendlier patterns:
- Keep the button enabled and, when it is pressed too early, show a clear message about what is missing.
- Or show the reason in visible text right next to the disabled button.
You may also see aria-disabled="true". It tells screen readers the button is unavailable while keeping it focusable, but it does not stop clicks; your script must ignore them itself.
Buttons in forms
A submit button can carry a name and a value. Only the button that was actually pressed is sent with the form, so the server knows which action the user chose.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Blog editor</title>
</head>
<body>
<form action="#" method="post">
<label for="title">Post title</label>
<input id="title" name="title" value="Best street food in Lucknow">
<p>
<button type="submit" name="action" value="draft">Save draft</button>
<button type="submit" name="action" value="publish">Publish</button>
</p>
</form>
<p>Sent: <output id="sent"></output></p>
<script>
const form = document.querySelector("form");
form.addEventListener("submit", (event) => {
event.preventDefault(); // demo only: nothing is sent
const data = new FormData(form, event.submitter);
document.querySelector("#sent").textContent = new URLSearchParams(data).toString();
});
</script>
</body>
</html>Press each button in turn: the data ends with action=draft or action=publish. Buttons can also change where and how the form is sent, with attributes such as formaction and formnovalidate, and a button can belong to a form it is not inside by using the form attribute. Both are covered in Input & Form Attributes.
Buttons that control other elements
Some buttons open or switch things on the same page:
- A toggle button, such as "Mute", reports its state with
aria-pressed="true"or"false", which your script updates. - A button that shows or hides a section reports it with
aria-expanded. - The
popovertargetattribute opens a popover with no JavaScript, as you will see in Popover API.
Common mistakes
- A typeless button in a form that should not submit. Add
type="button". - A
<div>or<span>used as a button. Use<button>. - A link used for an action, or a button used to go to another page. Links go somewhere; buttons do something.
- Icon-only buttons with no name. Add
aria-labeland hide the icon witharia-hidden="true". - Disabled buttons with no explanation. Tell users what they still need to do.
Key points
<button>has three types:submit,buttonandreset; write the type every time.- A button without a type inside a form submits it.
- Links go somewhere; buttons do something on the current page.
- A real button is focusable, works with Enter and Space and is announced as a button, with no extra code.
- Every button needs an accessible name; icon-only buttons need
aria-labelor hidden text. disabledblocks clicks and focus and is never submitted; explain why a button is unavailable.
Exercise
Build a "Rohtak Library" search panel with: a search input and a submit button labelled "Search"; an icon-only button showing "?" that reveals a hidden help paragraph; and a "Clear" button that empties the search box without submitting. Give the icon-only button a proper accessible name, and make sure only the "Search" button submits the form.
Show solution
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Library search</title>
</head>
<body>
<form action="#" method="get">
<label for="q">Search books</label>
<input id="q" name="q" type="search">
<button type="submit">Search</button>
<button type="button" id="clear">Clear</button>
<button type="button" id="help-btn" aria-label="Search help" aria-expanded="false">
<span aria-hidden="true">?</span>
</button>
</form>
<p id="help" hidden>Search by title, author or ISBN, for example "Premchand".</p>
<script>
const help = document.querySelector("#help");
const helpBtn = document.querySelector("#help-btn");
helpBtn.addEventListener("click", () => {
help.hidden = !help.hidden;
helpBtn.setAttribute("aria-expanded", String(!help.hidden));
});
document.querySelector("#clear").addEventListener("click", () => {
document.querySelector("#q").value = "";
});
</script>
</body>
</html>Only "Search" is a submit button; the other two have type="button". The help button's name comes from aria-label, and aria-expanded tells screen readers whether the help text is showing.
Quick Quiz
1. A <button> with no type attribute sits inside a <form>. What happens when it is pressed?
Inside a form, a button without a type acts as a submit button.
2. Which control should take the user to the "Fees" page?
Going to another page is navigation, so it is a link.
3. What is the best way to name an icon-only "close" button?
aria-label gives screen readers a name, and hiding the icon stops it being read as well.
4. Which statement about disabled is true?
Disabled buttons are inactive, unfocusable and never submitted, which is why they need an explanation nearby.
Learn HTML with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
