Skip to content

Accessibility

Swarm Icons handles the most common accessibility patterns for you. Most of the time, you don’t need to think about it. The defaults do the right thing.

Every icon on a page is either decorative or meaningful:

  • Decorative icons sit next to text that already explains what’s going on. A trash can icon next to the word “Delete”: the icon is decoration, the text carries the meaning.
  • Meaningful icons stand alone as the only way to understand an action. A trash can icon with no text: screen readers need to know what it does.

Swarm Icons detects which case you’re in and adds the right ARIA attributes automatically.

If you don’t pass aria-label or aria-labelledby, the icon is treated as decorative and hidden from screen readers:

echo swarm_icon('tabler:home');
// → <svg aria-hidden="true" ...>...</svg>

This is the right behavior for the majority of icons (the ones that sit next to visible text):

echo swarm_icon('tabler:home') . ' Home';
echo '<button>' . swarm_icon('tabler:trash') . ' Delete</button>';
echo '<a href="/">' . swarm_icon('tabler:home') . ' Dashboard</a>';

In all of these, the text does the communicating. The icon is visual reinforcement.

When an icon is the only indicator of what something does, give it an aria-label. Swarm Icons will add role="img" automatically:

echo swarm_icon('tabler:home', ['aria-label' => 'Home']);
// → <svg role="img" aria-label="Home" ...>...</svg>

This matters most for icon-only controls:

// Icon-only button
echo '<button>' . swarm_icon('tabler:trash', ['aria-label' => 'Delete item']) . '</button>';
// Icon-only link
echo '<a href="/settings">' . swarm_icon('tabler:settings', ['aria-label' => 'Settings']) . '</a>';

Without aria-label, a screen reader user wouldn’t know what these controls do.

You can also use aria-labelledby to reference another element on the page:

echo '<h2 id="section-title">Dashboard</h2>';
echo swarm_icon('tabler:layout-dashboard', ['aria-labelledby' => 'section-title']);

If you set aria-hidden or role yourself, Swarm Icons won’t touch them:

echo swarm_icon('tabler:home', ['role' => 'presentation']);
echo swarm_icon('tabler:home', ['aria-hidden' => 'true', 'aria-label' => 'Home']);

The automatic behavior only kicks in when those attributes aren’t already present.

Let most icons be decorative. If there’s text next to it, the icon doesn’t need a label. This is the default. You don’t need to do anything.

Label icon-only controls. Any button, link, or input that has no visible text needs aria-label:

echo '<button>' . swarm_icon('tabler:x', ['aria-label' => 'Close']) . '</button>';

Don’t double up. If the text already says “Delete”, don’t also label the icon “Delete”: a screen reader would announce it twice:

// Good: icon is decorative, text carries the meaning
echo swarm_icon('tabler:trash') . ' Delete';
// Avoid: screen reader says "Delete Delete"
echo swarm_icon('tabler:trash', ['aria-label' => 'Delete']) . ' Delete';

The same rules apply: the rendering pipeline is identical:

{# Decorative #}
{{ icon('tabler:home') }} Home
{# Meaningful #}
{{ icon('tabler:home', {'aria-label': 'Go to homepage'}) }}