How to Create Icon Buttons in CSS
This recipe builds a button that pairs an inline SVG icon with accessible text, so the action reads clearly to sighted users and screen reader users alike.
Why icon buttons need more than a picture
A lone icon is easy to place in a toolbar, but it only carries meaning if the viewer already recognizes it. A magnifying glass is a fairly safe bet for "search," but a lot of icons — a star, a flag, three dots — are ambiguous without context. Screen reader users have it worse: by default an <svg> or an icon-font glyph has no text content at all, so the button announces simply as "button" with no name attached. Fixing this is not about adding decoration; it is about giving every icon button a real accessible name, either visible on the page or exposed to assistive technology.
- Icon + visible text label — the safest default; nothing is hidden from anyone
- Icon-only button + aria-label — compact, but only for icons whose meaning is well established (a trash can, an X, a gear)
- Icon-only button + a visually-hidden text span — the same result as aria-label, but the text lives in the DOM as real content
- title attribute alone — not a substitute for the above; it is a hover tooltip that is inconsistently exposed to screen readers and invisible on touch devices
Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Icon Button with Visible Text</title>
<style>
body {
font-family: system-ui, sans-serif;
padding: 40px;
background: #f5f5f7;
}
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 18px;
font-size: 16px;
font-weight: 600;
color: #fff;
background: #0d6efd;
border: none;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.15s ease;
}
.btn:hover {
background: #0b5ed7;
}
.btn:focus-visible {
outline: 3px solid #0b5ed7;
outline-offset: 2px;
}
.btn svg {
width: 1.1em;
height: 1.1em;
flex-shrink: 0;
}
</style>
</head>
<body>
<button class="btn" type="button">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true" focusable="false">
<circle cx="9" cy="21" r="1"></circle>
<circle cx="20" cy="21" r="1"></circle>
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
</svg>
Add to cart
</button>
</body>
</html>Inline SVG is usually the better choice over an icon font for this pattern. Because the icon is markup rather than a character in a font file, its color follows the button's own color value through fill="currentColor" or stroke="currentColor", it stays sharp at any size since it is vector graphics, and it does not depend on a font file loading before the icon appears. When an icon sits beside visible text, mark the <svg> as aria-hidden="true" so assistive technology skips over it — the text next to it already provides the name, and without aria-hidden some screen readers will try to describe the graphic and end up announcing the button twice.
Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Icon-Only Button with aria-label</title>
<style>
body {
font-family: system-ui, sans-serif;
padding: 40px;
background: #f5f5f7;
}
.icon-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
padding: 0;
border: none;
border-radius: 50%;
background: #e9ecef;
color: #333;
cursor: pointer;
transition: background-color 0.15s ease;
}
.icon-btn:hover {
background: #dee2e6;
}
.icon-btn:focus-visible {
outline: 3px solid #0d6efd;
outline-offset: 2px;
}
.icon-btn svg {
width: 20px;
height: 20px;
}
</style>
</head>
<body>
<button class="icon-btn" type="button" aria-label="Close dialog">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true" focusable="false">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</body>
</html>Sizing and testing for accessibility
- Every icon-only button has an aria-label or equivalent hidden text describing the action, not just the icon's shape
- Decorative icons next to visible text are marked aria-hidden="true" so they are not announced twice
- The clickable area is at least 44 by 44 CSS pixels, even if the icon itself is smaller — pad the button, don't just enlarge the graphic
- A visible focus style (for example :focus-visible { outline: ... }) exists for keyboard users, not just a :hover style for mouse users
Whichever pattern you pick, the rule of thumb stays the same: an icon is a visual shortcut for people who already recognize it, and the accessible name is the fallback that makes sure everyone else — screen reader users, new users, anyone unsure what the picture means — still knows what the button does.
Frequently Asked Questions
- How do I align an icon and text inside a button?
- Make the button
display: inline-flexwithalign-items: centerand agap. This aligns the icon to the text's optical centre and spaces them consistently, without the baseline drift that margins on an inline icon produce. - How do I make an icon-only button accessible?
- Give it an
aria-labeldescribing the action, and mark the iconaria-hidden="true". Without a label the button is announced as just "button". Keep the tap target at least 44 by 44 pixels.