<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Duncan Faulkner</title>
    <description>The latest articles on DEV Community by Duncan Faulkner (@duncanfaulkner).</description>
    <link>https://dev.to/duncanfaulkner</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F252191%2Fb3d864a7-0c0b-43bd-98ae-0b1b1762b3b4.png</url>
      <title>DEV Community: Duncan Faulkner</title>
      <link>https://dev.to/duncanfaulkner</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/duncanfaulkner"/>
    <language>en</language>
    <item>
      <title>The WCAG AA Checklist Every Angular Component Should Pass Before You Ship</title>
      <dc:creator>Duncan Faulkner</dc:creator>
      <pubDate>Sun, 12 Jul 2026 08:00:00 +0000</pubDate>
      <link>https://dev.to/duncanfaulkner/the-wcag-aa-checklist-every-angular-component-should-pass-before-you-ship-3pdn</link>
      <guid>https://dev.to/duncanfaulkner/the-wcag-aa-checklist-every-angular-component-should-pass-before-you-ship-3pdn</guid>
      <description>&lt;p&gt;I've been writing Angular for ten years, and for most of them I treated accessibility the way nearly everyone does: as a thing I'd "get to" once the feature worked. It always lost the race to the deadline.&lt;/p&gt;

&lt;p&gt;What changed my mind wasn't guilt — it was realising &lt;em&gt;why&lt;/em&gt; a11y keeps getting skipped. It's not neglect. It's that accessibility is genuinely hard to hold in your head while you're also wrangling state, layout, and a ticket that was due yesterday. You can't fix "I forgot" with willpower. You fix it with a checklist.&lt;/p&gt;

&lt;p&gt;So here's the one I actually use before a component ships. It's WCAG &lt;strong&gt;AA&lt;/strong&gt;-oriented, Angular-specific, and ordered the way bugs actually show up. One thing up front, because it's the whole reason a list like this exists:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Automated tools catch maybe 30–40% of WCAG issues.&lt;/strong&gt; AXE, Lighthouse and the rest are brilliant at the mechanical stuff — a missing &lt;code&gt;alt&lt;/code&gt;, a contrast ratio, a duplicate &lt;code&gt;id&lt;/code&gt;. They cannot tell you whether your tab order makes sense, whether your focus goes somewhere useful after a dialog closes, or whether your &lt;code&gt;aria-live&lt;/code&gt; region actually announces anything. &lt;strong&gt;A green AXE run is the floor, not the finish line.&lt;/strong&gt; I've marked each item below with whether a tool will catch it (🤖) or whether it's on you (👤).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. Keyboard &amp;amp; focus
&lt;/h2&gt;

&lt;p&gt;This is where most "accessible" component libraries quietly fall apart.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Everything interactive works with the keyboard alone.&lt;/strong&gt; 👤 Unplug your mouse and drive the component. Every action reachable by click must be reachable by Tab/Enter/Space/arrows.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You used native elements where you could.&lt;/strong&gt; 🤖/👤 A &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; is keyboard-operable, focusable and announced for free. A &lt;code&gt;&amp;lt;div (click)&amp;gt;&lt;/code&gt; is none of those. If you're adding &lt;code&gt;role="button"&lt;/code&gt; and a &lt;code&gt;keydown&lt;/code&gt; handler to a &lt;code&gt;div&lt;/code&gt;, stop and use a &lt;code&gt;button&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Focus is always visible.&lt;/strong&gt; 🤖 Never &lt;code&gt;outline: none&lt;/code&gt; without a replacement. Use &lt;code&gt;:focus-visible&lt;/code&gt; so keyboard users get a clear ring and mouse users aren't bothered:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;  &lt;span class="nd"&gt;:focus-visible&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ngbr-color-focus&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;outline-offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tab order follows reading order.&lt;/strong&gt; 👤 No positive &lt;code&gt;tabindex&lt;/code&gt;. Let the DOM order do the work; if the visual order disagrees with the DOM, fix the DOM.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Focus is trapped in modal dialogs&lt;/strong&gt; and restored to the trigger on close. 👤 Don't hand-roll this — use the CDK:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;cdkTrapFocus&lt;/span&gt; &lt;span class="na"&gt;cdkTrapFocusAutoCapture&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"dialog"&lt;/span&gt; &lt;span class="na"&gt;aria-modal=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Confirm deletion&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    …
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No keyboard traps anywhere else.&lt;/strong&gt; 👤 You can always Tab &lt;em&gt;out&lt;/em&gt; of a component. The only legitimate trap is an open modal.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Focus management on dynamic changes
&lt;/h2&gt;

&lt;p&gt;The single most overlooked area, and almost entirely invisible to tooling.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Route changes move focus.&lt;/strong&gt; 👤 On navigation, an SPA leaves focus on the old (now-gone) element. Send it to the new page's heading:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationEnd&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;h1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;main h1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// h1 needs tabindex="-1"&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Opening a menu/drawer/dialog moves focus into it;&lt;/strong&gt; closing returns focus to the trigger. 👤&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deleting an item moves focus somewhere sensible&lt;/strong&gt; (the next row, or the list heading) — never to &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;. 👤&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;There's a skip link&lt;/strong&gt; to bypass the nav and jump to &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;. 🤖/👤&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Semantics &amp;amp; ARIA
&lt;/h2&gt;

