Skip to content

Commit

Permalink
Accessible SVG icons: Add title tag inside SVG if the title attribute…
Browse files Browse the repository at this point in the history
… is set (#245)

* Add title tag to SVG's if title attribute is set

* Update src/Svg.php

* Pass tile directly to the method as param

* Better variable name for title element

* Update regular expression, consider <svg> may be passed with not attributes.

* Svg class tests to ensure aria attribute and title element are added correctly.

* Add role as img: WCAG best practice

* Update tests to check for role attribute

* Update README.md - Add Accessibility section

* Update README.md

* Include usage example

---------

Co-authored-by: Dries Vints <[email protected]>
Co-authored-by: Nicky <[email protected]>
  • Loading branch information
3 people authored Jul 29, 2024
1 parent d8ddf96 commit 74275f4
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 0 deletions.
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,46 @@ If you'd like, you can use the `svg` helper to expose a fluent syntax for settin
{{ svg('camera')->id('settings-icon')->dataFoo('bar')->dataBaz() }}
```

### Accessibility

If the icon should have semantic meaning, a text alternative can be added with the title attribute. Refer to the [Usage](https://github.com/blade-ui-kit/blade-icons#usage) section of this documentation to learn how to add an attribute.

For almost all use cases, your icon will be assuming the role of an image. This means that deciding on if your icon has any semantic meaning, or what that semantic meaning is, you can use the [WCAG alt text decision tree](https://www.w3.org/WAI/tutorials/images/decision-tree/).

If your icon has semantic meaning, using the title attribute will apply the following features to the SVG element:

- Child element of `<title>` with a unique ID containing the value that was passed
- `title` attribute containing the value that was passed
- `role="img"`
- `aria-labelledby` to refer to the unique ID of the title element

Example usage:

```blade
<x-icon-camera title="camera" />
@svg('camera', ['title' => 'camera'])
```

Example result:

```html
<svg
title="Camera"
role="img"
aria-labelledby="svg-inline--title-ajx18rtJBjSu"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
>
<title id="svg-inline--title-ajx18rtJBjSu">
Camera
</title>
<path fill="currentColor" d="M438.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L338.8 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l306.7 0L233.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z"></path>
</svg>
```

If your icon does not have semantic meaning, you may want to hide the icon to reduce overall document clutter. You may do this by adding `aria-hidden="true"` to your icon.

## Building Packages

If you're interested in building your own third party package to integrate an icon set, it's pretty easy to do so. We've created [a template repo for you to get started with](https://github.com/blade-ui-kit/blade-icons-template). You can find the getting started instructions in its readme.
Expand Down
28 changes: 28 additions & 0 deletions src/Svg.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,36 @@ public function contents(): string
return $this->contents;
}

/**
* This method adds a title element and an aria-labelledby attribute to the SVG.
* To comply with accessibility standards, SVGs should have a title element.
* Check accessibility patterns for icons: https://www.deque.com/blog/creating-accessible-svgs/
*/
public function addTitle(string $title): string
{
// generate a random id for the title element
$titleId = 'svg-inline--title-'.Str::random(10);

// create title element
$titleElement = '<title id="'.$titleId.'">'.$title.'</title>';

// add aria-labelledby attribute to svg element
$this->attributes['aria-labelledby'] = $titleId;

// add role attribute to svg element
$this->attributes['role'] = 'img';

// add title element to svg
return preg_replace('/<svg[^>]*>/', "$0$titleElement", $this->contents);
}

public function toHtml(): string
{
// Check if the title attribute is set and add a title element to the SVG
if (array_key_exists('title', $this->attributes)) {
$this->contents = $this->addTitle($this->attributes['title']);
}

return str_replace(
'<svg',
sprintf('<svg%s', $this->renderAttributes()),
Expand Down
18 changes: 18 additions & 0 deletions tests/SvgTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,22 @@ public function it_can_pass_in_attributes_fluently()

$this->assertSame('<svg class="icon" style="color: #fff" data-foo></svg>', $svg->toHtml());
}

/** @test */
public function it_can_add_title_tag_if_title_attribute_is_passed()
{
$svg = new Svg('heroicon-s-camera', '<svg></svg>', ['title' => 'Camera']);

$this->assertStringContainsString('><title id="svg-inline--title-', $svg->toHtml());
$this->assertStringContainsString('</title></svg>', $svg->toHtml());
}

/** @test */
public function it_can_add_aria_labelledby_and_role_attributes_if_title_attribute_is_passed()
{
$svg = new Svg('heroicon-s-camera', '<svg></svg>', ['title' => 'Camera']);

$this->assertStringContainsString('aria-labelledby="svg-inline--title-', $svg->toHtml());
$this->assertStringContainsString('role="img">', $svg->toHtml());
}
}

0 comments on commit 74275f4

Please sign in to comment.