Skip to content

Commit 74275f4

Browse files
aarishgilanidriesvintsjdmghost
authored
Accessible SVG icons: Add title tag inside SVG if the title attribute 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]>
1 parent d8ddf96 commit 74275f4

File tree

3 files changed

+86
-0
lines changed

3 files changed

+86
-0
lines changed

README.md

+40
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,46 @@ If you'd like, you can use the `svg` helper to expose a fluent syntax for settin
541541
{{ svg('camera')->id('settings-icon')->dataFoo('bar')->dataBaz() }}
542542
```
543543

544+
### Accessibility
545+
546+
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.
547+
548+
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/).
549+
550+
If your icon has semantic meaning, using the title attribute will apply the following features to the SVG element:
551+
552+
- Child element of `<title>` with a unique ID containing the value that was passed
553+
- `title` attribute containing the value that was passed
554+
- `role="img"`
555+
- `aria-labelledby` to refer to the unique ID of the title element
556+
557+
Example usage:
558+
559+
```blade
560+
<x-icon-camera title="camera" />
561+
562+
@svg('camera', ['title' => 'camera'])
563+
```
564+
565+
Example result:
566+
567+
```html
568+
<svg
569+
title="Camera"
570+
role="img"
571+
aria-labelledby="svg-inline--title-ajx18rtJBjSu"
572+
xmlns="http://www.w3.org/2000/svg"
573+
viewBox="0 0 448 512"
574+
>
575+
<title id="svg-inline--title-ajx18rtJBjSu">
576+
Camera
577+
</title>
578+
<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>
579+
</svg>
580+
```
581+
582+
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.
583+
544584
## Building Packages
545585

546586
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.

src/Svg.php

+28
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,36 @@ public function contents(): string
3636
return $this->contents;
3737
}
3838

39+
/**
40+
* This method adds a title element and an aria-labelledby attribute to the SVG.
41+
* To comply with accessibility standards, SVGs should have a title element.
42+
* Check accessibility patterns for icons: https://www.deque.com/blog/creating-accessible-svgs/
43+
*/
44+
public function addTitle(string $title): string
45+
{
46+
// generate a random id for the title element
47+
$titleId = 'svg-inline--title-'.Str::random(10);
48+
49+
// create title element
50+
$titleElement = '<title id="'.$titleId.'">'.$title.'</title>';
51+
52+
// add aria-labelledby attribute to svg element
53+
$this->attributes['aria-labelledby'] = $titleId;
54+
55+
// add role attribute to svg element
56+
$this->attributes['role'] = 'img';
57+
58+
// add title element to svg
59+
return preg_replace('/<svg[^>]*>/', "$0$titleElement", $this->contents);
60+
}
61+
3962
public function toHtml(): string
4063
{
64+
// Check if the title attribute is set and add a title element to the SVG
65+
if (array_key_exists('title', $this->attributes)) {
66+
$this->contents = $this->addTitle($this->attributes['title']);
67+
}
68+
4169
return str_replace(
4270
'<svg',
4371
sprintf('<svg%s', $this->renderAttributes()),

tests/SvgTest.php

+18
Original file line numberDiff line numberDiff line change
@@ -199,4 +199,22 @@ public function it_can_pass_in_attributes_fluently()
199199

200200
$this->assertSame('<svg class="icon" style="color: #fff" data-foo></svg>', $svg->toHtml());
201201
}
202+
203+
/** @test */
204+
public function it_can_add_title_tag_if_title_attribute_is_passed()
205+
{
206+
$svg = new Svg('heroicon-s-camera', '<svg></svg>', ['title' => 'Camera']);
207+
208+
$this->assertStringContainsString('><title id="svg-inline--title-', $svg->toHtml());
209+
$this->assertStringContainsString('</title></svg>', $svg->toHtml());
210+
}
211+
212+
/** @test */
213+
public function it_can_add_aria_labelledby_and_role_attributes_if_title_attribute_is_passed()
214+
{
215+
$svg = new Svg('heroicon-s-camera', '<svg></svg>', ['title' => 'Camera']);
216+
217+
$this->assertStringContainsString('aria-labelledby="svg-inline--title-', $svg->toHtml());
218+
$this->assertStringContainsString('role="img">', $svg->toHtml());
219+
}
202220
}

0 commit comments

Comments
 (0)