&lt;p&gt;The first rule of ARIA is: don't use ARIA if a native element will do.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Landmarks are present:&lt;/strong&gt; one &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;, plus &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;footer&amp;gt;&lt;/code&gt; as needed. 🤖&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;One &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; per page, and headings don't skip levels.&lt;/strong&gt; 🤖 Heading structure is how screen-reader users navigate — it's a table of contents, not styling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Custom widgets carry the right role and state&lt;/strong&gt; — and put them in the &lt;code&gt;host&lt;/code&gt; object, not &lt;code&gt;@HostBinding&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ngbr-switch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;role&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;switch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[attr.aria-checked]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checked()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[attr.aria-disabled]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;disabled() || null&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tabindex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(keydown.enter)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toggle()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(keydown.space)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toggle(); $event.preventDefault()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NgbrSwitch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* checked = signal(false) … */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Icon-only buttons have an accessible name.&lt;/strong&gt; 🤖 &lt;code&gt;aria-label="Close"&lt;/code&gt; — an icon font or SVG is silent otherwise.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No &lt;code&gt;aria-*&lt;/code&gt; attribute references a missing &lt;code&gt;id&lt;/code&gt;.&lt;/strong&gt; 🤖 Dangling &lt;code&gt;aria-describedby&lt;/code&gt;/&lt;code&gt;aria-labelledby&lt;/code&gt; announce nothing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You didn't slap ARIA on a native element that already had the semantics.&lt;/strong&gt; 👤 &lt;code&gt;&amp;lt;button role="button"&amp;gt;&lt;/code&gt; is redundant; &lt;code&gt;&amp;lt;nav role="navigation"&amp;gt;&lt;/code&gt; is noise.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Forms &amp;amp; errors
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Every input has a real &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt;&lt;/strong&gt; with a matching &lt;code&gt;for&lt;/code&gt;/&lt;code&gt;id&lt;/code&gt; — not just a placeholder. 🤖&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Required fields expose it programmatically:&lt;/strong&gt; &lt;code&gt;[required]&lt;/code&gt; or &lt;code&gt;aria-required="true"&lt;/code&gt;, not just an asterisk. 🤖/👤&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Errors are linked to their field and announced.&lt;/strong&gt; 👤 Tie the message in with &lt;code&gt;aria-describedby&lt;/code&gt;, flip &lt;code&gt;aria-invalid&lt;/code&gt;, and give it &lt;code&gt;role="alert"&lt;/code&gt; so it's read on appearance:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;formControlName=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
         &lt;span class="na"&gt;[attr.aria-invalid]=&lt;/span&gt;&lt;span class="s"&gt;"showError()"&lt;/span&gt;
         &lt;span class="na"&gt;[attr.aria-describedby]=&lt;/span&gt;&lt;span class="s"&gt;"showError() ? 'email-error' : null"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  @if (showError()) {
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email-error"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"alert"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Enter a valid email address.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;On submit, focus moves to the first error&lt;/strong&gt; (or an error summary). 👤 Don't make a keyboard user hunt for what went wrong.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Error state isn't signalled by colour alone&lt;/strong&gt; (see §5). 👤&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Colour, contrast &amp;amp; motion
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Text meets AA contrast&lt;/strong&gt; — 4.5:1 for body, 3:1 for large text and UI components/focus indicators. 🤖&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Information is never conveyed by colour alone.&lt;/strong&gt; 👤 A red border needs an icon or text too — that's WCAG 1.4.1, and it's invisible to a colour-blind user &lt;em&gt;and&lt;/em&gt; to AXE. This bites hardest in charts and status indicators.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The UI survives 200% zoom and 400% reflow&lt;/strong&gt; without horizontal scrolling or clipping. 👤&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You honour &lt;code&gt;prefers-reduced-motion&lt;/code&gt;.&lt;/strong&gt; 🤖/👤 Animations can trigger vestibular disorders:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;  &lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-reduced-motion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;*,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;animation-duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.01ms&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;transition-duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.01ms&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It works in both light and dark themes&lt;/strong&gt; — derive colours from tokens so contrast holds in both, rather than hardcoding. 👤&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Dynamic content &amp;amp; screen-reader UX
&lt;/h2&gt;

&lt;p&gt;The stuff that separates "passes the audit" from "actually usable."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Async updates are announced.&lt;/strong&gt; 👤 "Showing 12 results," "Saved," "Loading…" — use the CDK's &lt;code&gt;LiveAnnouncer&lt;/code&gt; rather than hand-rolling a live region:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;announcer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LiveAnnouncer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// after a filter runs:&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;announcer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;announce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; results`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;polite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Loading and busy states are exposed,&lt;/strong&gt; e.g. &lt;code&gt;aria-busy="true"&lt;/code&gt; on the region being updated. 👤&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Images have meaningful &lt;code&gt;alt&lt;/code&gt;&lt;/strong&gt; (and decorative ones have &lt;code&gt;alt=""&lt;/code&gt;). 🤖 With &lt;code&gt;NgOptimizedImage&lt;/code&gt;, &lt;code&gt;alt&lt;/code&gt; is mandatory — lean on it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data visualisations have a text equivalent.&lt;/strong&gt; 👤 An SVG chart is invisible to a screen reader. Behind every chart there should be a visually-hidden &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; of the same data — this is the single hardest item on the list, and the one no tool will ever flag.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How to actually run this
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Automated pass&lt;/strong&gt; — wire &lt;code&gt;@axe-core/playwright&lt;/code&gt; (or jest-axe) into CI so the 🤖 items can never regress. This is table stakes; it just can't be the whole test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keyboard pass&lt;/strong&gt; — unplug the mouse, Tab through the whole flow. Most 👤 issues surface in 60 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Screen-reader pass&lt;/strong&gt; — VoiceOver (⌘+F5 on macOS) or NVDA (free on Windows). You don't need to be an expert; just listen to whether what you hear makes sense.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. None of these are exotic — they're the same dozen things, over and over, which is exactly why a checklist beats good intentions.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I got tired of running this by hand for every component, so I built it into the defaults. &lt;a href="https://ngbracket.com/waitlist?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=wcag-post" rel="noopener noreferrer"&gt;NgBracket&lt;/a&gt; is a set of Angular component packs where every one of these is handled out of the box — keyboard, focus management, ARIA, contrast, the screen-reader table behind every chart — so AA-by-default is the starting point, not a ticket for later. If accessibility is the part of your build that keeps losing the race to the deadline, that's exactly the problem it's meant to take off your plate. We're launching soon; &lt;a href="https://ngbracket.com/waitlist?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=wcag-post" rel="noopener noreferrer"&gt;join the waiting list&lt;/a&gt; for early access.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>angular</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Managing Focus in Angular: Dialogs, Drawers and Route Changes Without Stranding Your Users</title>
      <dc:creator>Duncan Faulkner</dc:creator>
      <pubDate>Fri, 10 Jul 2026 19:33:30 +0000</pubDate>
      <link>https://dev.to/duncanfaulkner/managing-focus-in-angular-dialogs-drawers-and-route-changes-without-stranding-your-users-4agf</link>
      <guid>https://dev.to/duncanfaulkner/managing-focus-in-angular-dialogs-drawers-and-route-changes-without-stranding-your-users-4agf</guid>
      <description>&lt;p&gt;Every accessibility checklist mentions focus management, and then moves on — as if it were one item. It isn't. It's a discipline, and it's the single thing that most often separates an app that technically passes an audit from one a keyboard user can actually get through.&lt;/p&gt;

&lt;p&gt;Here's the mental model that fixed it for me: focus is a cursor, and there is only ever one of it. For someone using a keyboard or a screen reader, focus is where they are in your app. Sighted mouse users don't think about it because their eyes and pointer move independently. Everyone else has exactly one position, and every time your UI changes what's on screen, you owe them an answer to one question:&lt;/p&gt;

&lt;p&gt;Where does focus go now?&lt;/p&gt;

&lt;p&gt;Get that answer wrong and the user is teleported somewhere useless — usually the top of the page, or worse, , from which they have to Tab through your entire chrome to get back. And no automated tool will tell you, because "where focus went after a state change" isn't in a DOM snapshot. Let's go through the four places Angular apps get it wrong.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Route changes: the SPA focus black hole
This is the most common and most invisible bug in the entire framework. When you navigate a server-rendered site, the browser loads a new document and puts focus back at the top — predictable. In an SPA, the router swaps some DOM and focus stays on whatever you clicked, which is usually a link that no longer exists. Screen-reader users get no signal that the page changed at all.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fix is to move focus to the new page's heading on every navigation:&lt;/p&gt;

&lt;p&gt;@Component({ /* … */ })&lt;br&gt;
export class App {&lt;br&gt;
  private readonly router = inject(Router);&lt;br&gt;
  private readonly doc = inject(DOCUMENT);&lt;/p&gt;

&lt;p&gt;constructor() {&lt;br&gt;
    this.router.events&lt;br&gt;
      .pipe(&lt;br&gt;
        filter((e) =&amp;gt; e instanceof NavigationEnd),&lt;br&gt;
        takeUntilDestroyed(),&lt;br&gt;
      )&lt;br&gt;
      .subscribe(() =&amp;gt; {&lt;br&gt;
        const heading = this.doc.querySelector('main h1') as HTMLElement | null;&lt;br&gt;
        heading?.focus();&lt;br&gt;
      });&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
Two things make this work: the &lt;/p&gt;
&lt;h1&gt; needs tabindex="-1" so it's programmatically focusable without joining the tab order, and you want a skip link as the very first focusable element so people can jump past the nav to :&lt;/h1&gt;


&lt;p&gt;Skip to main content&lt;br&gt;
That's it — one subscription, and every route change now announces itself.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Dialogs: trap it, then give it back
A modal has two focus responsibilities, and most hand-rolled dialogs get exactly one of them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While open, focus must be trapped inside — Tab shouldn't escape to the page behind it. Don't build this yourself; the CDK does it correctly, including the wrap-around:&lt;/p&gt;


&lt;h2 id="title"&gt;Delete this project?&lt;/h2&gt;

&lt;p&gt;When it closes, focus must return to the element that opened it — otherwise it falls to  and the user is stranded at the top of the page. This is the half everyone forgets:&lt;/p&gt;

&lt;p&gt;private returnFocusTo: HTMLElement | null = null;&lt;/p&gt;

&lt;p&gt;open() {&lt;br&gt;
  this.returnFocusTo = this.doc.activeElement as HTMLElement;&lt;br&gt;
  this.isOpen.set(true);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;close() {&lt;br&gt;
  this.isOpen.set(false);&lt;br&gt;
  this.returnFocusTo?.focus();&lt;br&gt;
}&lt;br&gt;
Better still, don't manage overlays by hand at all — @angular/cdk/dialog's Dialog service handles focus trapping and restoration for you (restoreFocus is on by default). If you're opening real modals, reach for it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Drawers, menus and disclosures: the same rule, smaller
A cart drawer, a nav flyout, a filter panel — these are just modals with less drama, and the rule is identical: move focus in when it opens, return it to the trigger when it closes. A drawer that opens but leaves focus behind the overlay is a keyboard trap in reverse — the user is tabbing through content they can't even see.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For menus with arrow-key navigation, the CDK's cdk/a11y ListKeyManager / FocusKeyManager gives you roving focus (arrow keys move between items, Tab leaves the whole widget) without you writing keydown spaghetti.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Destructive actions: don't drop focus into the void
Delete the row that currently has focus and — if you do nothing — focus evaporates to . The fix is to send it somewhere sensible: the next row, or if you just deleted the last one, the list heading. The trick in Angular is timing: you have to wait for the signal update to flush to the DOM before the new target exists. Use afterNextRender:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;private readonly host = inject(ElementRef);&lt;br&gt;
private readonly injector = inject(Injector);&lt;/p&gt;

&lt;p&gt;removeAt(index: number) {&lt;br&gt;
  this.items.update((list) =&amp;gt; list.filter((_, i) =&amp;gt; i !== index));&lt;/p&gt;

&lt;p&gt;afterNextRender(&lt;br&gt;
    () =&amp;gt; {&lt;br&gt;
      const rows = this.host.nativeElement.querySelectorAll('[data-row]');&lt;br&gt;
      const next = rows[Math.min(index, rows.length - 1)];&lt;br&gt;
      (next ?? this.host.nativeElement.querySelector('h2'))?.focus();&lt;br&gt;
    },&lt;br&gt;
    { injector: this.injector },&lt;br&gt;
  );&lt;br&gt;
}&lt;br&gt;
The same pattern covers "add item" (focus the new input), "load more" (focus the first new result), and "submit failed" (focus the first error).&lt;/p&gt;

&lt;p&gt;And the flip side: don't steal focus&lt;br&gt;
Managing focus doesn't mean grabbing it constantly. Two anti-patterns to avoid:&lt;/p&gt;

&lt;p&gt;autofocus on load. Yanking focus into a search box on every page load fights people who navigate by heading or landmark. Only autofocus inside a modal or a deliberate single-purpose page.&lt;br&gt;
Focusing on scroll or hover. Focus should follow intent (a click, a key, an explicit action), never an incidental event.&lt;br&gt;
And whatever you do, keep the focus ring visible. :focus-visible gives keyboard users a clear indicator without bothering mouse users:&lt;/p&gt;

&lt;p&gt;:focus-visible {&lt;br&gt;
  outline: 2px solid var(--ngbr-color-focus);&lt;br&gt;
  outline-offset: 2px;&lt;br&gt;
}&lt;br&gt;
Never outline: none without a replacement. A focus you can't see is a focus you can't manage.&lt;/p&gt;

&lt;p&gt;How to test it in two minutes&lt;br&gt;
Put your mouse down. Now drive the whole flow with Tab, Enter, Space and arrows, and after every interaction ask the one question: where did my focus go? Open a dialog — is focus inside it? Close it — did it come back to the button? Change routes — are you on the new heading? Delete something — are you on a real element, or lost at the top?&lt;/p&gt;

&lt;p&gt;You'll find your bugs in about ninety seconds. That's the whole reason focus management gets skipped: not because it's hard, but because it's invisible until you actually try to use the thing without a pointer.&lt;/p&gt;

&lt;p&gt;This is the kind of detail that's genuinely tedious to get right on every component — so we got it right once. &lt;a href="https://ngbracket.com/waitlist?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=focus-post" rel="noopener noreferrer"&gt;NgBracket&lt;/a&gt; is a set of accessible-by-default Angular component packs where focus trapping, restoration and route-change handling are built in, not bolted on. We're launching soon; &lt;a href="https://ngbracket.com/waitlist?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=focus-post" rel="noopener noreferrer"&gt;join the waiting list&lt;/a&gt; for early access.*&lt;/p&gt;



</description>
      <category>a11y</category>
      <category>angular</category>
      <category>frontend</category>
      <category>ux</category>
    </item>
    <item>
      <title>Why I Built an Angular Component Library Where Accessibility Isn't a Backlog Item</title>
      <dc:creator>Duncan Faulkner</dc:creator>
      <pubDate>Sun, 05 Jul 2026 17:31:39 +0000</pubDate>
      <link>https://dev.to/duncanfaulkner/why-i-built-an-angular-component-library-where-accessibility-isnt-a-backlog-item-4i65</link>
      <guid>https://dev.to/duncanfaulkner/why-i-built-an-angular-component-library-where-accessibility-isnt-a-backlog-item-4i65</guid>
      <description>&lt;p&gt;I've been building with Angular for ten years, and in that time I've watched the same thing happen on nearly every project. A component gets built, it works with a mouse, it ships. Accessibility — keyboard support, focus, ARIA, contrast, the screen-reader story — becomes a line in the backlog called something like &lt;em&gt;"a11y pass"&lt;/em&gt;. And that ticket sits there. It gets groomed, re-estimated, pulled into a sprint, bumped out of the sprint, and eventually closed as "we'll come back to it." We never came back to it.&lt;/p&gt;

&lt;p&gt;For a long time I thought that was a discipline problem. If we just cared more, tested more, remembered more, we'd get it right. I don't believe that anymore. &lt;strong&gt;Accessibility keeps losing the race to the deadline not because people don't care, but because it's genuinely hard to hold in your head while you're also wrangling state, layout, data and a ticket that was due yesterday.&lt;/strong&gt; You cannot fix a systemic problem with individual willpower. You fix it by changing the default.&lt;/p&gt;

&lt;p&gt;That realisation is the whole reason I built what I built. This is the story of the problem I was actually trying to solve, and how I went about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem isn't "not enough components"
&lt;/h2&gt;

&lt;p&gt;Let me be clear about what I was &lt;em&gt;not&lt;/em&gt; trying to do, because it matters. I wasn't trying to make another component library. There are plenty. A few new ones have appeared just in the last few months, and they're perfectly good — nicely styled, well documented, quick to drop in.&lt;/p&gt;

&lt;p&gt;But open the accessibility story on almost any of them and it's the same shape every time: a compliance checkbox, bolted on near the end, covering the third of WCAG that an automated tool can see. &lt;code&gt;aria-label&lt;/code&gt; is present, contrast is fine on the light theme, AXE is green. And then you drive it with a keyboard and the dialog swallows your focus, the custom select doesn't respond to arrow keys, the chart is a blank rectangle to a screen reader, and the dark theme quietly fails contrast because nobody checked.&lt;/p&gt;

&lt;p&gt;That's the gap. It's not a shortage of components — it's that in almost every library, &lt;strong&gt;accessibility is a feature you're expected to finish yourself&lt;/strong&gt;, and modern Angular is something the library will get to eventually. I didn't want to add to the pile of components. I wanted to close that gap.&lt;/p&gt;

&lt;p&gt;So I made two bets, and the whole thing is built on them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bet one: accessibility is a first-class citizen, not a compliance pass
&lt;/h2&gt;

&lt;p&gt;The rule I set myself is simple to state and annoying to keep: &lt;strong&gt;no component ships until it works by keyboard and by screen reader.&lt;/strong&gt; Not "until AXE is green" — AXE is a regression net for the easy third, and I run it in CI on every component, but it is not the test. The test is a human putting the mouse down and driving the whole thing with Tab, Enter, Space and arrows, then turning on VoiceOver or NVDA and listening to whether what they hear matches what's on screen.&lt;/p&gt;

&lt;p&gt;In practice that meant solving, once and properly, all the things that normally get skipped:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Focus management as a discipline&lt;/strong&gt; — trap it in dialogs and drawers, restore it to the trigger on close, move it to the heading on route change, send it somewhere sane after you delete the row it was sitting on. One position, always accounted for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State that's communicated, not just coloured&lt;/strong&gt; — &lt;code&gt;aria-invalid&lt;/code&gt; and &lt;code&gt;aria-describedby&lt;/code&gt; wiring errors to fields, &lt;code&gt;role="alert"&lt;/code&gt; so they announce, live regions that actually exist in the DOM before the text arrives so a screen reader speaks them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The hardest case, data visualisation&lt;/strong&gt; — every chart is keyboard-navigable and carries a visually-hidden data table behind the SVG as the screen-reader source of truth, because "this picture holds information that exists nowhere else" is the single biggest blind spot in automated testing. (There's even optional audio sonification for trends, which was two weeks of my life and worth every day.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contrast that survives theming&lt;/strong&gt; — every colour is a token that re-tones for dark mode and holds AA on both surfaces, verified with an automated contrast pass across every component in light &lt;em&gt;and&lt;/em&gt; dark, not eyeballed once on white.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that is exotic. It's all well-understood. The point was never to invent new accessibility techniques — it was to do the known-correct thing &lt;em&gt;every single time&lt;/em&gt;, so that the person consuming the library never has to file that "a11y pass" ticket, because it's already done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bet two: signals-first, because reactive accessibility is better accessibility
&lt;/h2&gt;

&lt;p&gt;The second bet is that this had to be built the modern Angular way — and not for fashion. &lt;strong&gt;Standalone components, signals, &lt;code&gt;OnPush&lt;/code&gt; everywhere, Material 3 tokens, strict TypeScript, zero NgModules.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Signals aren't just a nicer state primitive here; they turn out to make accessibility &lt;em&gt;easier to get right&lt;/em&gt;. When your &lt;code&gt;aria-live&lt;/code&gt; announcement, your &lt;code&gt;aria-invalid&lt;/code&gt; state, your &lt;code&gt;aria-current&lt;/code&gt; and your focus targets are all derived from signals, the accessible state can't drift out of sync with the visual state — they're computed from the same source. Reactive accessibility is more correct accessibility, almost for free. Building on the old change-detection model, you're forever remembering to keep two representations in step. With signals you compute one and both fall out of it.&lt;/p&gt;

&lt;p&gt;It also means the library is built the way you're &lt;em&gt;already&lt;/em&gt; building in Angular v20+ — so it drops into a modern app without dragging a NgModule-era mental model back in with it. A component library that fights your architecture isn't saving you time.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I actually went about it
&lt;/h2&gt;

&lt;p&gt;Two bets are easy to write on a slide. Holding them across a real, sellable product took some machinery:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AXE wired into CI&lt;/strong&gt; as the automated floor, plus a structural accessibility test suite — the regression net for the third that tools &lt;em&gt;can&lt;/em&gt; catch, so nothing slips backwards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A browser-driven contrast harness&lt;/strong&gt; that walks every component in both themes and fails the build on a single AA violation. That's how I know dark mode holds, rather than hoping.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A token architecture&lt;/strong&gt; where every surface, text and semantic colour chains through shared design tokens, so theming and dark-mode contrast cascade instead of being re-hardcoded per component.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dogfooding, relentlessly.&lt;/strong&gt; I built the marketing site itself, its FAQ, and three complete example apps — an admin dashboard, a storefront, and a booking diary — entirely out of the packs. If a component was awkward to use or quietly inaccessible, I found out by living in it, not by reading my own docs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eight packs, a hundred-and-six components, all held to the same line. Built solo, which is terrifying to admit and also exactly why the defaults had to carry the weight — I can't manually re-check accessibility on a hundred components by hand every release, so it had to be baked in and enforced by tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm banging this drum
&lt;/h2&gt;

&lt;p&gt;I'm writing this partly because the space is getting busier, and I think the distinction is worth being loud about. More component libraries is good for Angular. But "accessible by default" and "modern by default" are not the headline feature on most of them — they're the footnote. For me they're the &lt;em&gt;entire product&lt;/em&gt;. If a green CI check is going to tell you the truth about whether a real person can use what you shipped, the two-thirds that automated tools can't see have to already be handled. That's the thing I built, and it's the thing I'll keep making noise about.&lt;/p&gt;

&lt;p&gt;If accessibility is the part of your build that always loses the race to the deadline — the ticket that never gets picked up — that's precisely the problem this is meant to take off your plate.&lt;/p&gt;

&lt;p&gt;And it's not finished. Eight packs is where it starts, not where it stops — there are more in the pipeline, on a steady cadence, each held to the same line. Which is also where you come in: I'd genuinely rather build the components &lt;em&gt;you're&lt;/em&gt; about to write by hand than guess. So if there's a widget you keep re-implementing, a pack you wish existed, or a gnarly accessibility case you've never seen a library get right — tell me. A lot of what's here started as exactly that kind of "someone should just build this properly" itch, and I'm very open to the next one being yours.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I got tired of watching accessibility lose the race, so I built the defaults instead. &lt;a href="https://ngbracket.com/waitlist?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=why-post" rel="noopener noreferrer"&gt;NgBracket&lt;/a&gt; is a set of Angular component packs — 8 packs, 106 components — where WCAG AA, AXE-passing, keyboard and screen-reader support are the baseline, and everything is signals-first, standalone, &lt;code&gt;OnPush&lt;/code&gt;, Material 3 themeable, zero NgModules. We're launching soon; &lt;a href="https://ngbracket.com/waitlist?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=why-post" rel="noopener noreferrer"&gt;join the waiting list&lt;/a&gt; for early access.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>a11y</category>
      <category>webdev</category>
    </item>
    <item>
      <title>AXE Passes, But It's Still Unusable: The Accessibility Bugs Automated Tools Can't Catch</title>
      <dc:creator>Duncan Faulkner</dc:creator>
      <pubDate>Thu, 02 Jul 2026 07:10:33 +0000</pubDate>
      <link>https://dev.to/duncanfaulkner/axe-passes-but-its-still-unusable-the-accessibility-bugs-automated-tools-cant-catch-26ea</link>
      <guid>https://dev.to/duncanfaulkner/axe-passes-but-its-still-unusable-the-accessibility-bugs-automated-tools-cant-catch-26ea</guid>
      <description>&lt;p&gt;There's a particular kind of false confidence that comes from a green accessibility report. The CI check passes, the Lighthouse score says 100, the AXE panel is all ticks. You ship it. And somewhere, a person using a screen reader hits your component and simply &lt;em&gt;can't&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I've shipped that component. More than once. Here's what I learned the hard way: &lt;strong&gt;automated accessibility testing checks whether your HTML is well-formed, not whether your interface is usable.&lt;/strong&gt; Those are very different questions, and the gap between them is where real users get stranded.&lt;/p&gt;

&lt;p&gt;AXE is genuinely excellent — wire it into CI today if you haven't. But it works by inspecting a static snapshot of the DOM and checking it against a ruleset. It can see that an attribute is &lt;em&gt;present&lt;/em&gt;. It cannot see whether the attribute is &lt;em&gt;true&lt;/em&gt;, whether the sequence &lt;em&gt;makes sense&lt;/em&gt;, or whether anything actually &lt;em&gt;happens&lt;/em&gt; when you press a key. It catches roughly a third of WCAG issues. The other two-thirds need a human.&lt;/p&gt;

&lt;p&gt;Let me show you the gap, with five components that all pass AXE clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The modal that swallows your focus
&lt;/h2&gt;

&lt;p&gt;A dialog opens, you confirm, it closes. AXE is delighted: &lt;code&gt;role="dialog"&lt;/code&gt;, &lt;code&gt;aria-modal&lt;/code&gt;, labelled heading, focus trapped while open. All correct.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;confirm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ✅ AXE passes. ❌ focus just fell to &amp;lt;body&amp;gt;.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what a keyboard user experiences: the dialog vanishes and their focus lands on &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;, at the top of the page. They've lost their place entirely and have to Tab back through the whole UI. AXE can't catch this, because &lt;em&gt;where focus goes after a state change&lt;/em&gt; isn't in the DOM snapshot — it's in time.&lt;/p&gt;

&lt;p&gt;The fix is to remember the trigger and restore to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;openDialog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trigger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeElement&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// put them back where they were&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. The error you can only see if you can see
&lt;/h2&gt;

&lt;p&gt;A form field fails validation. A red border appears. AXE checks the label is present and the border colour has enough contrast — both fine. Green.&lt;/p&gt;

&lt;p&gt;But the &lt;em&gt;only&lt;/em&gt; signal that something went wrong is colour. A colour-blind user sees nothing. A screen-reader user hears nothing, because the message was never wired to the field or announced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- passes AXE, communicates nothing --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;formControlName=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"error-text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Enter a valid email&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing connects that &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; to the input, and nothing tells assistive tech it appeared. The fix is to make the error &lt;em&gt;programmatic&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;formControlName=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
       &lt;span class="na"&gt;[attr.aria-invalid]=&lt;/span&gt;&lt;span class="s"&gt;"showError()"&lt;/span&gt;
       &lt;span class="na"&gt;[attr.aria-describedby]=&lt;/span&gt;&lt;span class="s"&gt;"showError() ? 'email-error' : null"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
@if (showError()) {
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email-error"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"alert"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Enter a valid email.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;aria-invalid&lt;/code&gt; states the field is wrong, &lt;code&gt;aria-describedby&lt;/code&gt; ties the explanation to it, and &lt;code&gt;role="alert"&lt;/code&gt; makes it announce the moment it renders. None of those three things is something AXE will ever require — it only knows the label exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The toggle that looks operable and isn't
&lt;/h2&gt;

&lt;p&gt;This one is sneaky, because the ARIA is &lt;em&gt;perfect&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;role&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;switch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[attr.aria-checked]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checked()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tabindex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// …no keyboard handler.&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AXE sees a valid &lt;code&gt;switch&lt;/code&gt; with a valid &lt;code&gt;aria-checked&lt;/code&gt; state and a tabindex. It has no way to know that pressing &lt;strong&gt;Space&lt;/strong&gt; does absolutely nothing, because "is this control actually operable by keyboard" requires &lt;em&gt;running&lt;/em&gt; it, not reading it. A mouse user toggles it happily; a keyboard user can focus it and then... nothing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;role&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;switch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[attr.aria-checked]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checked()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tabindex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(keydown.space)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toggle(); $event.preventDefault()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(keydown.enter)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toggle()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Or — better — wrap a native &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; and skip the role entirely. The native element can't have this bug.)&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The live region that never speaks
&lt;/h2&gt;

&lt;p&gt;You added an &lt;code&gt;aria-live&lt;/code&gt; region for "12 results" after filtering. Textbook. Except you did it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@if (announcement()) {
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ announcement() }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AXE sees a valid live region and approves. But screen readers only announce changes to a live region that was &lt;strong&gt;already in the DOM&lt;/strong&gt; when the change happened. By &lt;code&gt;@if&lt;/code&gt;-ing the whole element &lt;em&gt;in&lt;/em&gt; at the same moment as the text, you've defeated the mechanism — the region and its content arrive together, and nothing is announced. The region has to exist empty, then receive text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sr-only"&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ announcement() }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or skip the footgun and use the CDK's &lt;code&gt;LiveAnnouncer&lt;/code&gt;, which maintains a persistent region for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;announcer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LiveAnnouncer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;announcer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;announce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; results`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;polite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is my favourite example because the code &lt;em&gt;looks&lt;/em&gt; more correct than the version that works. Tooling can't tell the difference; a screen reader tells you immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The chart that doesn't exist
&lt;/h2&gt;

&lt;p&gt;You render a beautiful SVG line chart. Contrast is AA, there's a &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;, AXE is satisfied. To a screen-reader user, it is a blank rectangle. SVG internals aren't exposed as data, and "this picture conveys information that exists nowhere else on the page" is not a rule any automated tool can express.&lt;/p&gt;

&lt;p&gt;The only real fix is to provide the same data in a form assistive tech &lt;em&gt;can&lt;/em&gt; read — a visually-hidden table behind the visual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;figure&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;…&lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;table&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sr-only"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;caption&amp;gt;&lt;/span&gt;Monthly revenue, Jan–Dec&lt;span class="nt"&gt;&amp;lt;/caption&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;&lt;/span&gt;Month&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&lt;/span&gt;Revenue&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- one row per point --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/figure&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the single biggest blind spot in automated testing, and it's why data visualisation is the hardest thing in front-end accessibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  The only test that actually works
&lt;/h2&gt;

&lt;p&gt;Notice the pattern across all five: AXE was &lt;em&gt;right&lt;/em&gt; every time. The HTML was well-formed. The bug was always in something a snapshot can't see — sequence, interaction, timing, meaning.&lt;/p&gt;

&lt;p&gt;So keep AXE in CI. It's a brilliant regression net for the third of issues it covers, and it's free. Just don't mistake it for the test. The real test takes two minutes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Put the mouse down and Tab through it.&lt;/strong&gt; Can you reach and operate everything? Does focus go somewhere sane after every action?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turn on a screen reader&lt;/strong&gt; — VoiceOver (⌘+F5) or NVDA — and listen. Does what you hear match what's on screen?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If those two passes are clean, you've covered the two-thirds that matter most. If you only run the automated check, you've verified your HTML is tidy and learned almost nothing about whether a real person can use what you built.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This gap is exactly why I built &lt;a href="https://ngbracket.com/waitlist?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=axe-post" rel="noopener noreferrer"&gt;NgBracket&lt;/a&gt; the way I did — every component is driven by keyboard and screen reader before it ships, not just run through AXE. Accessible-by-default means the two-thirds the tools can't see are already handled, so a green CI check is telling you the truth for once.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>a11y</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Introduction to Vitest and Angular</title>
      <dc:creator>Duncan Faulkner</dc:creator>
      <pubDate>Sun, 13 Oct 2024 21:49:59 +0000</pubDate>
      <link>https://dev.to/duncanfaulkner/introduction-to-vitest-and-angular-1j3a</link>
      <guid>https://dev.to/duncanfaulkner/introduction-to-vitest-and-angular-1j3a</guid>
      <description>&lt;p&gt;The Angular team deprecated Karma a few versions ago and are currently working on ways to provide an alternative 3rd party unit testing frameworks. Currently the options talked about so far are Web Test Runner (likely to be the default), this is a browser based unit test runner similar in many ways to Karma. The other option being discussed is Jest, this can be installed now but can be a little tricky sometimes to get set up and some known issues when Angular is set up to use ES builds (the default in Angular 18 onward). &lt;/p&gt;

&lt;p&gt;The Angular team are looking at using the CLI for the installation process of these unit testing frameworks, similar to when selecting a CSS framework when creating a new project. This is all still very early on in the development cycle and may be a little way off before it becomes developer preview (currently still experimental) and no mention (as yet) of how this would work with an existing project.&lt;/p&gt;

&lt;p&gt;Now that newer versions of Angular use Vite as a development build server and the default in Angular 18, it is now possible to use Vitest in our Angular projects. The installation process is straight forward to set up and feels less problematic than setting up Jest from my recent experience. The great news here is that Vitest has a similar syntax to Karma and Jest, so the learning curve should be fairly small.&lt;/p&gt;

&lt;p&gt;In this post I'm going to walk through setting up and configuring Vitest in an Angular 18 application and replacing Karma . I won't be writing unit tests in this post, I'll leave that for another post, this post is about setting up Angular to use &lt;code&gt;Vitest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I'm going to start a new project for this post but this will also work on an existing project.&lt;/p&gt;

&lt;p&gt;In a directory type the following in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng new my-vitest-app  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;my-vitest-app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm i
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a new Angular application called &lt;code&gt;my-vitest-app&lt;/code&gt;, it'll change into that directory and then run &lt;code&gt;npm install&lt;/code&gt; with our application created we can now remove &lt;code&gt;Karma&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;From the terminal, type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm uninstall karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We don't need to uninstall &lt;code&gt;@types/jasmine&lt;/code&gt; and &lt;code&gt;jasmine-core&lt;/code&gt; as  Vitest uses Jasmine under the hood, we can use &lt;code&gt;it&lt;/code&gt; and &lt;code&gt;describe&lt;/code&gt; from Jasmine so we don't need to import anything in our test files. It is possible to use &lt;code&gt;it&lt;/code&gt; and &lt;code&gt;describe&lt;/code&gt; from Vitest but in doing so we won't be able to use functions like &lt;code&gt;fakeAysnc&lt;/code&gt; in our unit tests.&lt;/p&gt;

&lt;p&gt;The easiest way to get Vitest installed in our project is to use a plugin and AnalogJS has just what we need to setup and configure Vitest for us.&lt;/p&gt;

&lt;p&gt;From the terminal type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @analogjs/platform &lt;span class="nt"&gt;-D&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Followed by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng g @analogjs/platform:setup-vitest &lt;span class="nt"&gt;--project&lt;/span&gt; my-vitest-app        
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once complete the following files will be generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vite.config.mts&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;reference types="vitest" /&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;angular&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@analogjs/vite-plugin-angular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// https://vitejs.dev/config/&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nf"&gt;angular&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jsdom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;setupFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/test-setup.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/*.spec.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;reporters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;define&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;import.meta.vitest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;vite.config.mts&lt;/code&gt; will be in the root of the project, this sets up the configuration for Vitest, what environment to use, the default is &lt;code&gt;jsdom&lt;/code&gt;, you can change this to &lt;code&gt;happy-dom&lt;/code&gt; (you will need to install &lt;code&gt;happy-dom&lt;/code&gt;). The &lt;code&gt;test-setup.ts&lt;/code&gt; will be in the &lt;code&gt;src&lt;/code&gt; directory and this sets up the test environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@analogjs/vitest-angular/setup-zone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;BrowserDynamicTestingModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;platformBrowserDynamicTesting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/platform-browser-dynamic/testing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getTestBed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core/testing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;getTestBed&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;initTestEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;BrowserDynamicTestingModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;platformBrowserDynamicTesting&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are using a&lt;code&gt;zoneless&lt;/code&gt; application then the &lt;code&gt;test-setup.ts&lt;/code&gt; will be slightly different:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@analogjs/vitest-angular/setup-snapshots&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;BrowserDynamicTestingModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;platformBrowserDynamicTesting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/platform-browser-dynamic/testing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getTestBed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core/testing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;getTestBed&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;initTestEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;BrowserDynamicTestingModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;platformBrowserDynamicTesting&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;angular.json&lt;/code&gt; the &lt;code&gt;test&lt;/code&gt; section will be replaced with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"builder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@analogjs/vitest-angular:test"&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally the &lt;code&gt;tsconfig.spec.json&lt;/code&gt; will be update to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"extends"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./tsconfig.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"outDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./out-tsc/spec"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"jasmine"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"vitest/globals"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;added&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es2016"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;added&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"src/**/*.spec.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"src/**/*.d.ts"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"src/test-setup.ts"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;added&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run Vitest tests we need to change the scripts section, in our applications &lt;code&gt;package.json&lt;/code&gt; file add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;scripts&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vitest"&lt;/span&gt;&lt;span class="w"&gt;   
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;npm run test&lt;/code&gt; will run all of our test files our project.&lt;/p&gt;

&lt;p&gt;If you don't want to install Volta you can skip this section.&lt;/p&gt;

&lt;p&gt;To run &lt;code&gt;Vitest&lt;/code&gt; from the terminal we need to install Vitest, we have a couple of options, globally using &lt;code&gt;npm i -g vitest&lt;/code&gt;, I tend to use Volta, this is a tool chain manager, similar to Node Version Manager (NVM) but Volta handles other JavaScript tooling other than just Node. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you currently have a version of node installed, you will need to uninstall this first before install Volta, once Volta is installed we can install multiple versions of node without removing the previous versions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To install Volta head over to &lt;code&gt;https://volta.sh/&lt;/code&gt; and get the appropriate installer package for your operating system, I'm using Ubuntu and all we need for this is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# install volta&lt;/span&gt;
curl https://get.volta.sh | bash
&lt;span class="c"&gt;# install node&lt;/span&gt;
volta &lt;span class="nb"&gt;install &lt;/span&gt;node@20.15.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;As a side note, one of the nice features of Volta, is the ability to pin the version of node we are using to our &lt;code&gt;package.json&lt;/code&gt; file, meaning that Volta will always select the correct version of Node, this is real handy when working in a team of developers, to pin a version of Node to our &lt;code&gt;package.json&lt;/code&gt; file run &lt;code&gt;volta pin node@20.15.0&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With &lt;code&gt;volta&lt;/code&gt; and &lt;code&gt;node&lt;/code&gt; installed we can now install Vitest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;volta &lt;span class="nb"&gt;install &lt;/span&gt;vitest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Vitest installed we can run &lt;code&gt;volta ls&lt;/code&gt; this will show a list of the tooling that we have installed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# example listing from Volta&lt;/span&gt;
⚡️ Currently active tools:

    Node: v20.15.1 &lt;span class="o"&gt;(&lt;/span&gt;default&lt;span class="o"&gt;)&lt;/span&gt;
    Tool binaries available:
        vitest &lt;span class="o"&gt;(&lt;/span&gt;current @ /home/repo/vitest-app/package.json&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;With everything installed we can now run our unit tests, if we want to run a selection of tests (or a specific test) then we need to use the terminal and pass in an additional parameter, this parameter will run any test file that contains this parameter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# run a range of unit tests&lt;/span&gt;
vitest dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also specify a file, this will only run a unit test that matches the file name, note that this doesn't support regex or glob patterns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# run a specific unit test&lt;/span&gt;
vitest app.component
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above will run the unit tests and watch for changes, to run the unit test once and not watch for changes we can change the command to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# run unit test(s) once then stop&lt;/span&gt;
vitest run &lt;span class="o"&gt;[&lt;/span&gt;optionally specify an additional parameter]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we want to run our unit tests in a CI environment we don't need for them to be in watch mode, so add a &lt;code&gt;test:ci&lt;/code&gt; to the script section of the &lt;code&gt;package.json&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;scripts&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vitest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"test:ci"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vitest run"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to set up our build pipelines to run our tests, setting up build pipelines is out of scope for this post, so I will assume you know how to do this, if not they are plenty of posts on how to do this.&lt;/p&gt;

&lt;p&gt;The next part I want to cover is code coverage, so we've written our unit tests for our various features in our project, but have we written enough? Have we covered the important parts that need testing? This is where code coverage comes to the rescue. Code Coverage tell us how much of our feature is covered by unit tests, whether we've missed a path, for example we've covered the &lt;code&gt;if&lt;/code&gt; condition but not the &lt;code&gt;else&lt;/code&gt; condition. &lt;/p&gt;

&lt;p&gt;Let's add a new script to the scripts section of our &lt;code&gt;package.json&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;scripts&lt;/span&gt;&lt;span class="w"&gt;    
&lt;/span&gt;&lt;span class="nl"&gt;"coverage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"vitest run --coverage"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this we can run &lt;code&gt;npm run coverage&lt;/code&gt; locally and will create a &lt;code&gt;coverage&lt;/code&gt; directory for us in the root of the project and we'll get a table of files along with their code coverage:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;% Stmts&lt;/th&gt;
&lt;th&gt;% Branch&lt;/th&gt;
&lt;th&gt;% Funcs&lt;/th&gt;
&lt;th&gt;% Lines&lt;/th&gt;
&lt;th&gt;Uncovered Line #s&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;All files&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;app.component.ts&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When we run this it might prompt to install a supporting package this can be either &lt;code&gt;coverage-v8&lt;/code&gt; (the default) or  &lt;code&gt;coverage-instanbul&lt;/code&gt; package, if not we can install them manually using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# For v8&lt;/span&gt;
npm i &lt;span class="nt"&gt;-D&lt;/span&gt; @vitest/coverage-v8

&lt;span class="c"&gt;# For istanbul&lt;/span&gt;
npm i &lt;span class="nt"&gt;-D&lt;/span&gt; @vitest/coverage-istanbul
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can set the &lt;code&gt;provider&lt;/code&gt; type in the &lt;code&gt;coverage&lt;/code&gt; section in the &lt;code&gt;vite.config.mts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;angular&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
    &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jsdom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;setupFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src/test-setup.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;**/*.spec.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;reporters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;coverage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;v8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;define&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;import.meta.vitest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run &lt;code&gt;code coverage&lt;/code&gt; in our CI environment we need to adjust our &lt;code&gt;test:ci&lt;/code&gt; script to include a reporter, here I've used &lt;code&gt;junit&lt;/code&gt; and &lt;code&gt;junit.xml&lt;/code&gt; and there is a handy plugin for &lt;code&gt;Azure Devops&lt;/code&gt; to read the &lt;code&gt;junit.xml&lt;/code&gt; file which will import the file and create a nice dashboard for us.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;script:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;scripts&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"test:ci"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vitest run --reporter=default --reporter=junit --outputFile=reports/junit.xml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;Azure devops&lt;/code&gt; we can add the &lt;code&gt;PublishTestResults&lt;/code&gt; task in our build pipelines and would have the task run after our unit tests have completed. &lt;/p&gt;

&lt;p&gt;When we run our unit tests the results will be displayed in the terminal (unless running in a CI environment), but if you prefer to see the results in a browser this is also possible, first we need to install the ui tooling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @vitest/ui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will need to match the version of &lt;code&gt;@vitest/ui&lt;/code&gt; to the same version of &lt;code&gt;vitest&lt;/code&gt; installed on your system, once installed we can start it by running &lt;code&gt;vitest --ui&lt;/code&gt; from the terminal, if we add the &lt;code&gt;html&lt;/code&gt; to the &lt;code&gt;reporters&lt;/code&gt; array in our &lt;code&gt;vite.config.ts&lt;/code&gt; file, this will create an &lt;code&gt;html&lt;/code&gt; directory with all the files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;angular&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
    &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jsdom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;setupFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src/test-setup.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;**/*.spec.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;reporters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// add html into this array.&lt;/span&gt;
      &lt;span class="na"&gt;coverage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;v8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;define&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;import.meta.vitest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this post we looked at installing and setting up Vitest, a unit test runner in for Angular applications based on Vite, we also added code coverage to our test runs to see how much code coverage our unit tests cover, we also learnt how we can set this up in our CI in Azure Devops. &lt;/p&gt;

</description>
      <category>typescript</category>
      <category>testing</category>
      <category>softwaredevelopment</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Using Mat-Icon Component – Part Two</title>
      <dc:creator>Duncan Faulkner</dc:creator>
      <pubDate>Mon, 27 Jan 2020 13:27:00 +0000</pubDate>
      <link>https://dev.to/duncanfaulkner/using-mat-icon-component-part-two-3fe8</link>
      <guid>https://dev.to/duncanfaulkner/using-mat-icon-component-part-two-3fe8</guid>
      <description>&lt;h2&gt;
  
  
  Blog Post #002
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Duncan Faulkner – January 2020
&lt;/h2&gt;

&lt;p&gt;I hadn’t intended to write about this just yet, but I got a chance to investigate this sooner than I had thought. I was refactoring part of a project that was using the Mat-Icon component, with an icon.module.ts file as I’d discussed in the previous post.&lt;/p&gt;

&lt;p&gt;I wanted to see if I could improve on the code and reduce the amount of repeated code by iterating over a file and registering the icons.&lt;/p&gt;

&lt;p&gt;I started with a JSON file and populated it with some image names and added the HttpClientModule to use an http.get() method to read the JSON file. All good so far, the file is reading and looping with image names being returned and passed into the matIconRegistery.addSvgIcon() method.&lt;/p&gt;

&lt;p&gt;I thought I might get some issues, but it all seemed to work fine, except the application was erroring, the issue was that the application was loading before the call to the JSON file had returned with the results, so even though I was getting images and registering icons, the app had already tried to use the icons and they weren’t there at that point, and the images were being returned after the app had started.&lt;/p&gt;

&lt;p&gt;OK, so may be this is a timing issue and it doesn’t work in the icon.module.ts file, so I moved code to the app.component.ts file. Nope, the results were the same. This was not the result I was expecting and it was a little odd because the icon.module.ts file was loading images before.&lt;/p&gt;

&lt;p&gt;I’m going to revisit this at a later date, as I would like to understand what the issue is and why it was behaving the way it did, my guess is it has something to do with the http.get() request and the timing around that.&lt;/p&gt;

&lt;p&gt;But as I needed to get this refactoring completed I needed to look for another solution.&lt;/p&gt;

&lt;p&gt;After a bit of research I found an answer and that was to create a service to handle the iterating and registering of icons. Instead of a JSON file to store a list of images the solution was an enum, with a key value structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;Icons&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;power&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;power&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;save&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;save&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To keep things simple I’ve made the key and the image name the same.&lt;/p&gt;

&lt;p&gt;The service is quite simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// imports&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Icons&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../image.enum&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;providedIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IconService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;matIconRegistry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MatIconRegistry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;sanitizer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Domsanitizer&lt;/span&gt;
&lt;span class="p"&gt;){}&lt;/span&gt;

&lt;span class="nf"&gt;registerIcons&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Icons&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assets/images/icons&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;icon&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matIconRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSvgIcon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       &lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
       &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sanitizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bypassSecurityTrustResourceUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.svg`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;);&lt;/span&gt; 
 &lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And on the app.component.ts file call the registerIcons() function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// imports&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;IconService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./icon.service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;iconService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IconService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iconService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerIcons&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s it, not what I had in mind when I started this, but it does deliver what I wanted to do, and that was to reduce the amount of code to register icons. Though I guess the enum could get pretty lengthy if there are a lot of images.&lt;/p&gt;

&lt;p&gt;I have to thank Stefanos Kouroupis -dev.to – for his article and code on this as I wouldn’t have thought about using a service for this and this has changed my thinking. I wanted to include here it as a follow up to my previous post for completeness, you can see his full article here &lt;a href="https://dev.to/elasticrash/using-custom-made-svg-icons-through-the-angular-material-library-2pif"&gt;https://dev.to/elasticrash/using-custom-made-svg-icons-through-the-angular-material-library-2pif&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I will add a github repo of the code in a day or two.&lt;/p&gt;

&lt;p&gt;Original article published at: &lt;a href="https://anglebrackets.io/index.php/2020/01/25/using-mat-icon-component-part-two/" rel="noopener noreferrer"&gt;https://anglebrackets.io/index.php/2020/01/25/using-mat-icon-component-part-two/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>angularmaterial</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Angular Materials Mat-Icon component</title>
      <dc:creator>Duncan Faulkner</dc:creator>
      <pubDate>Thu, 14 Nov 2019 18:34:22 +0000</pubDate>
      <link>https://dev.to/duncanfaulkner/angular-materials-mat-icon-component-mff</link>
      <guid>https://dev.to/duncanfaulkner/angular-materials-mat-icon-component-mff</guid>
      <description>&lt;h3&gt;
  
  
  Using local SVG images.
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Blog post #001
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Duncan Faulkner - October 2019
&lt;/h4&gt;

&lt;p&gt;When developing an Angular application with Angular Material, there comes a point when we need to add icons on our components, or buttons etc... Angular Material has the Mat-Icon component for doing this. This component works with web fonts like Font-Awesome for instance, simply by adding the name of the image required and an image is displayed. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;For example:&lt;/em&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;mat-icon&amp;gt;home&amp;lt;/mat-icon&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;em&gt;Requires Angular application and Angular Material is installed/configured and a reference to a web font like Font-Awesome library is all set up&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But what about if you have custom icons that are not part of a web font? And would like to makes changes to the icon, for example: change the colour on hover or on a specific condition at run time?&lt;/p&gt;

&lt;p&gt;In a recent project I had a bespoke set of SVG icons, the Angular web application was to be installed on a server that didn't have access to the internet, so the images had to be local. I wanted to use the Mat-Icon component out of the box &lt;em&gt;(in an early version of the project I had a custom icon component)&lt;/em&gt;. I still wanted to be able to change the colours of the icons at various stages throughout the application based on certain conditions as well as on hover. This post covers how I achieved that.&lt;/p&gt;

&lt;h3&gt;
  
  
  SVG Icons
&lt;/h3&gt;

&lt;p&gt;There are a number of different ways to register an icon with the Mat-Icon component, this post discusses  &lt;code&gt;addSvgIcon&lt;/code&gt;, the others are &lt;code&gt;addSvgIconInNamespace, addSvgIconLiteral or addSvgIconLiteralInNamespace&lt;/code&gt; and are all methods of &lt;code&gt;MatIconRegistry&lt;/code&gt; and I might cover these in more detail in a future post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up an Angular project
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; &lt;em&gt;In this post, I'm not going to step through the creation of an Angular application as there are so many already online. Plus I tend to create Nx workspaces for all my Angular projects as I prefer this project layout. I plan to do a blog post on this very soon, for the moment though see &lt;a href="https://nx.dev/angular/getting-started/what-is-nx" rel="noopener noreferrer"&gt;Getting started with Narwhal's Nx Workspaces&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In the newly created Angular project, create a &lt;code&gt;shared directory&lt;/code&gt; and add a new file named &lt;code&gt;material.module.ts&lt;/code&gt;. I like to separate Angular Material imports into their own module, I also create a separate module for other 3rd party components, just makes it easier to import later, especially using the Nx workspace layout and feature folders.&lt;/p&gt;

&lt;p&gt;In the material.module.ts&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Material Module example.&lt;/span&gt;
&lt;span class="c1"&gt;// All other Angular Material component imports here&lt;/span&gt;
&lt;span class="c1"&gt;// but the important ones are...&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;MatIconModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MatIconRegistry&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/material/icon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;NgModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// Other material imports removed for brevity,&lt;/span&gt;
    &lt;span class="nx"&gt;MatIconModule&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// Other material exports removed for brevity,&lt;/span&gt;
    &lt;span class="nx"&gt;MatIconModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MatIconRegistry&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;entryComponents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;MatIconRegistry&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;  &lt;span class="kd"&gt;class&lt;/span&gt;  &lt;span class="nc"&gt;MaterialModule&lt;/span&gt;  &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MatIconModule is the module for the component, and the MatIconRegistry is a service to register and display icons. Add a reference to the  &lt;code&gt;material.module.ts&lt;/code&gt; in the &lt;code&gt;app.module.ts&lt;/code&gt;. Don't forget to export it as well, otherwise it won't be available and Angular will not know anything about the Angular Material components.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Include the material module in app.module.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="nx"&gt;BrowserModule&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="k"&gt;from&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/platform-browser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="nx"&gt;NgModule&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="k"&gt;from&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="nx"&gt;AppComponent&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="k"&gt;from&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="nx"&gt;BrowserAnimationsModule&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="k"&gt;from&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/platform-browser/animations&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="nx"&gt;MaterialModule&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="k"&gt;from&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./shared/material.module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;NgModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;AppComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;BrowserModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="nx"&gt;BrowserAnimationsModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="nx"&gt;MaterialModule&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="na"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;AppComponent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;  &lt;span class="kd"&gt;class&lt;/span&gt;  &lt;span class="nc"&gt;AppModule&lt;/span&gt;  &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have the Angular Material components set up and configured we need to register the icons, before we can use them. For the moment we are just going to add these to the &lt;code&gt;app.component.ts&lt;/code&gt; to get up and running, we'll look at a better method later on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// First Example&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;styleUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.component.scss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;matIconRegistry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MatIconRegistery&lt;/span&gt;
    &lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matIconRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSvgIcon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assets/img/icon/home.svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matIconRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSvgIcon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assets/img/icon/add.svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// or we could do this, and chain the addsvgIcon methods.&lt;/span&gt;
    &lt;span class="c1"&gt;// we'll use this method going forward in this post&lt;/span&gt;
    &lt;span class="c1"&gt;// {&lt;/span&gt;
    &lt;span class="c1"&gt;//      this.matIconRegistry.addSvgIcon('home','assets/img/icon/home.svg')&lt;/span&gt;
    &lt;span class="c1"&gt;//      .addSvgIcon('add','assets/img/icon/add.svg');&lt;/span&gt;
    &lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this to the &lt;code&gt;app.component.html&lt;/code&gt; page, we'll discuss this in more detail in a bit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- First HTML example --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;mat-icon&lt;/span&gt; &lt;span class="na"&gt;svgIcon=&lt;/span&gt;&lt;span class="s"&gt;"home"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/mat-icon&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;mat-icon&lt;/span&gt; &lt;span class="na"&gt;svgIcon=&lt;/span&gt;&lt;span class="s"&gt;"add"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/mat-icon&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point we are not going to see much in the browser as we have an issue with the image URL. If you open the browsers console section, you will see the following error:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Error: unsafe value used in a resource URL context&lt;/code&gt;. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So what does this error mean? A brief explanation from the Angular Material website says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To prevent an Cross Site Scripting (XSS), SVG URL's and HTML strings passed to MatIconRegistry must be marked as trusted by the Angular's DomSanitizer service. Icons are fetched via XmlHttpRequest and must have their URL's on the same domain as the containing page or configured to allow cross-domain access.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So lets add the DomSanitizer and fix this issue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Second Example - with the DomSanitizer&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;styleUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.component.scss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;domSanitizer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DomSanitizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;matIconRegistry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MatIconRegistery&lt;/span&gt;
    &lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matIconRegistry&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSvgIcon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domSanitizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bypassSecurityTrustResourceUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assets/img/icon/home.svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSvgIcon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domSanitizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bypassSecurityTrustResourceUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assets/img/icon/add.svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// add other icons here....;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The call to &lt;code&gt;bypassSecurityTrustResourceUrl&lt;/code&gt; takes a URL as a parameter and sanitizes it so that an attacker cannot inject a 'JavaScript:` URL for example, see official documentation on &lt;a href="https://angular.io/api/platform-browser/DomSanitizer#description" rel="noopener noreferrer"&gt;DomSanitizer&lt;/a&gt;. &lt;br&gt;
Now that we have this in place we should now see two icons in the browser.&lt;/p&gt;

&lt;p&gt;If we have a lot of icons to add this means lots of typing and lots of repetitive code, so lets refactor this some more. Start by removing all of this code (including the constructor) from the app.component.ts. This shouldn't really be in the app.component.ts file. Lets create another new module in the shared directory and call it &lt;code&gt;icon.module.ts&lt;/code&gt; and then add the following:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;typescript&lt;br&gt;
// Third Example - icon module&lt;br&gt;
import  {  NgModule  }  from  '@angular/core';&lt;br&gt;
import  {  DomSanitizer,  SafeResourceUrl  }  from  '@angular/platform-browser';&lt;br&gt;
import  {  MaterialModule  }  from  '../shared/material.module';&lt;br&gt;
import  {  MatIconRegistry  }  from  '@angular/material/icon';&lt;br&gt;
@NgModule({&lt;br&gt;
declarations:  [],&lt;br&gt;
imports:  [MaterialModule],&lt;br&gt;
exports:  [],&lt;br&gt;
providers:  []&lt;br&gt;
})&lt;br&gt;
export  class  IconModule  {&lt;br&gt;
private  path:  string  =  '../../assets/images'; // change this &lt;br&gt;
constructor(&lt;br&gt;
private  domSanitizer:  DomSanitizer,&lt;br&gt;
public  matIconRegistry:  MatIconRegistry&lt;br&gt;
)  {&lt;br&gt;
this.matIconRegistry&lt;br&gt;
.addSvgIcon('home',  this.setPath(&lt;/code&gt;${this.path}/home.svg&lt;code&gt;))&lt;br&gt;
.addSvgIcon('add',  this.setPath(&lt;/code&gt;${this.path}/file-plus.svg&lt;code&gt;));&lt;br&gt;
}&lt;br&gt;
private  setPath(url:  string):  SafeResourceUrl  {&lt;br&gt;
    return  this.domSanitizer.bypassSecurityTrustResourceUrl(url);&lt;br&gt;
    }&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;br&gt;
Overall, that's not too bad, we are only writing out the domSanitizer code once in the &lt;code&gt;private&lt;/code&gt; method but more importantly, all the code is out of the &lt;code&gt;app.component.ts&lt;/code&gt; file and is now a self contained module. If there are a lot of icons to add then this file will get a bit long, but the typing has gotten shorter (well, a little shorter at least). You could change the &lt;code&gt;constructor&lt;/code&gt;to iterate through a &lt;code&gt;.json&lt;/code&gt; file of image names, the path wouldn't change and could be a &lt;code&gt;const&lt;/code&gt;, which would only mean maintaining a &lt;code&gt;.json&lt;/code&gt; file with any new images. I might look at doing that in a follow up post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Don't forget to add this new icon.module.ts to the app.module.ts otherwise it won't work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the Mat-Icon component
&lt;/h2&gt;

&lt;p&gt;So how do we use the mat-icon component? As seen earlier in this post we add the following code to our app.components.html page.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`html&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;br&gt;
This is a very simple example is showing how to put a &lt;code&gt;home&lt;/code&gt; and an &lt;code&gt;add&lt;/code&gt; icon on a page/component. This is not too dissimilar to how we would use this component with web fonts, but we are now using the &lt;code&gt;svgIcon&lt;/code&gt; input property. The value we give to this input is the first parameter used in our call to register the &lt;code&gt;.addSvgIcon('home', ...)&lt;/code&gt; in this case &lt;code&gt;home&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now we have an icon in place, how do we change the colour of the icon when someone hovers over it for example. &lt;/p&gt;

&lt;h2&gt;
  
  
  Change the  icon colour
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A Home icon&lt;/strong&gt;.&lt;br&gt;
Example icon copy this into a file with the SVG extension.&lt;br&gt;
&lt;code&gt;&lt;/code&gt;&lt;code&gt;svg&lt;br&gt;
&amp;lt;svg version="1.1" width="24" height="24" viewBox="0 0 24 24"&amp;gt;&amp;lt;path d="M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z" fill="#00FFFF" /&amp;gt;&amp;lt;/svg&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;br&gt;
In the above XML, I've removed all the namespaces etc... The main part here is the &lt;code&gt;fill="#00FFFF"&lt;/code&gt; this sets the colour for the image, in this case to AquaMarine. &lt;/p&gt;

&lt;p&gt;If the &lt;code&gt;fill="#00..."&lt;/code&gt; property is not there and you want a different colour to the default black, then you can add it to the path as above, it's optional.&lt;/p&gt;

&lt;p&gt;I usually add the &lt;code&gt;fill&lt;/code&gt; property and set it to white and then change them in SCSS as and when required (but because this example had a white the background I used another colour). &lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`html&lt;br&gt;
!&amp;lt;-- Second HTML example --&amp;gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;!&amp;amp;lt;-- other code omitted for brevity --&amp;amp;gt; 
&amp;lt;a&amp;gt;

&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;br&gt;
Add a class to the mat-icon html tag, as above and then add the SCSS, for the class. So when a user hovers over the button the colour of the icon changes.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;scss&lt;br&gt;
.btn-icon  {&lt;br&gt;
    &amp;amp;:hover  {&lt;br&gt;
        path {&lt;br&gt;
            fill:  rgba(#00ffff,  1);&lt;br&gt;
            }&lt;br&gt;
        }&lt;br&gt;
    }&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Note&lt;/strong&gt; One thing I did forget to add to the app.component.ts above, was the following line: &lt;br&gt;
&lt;code&gt;encapsulation:  ViewEncapsulation.None&lt;/code&gt;, without this then the hover affect doesn't work. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;typescript&lt;br&gt;
@Component({&lt;br&gt;
selector:  'mat-icon-demo-root',&lt;br&gt;
templateUrl:  './app.component.html',&lt;br&gt;
styleUrls:  ['./app.component.scss'],&lt;br&gt;
encapsulation:  ViewEncapsulation.None,&lt;br&gt;
})&lt;br&gt;
export  class  AppComponent  {&lt;br&gt;
    title  =  'mat-icon-demo';&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;br&gt;
Enjoy! &lt;/p&gt;

&lt;p&gt;There is a github repo for this blog post at:&lt;br&gt;
&lt;a href="https://github.com/Anglebrackets-io/mat-icon-demo" rel="noopener noreferrer"&gt;https://github.com/Anglebrackets-io/mat-icon-demo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>angularmaterial</category>
      <category>typescript</category>
      <category>svg</category>
    </item>
  </channel>
</rss>
