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.

Note: focusable="false" on an inline <svg> is a small extra safety net: some older versions of Internet Explorer and Edge would add SVG elements to the tab order even when they carried no interactive role. Modern browsers do not need it, but it is harmless to include.

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

ApproachWhen to use
Icon + visible text labelDefault choice; works for toolbars, primary actions, and anywhere space allows
Icon-only + aria-labelCompact UI (toolbars, mobile) where the icon's meaning is well-established (a trash can, an X)
Icon-only + visually-hidden spanSame result as aria-label, but keeps the text as normal DOM content instead of an attribute
title attribute aloneNever sufficient on its own — a supplementary hover tooltip, not reliably announced by assistive tech, and unavailable on touch
  • 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
Note: Tab to the button and turn on a screen reader (VoiceOver on Mac, NVDA on Windows) to hear exactly what gets announced. If it just says "button," the accessible name is missing.

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-flex with align-items: center and a gap. 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-label describing the action, and mark the icon aria-hidden="true". Without a label the button is announced as just "button". Keep the tap target at least 44 by 44 pixels.