<?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>Forem: Tryggvi Gylfason</title>
    <description>The latest articles on Forem by Tryggvi Gylfason (@tryggvigy).</description>
    <link>https://forem.com/tryggvigy</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F153887%2Fbb36a5b0-a5e7-4a78-8e40-8b5b0df908ec.jpg</url>
      <title>Forem: Tryggvi Gylfason</title>
      <link>https://forem.com/tryggvigy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/tryggvigy"/>
    <language>en</language>
    <item>
      <title>Big Text !== Heading</title>
      <dc:creator>Tryggvi Gylfason</dc:creator>
      <pubDate>Mon, 22 Jun 2020 00:35:26 +0000</pubDate>
      <link>https://forem.com/tryggvigy/big-text-heading-d92</link>
      <guid>https://forem.com/tryggvigy/big-text-heading-d92</guid>
      <description>&lt;p&gt;It's important to create a logical heading hierarchy on pages so users of assistive tech like screen readers can more easily understand and navigate around (screen readers can jump between headings) the page. Big text doesn't always mean the text should be a heading:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hRCR6aY2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hj4z7xqp6xdlktd3mpoy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hRCR6aY2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hj4z7xqp6xdlktd3mpoy.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Avoid associating the size of text with its status as a heading.&lt;/p&gt;

&lt;p&gt;Spin up a screen reader like VoiceOver, Jaws or NVDA and see for yourself if the headings actually make sense. &lt;/p&gt;

&lt;p&gt;In VoiceOver, a screen reader user will be presented with this in the rotor headings menu&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iDesCT6O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/g33lj64lcs5nbfth8kzj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iDesCT6O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/g33lj64lcs5nbfth8kzj.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check out &lt;a href="https://www.youtube.com/watch?v=5R-6WvAihms"&gt;Screen Reader Basics: VoiceOver&lt;/a&gt; from the A11ycasts series on YouTube. It's great!&lt;/p&gt;

&lt;p&gt;"heading level 2 Public Playlists" is announced right after "heading level 1 PROFILE" making it clear that Public Playlists is a subsection on the profile page.&lt;/p&gt;

&lt;p&gt;One of the best things you can do imo as developer getting into accessibility is learning how people actually user assistive technologies like screen readers. This helps you answer a lot of your own question around accessibility.&lt;/p&gt;

&lt;p&gt;Big text !== heading&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>html</category>
    </item>
    <item>
      <title>Invisible Yet Focusable</title>
      <dc:creator>Tryggvi Gylfason</dc:creator>
      <pubDate>Sun, 21 Jun 2020 01:25:57 +0000</pubDate>
      <link>https://forem.com/tryggvigy/invisible-yet-focusable-2oai</link>
      <guid>https://forem.com/tryggvigy/invisible-yet-focusable-2oai</guid>
      <description>&lt;p&gt;These two mixins are one of my favorites. Flexible and useful for creating accessible interfaces.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="cm"&gt;/*
* Hide only visually, but have it available for screen readers:
* https://snook.ca/archives/html_and_css/hiding-content-for-accessibility
*/&lt;/span&gt;
&lt;span class="k"&gt;@mixin&lt;/span&gt; &lt;span class="nf"&gt;screen-reader-only&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;pointer-events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@mixin&lt;/span&gt; &lt;span class="nf"&gt;undo-screen-reader-only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$position&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;unset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$position&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;pointer-events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&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;They are quite handy when hiding something visually.&lt;/p&gt;

&lt;p&gt;By using mixins rather than classes we can keep working in pure CSS when showing/hiding the elements instead of reaching for JS to toggle classes.&lt;/p&gt;

&lt;p&gt;Another benefit these mixins have (over &lt;code&gt;display:none&lt;/code&gt; and &lt;code&gt;visibility:hidden&lt;/code&gt;) is that the element can still receive focus, and when it does, we can undo the mixin to show the element. Again, no JS needed. In the cards below, try pressing Shift+Tab to jump to the play button on the previous card.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/tryggvigy/embed/vYLxKjP?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>css</category>
      <category>a11y</category>
    </item>
    <item>
      <title>Building Accessible Grids 2</title>
      <dc:creator>Tryggvi Gylfason</dc:creator>
      <pubDate>Sun, 21 Jun 2020 00:04:37 +0000</pubDate>
      <link>https://forem.com/tryggvigy/building-accessible-grids-2-34ha</link>
      <guid>https://forem.com/tryggvigy/building-accessible-grids-2-34ha</guid>
      <description>&lt;p&gt;Part 1: &lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/tryggvigy" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CttI8X_H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--eYC1-GpX--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/153887/bb36a5b0-a5e7-4a78-8e40-8b5b0df908ec.jpg" alt="tryggvigy"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/tryggvigy/building-accessible-grids-1-216j" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Building Accessible Grids 1&lt;/h2&gt;
      &lt;h3&gt;Tryggvi Gylfason ・ Jun 20 '20 ・ 4 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#a11y&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#html&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#react&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
 



&lt;p&gt;Here's what we'll build in this post. If you are reading on your phone I'm sorry, this wont work! This time we are focusing (😏) on keyboard support. &lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/tryggvigy/embed/rNxyeJV?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;It's a playlist!&lt;/p&gt;

&lt;p&gt;Let's quickly recap the first post. We want to build a &lt;em&gt;composite grid component&lt;/em&gt; that can be skipped with a single Tab key press and has keyboard support. Pressing an arrow key will navigate to the next interactive element.&lt;/p&gt;

&lt;p&gt;Let's build the playlist HTML.&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"grid"&lt;/span&gt; &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- header row --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid__header-row"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt; &lt;span class="na"&gt;aria-rowindex=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"columnheader"&lt;/span&gt; &lt;span class="na"&gt;aria-colindex=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Title&lt;span class="nt"&gt;&amp;lt;/button&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;div&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"columnheader"&lt;/span&gt; &lt;span class="na"&gt;aria-colindex=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Album&lt;span class="nt"&gt;&amp;lt;/button&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;div&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"columnheader"&lt;/span&gt; &lt;span class="na"&gt;aria-colindex=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Duration&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- regular row --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid__row"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt; &lt;span class="na"&gt;aria-rowindex=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"gridcell"&lt;/span&gt; &lt;span class="na"&gt;aria-colindex=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;Black Parade&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Beyoncé&lt;span class="nt"&gt;&amp;lt;/a&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;div&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"gridcell"&lt;/span&gt; &lt;span class="na"&gt;aria-colindex=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"gridcell"&lt;/span&gt; &lt;span class="na"&gt;aria-colindex=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      4:41
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"heart"&lt;/span&gt; &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&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;Add to your liked songs&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
        ♡
      &lt;span class="nt"&gt;&amp;lt;/button&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;/div&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- more rows ... --&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;2 out of 3 headers are buttons. Perhaps they are sortable.&lt;/li&gt;
&lt;li&gt;First column TITLE contains the song name and list of artists.&lt;/li&gt;
&lt;li&gt;Second column ALBUM contains the album link if any (songs can be singles)&lt;/li&gt;
&lt;li&gt;Third column DURATION contains the song duration but also a heart button to add the song to your liked songs. The heart should appear when the row is focused/hovered. When invisible it should still be able to receive focus!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key hierarchy of selectors is this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"grid"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;aria-rowindex&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
    &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;aria-colindex&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
        &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;button&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From any given button or link, we can traverse up its parent chain in the DOM and find which row and column index it belongs to. Let's move focus based on the current focus and the row and column indexes.&lt;/p&gt;

&lt;p&gt;The first thing we have to do is remove all the buttons and links from the natural tab order.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&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;.grid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Remove all buttons/links from the natural tab order&lt;/span&gt;
&lt;span class="nx"&gt;grid&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a:not([tabindex="0"]), button:not([tabindex="0"])&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="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&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;-1&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;Now the grid can be skipped with a single Tab key instead of the user having to tab through every link and button in the grid.&lt;/p&gt;

&lt;p&gt;Next, let's add a keydown handle to the grid and call a function to move the focus in the direction of the arrow keys.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&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&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="nx"&gt;e&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="c1"&gt;// Prevent scrolling&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowUp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowDown&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="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowUp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;moveFocus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;up&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowDown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;moveFocus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;down&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowLeft&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;moveFocus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowRight&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;moveFocus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&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="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;moveFocus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;direction&lt;/span&gt;&lt;span class="p"&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;hasFocusableElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ensureFocusableElementInGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;hasFocusableElement&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;up&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;focusUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;down&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;focusDown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;focusLeft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;focusRight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;grid&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 first thing we do before moving the focus is call &lt;code&gt;ensureFocusableElementInGrid&lt;/code&gt; to make sure that a focusable element is present in the grid.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;ensureFocusableElementInGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&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;firstElem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a, button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&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;currentFocusable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&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;[tabindex="0"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;firstElem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Happens if the grid does not contain any interactive elements.&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;currentFocusable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;currentFocusable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&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="k"&gt;return&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's look at&lt;/p&gt;

&lt;p&gt;&lt;code&gt;focusUp&lt;/code&gt; and &lt;code&gt;focusDown&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;find the currently focused element&lt;/li&gt;
&lt;li&gt;find the next cell in above/below row that has interactive elements&lt;/li&gt;
&lt;li&gt;If found, focus the first interactive element in the cell if going down and the last interactive element if going up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;focusLeft&lt;/code&gt; and &lt;code&gt;focusRight&lt;/code&gt; require an extra step:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;find the currently focused element&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;extra!&lt;/em&gt; focus the next interactive element in the cell if it exists.&lt;/li&gt;
&lt;li&gt;Otherwise, find the next cell in the current row that has interactive elements&lt;/li&gt;
&lt;li&gt;If found, focus the first interactive element in the cell if going right and the last interactive element if going left.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's a bit of code involved but it's quite reusable:&lt;br&gt;
&lt;iframe height="600" src="https://codepen.io/tryggvigy/embed/rNxyeJV?height=600&amp;amp;default-tab=js&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Here is a React version&lt;br&gt;
&lt;iframe src="https://codesandbox.io/embed/accessible-grid-inugu"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;I hope you can see how this can be extrapolated to lists or different kind of grids or tables. Maybe a spreadsheet for example.&lt;/p&gt;

&lt;p&gt;The same basic principles apply.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remove all interactive elements within the composite component from the tab order&lt;/li&gt;
&lt;li&gt;On arrow key press, find the next element that should get focus&lt;/li&gt;
&lt;li&gt;Move the focus using &lt;a href="https://www.w3.org/TR/wai-aria-practices-1.1/#kbd_roving_tabindex"&gt;roving tabindex&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Edit&lt;/strong&gt;&lt;br&gt;
It's worth mentioning the &lt;a href="https://engineering.fb.com/web/aria-grid-supporting-nonvisual-layout-and-keyboard-traversal/"&gt;ARIA Grid: Supporting nonvisual layout and keyboard traversal&lt;/a&gt; article from Facebook that touches on very similar things.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>webdev</category>
      <category>html</category>
      <category>react</category>
    </item>
    <item>
      <title>Building Accessible Grids 1</title>
      <dc:creator>Tryggvi Gylfason</dc:creator>
      <pubDate>Sat, 20 Jun 2020 21:09:35 +0000</pubDate>
      <link>https://forem.com/tryggvigy/building-accessible-grids-1-216j</link>
      <guid>https://forem.com/tryggvigy/building-accessible-grids-1-216j</guid>
      <description>&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interactive Element&lt;/strong&gt;: Any html element that can receive focus. For example the &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;button&lt;/code&gt; and &lt;code&gt;input&lt;/code&gt; elements are interactive by default. Any element can be made interactive by adding a &lt;code&gt;tabindex="0&lt;/code&gt; attribute to it. Similarly, any element can be made non-interactive by using &lt;code&gt;tabindex="-1"&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Part 2: &lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/tryggvigy" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CttI8X_H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--eYC1-GpX--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/153887/bb36a5b0-a5e7-4a78-8e40-8b5b0df908ec.jpg" alt="tryggvigy"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/tryggvigy/building-accessible-grids-2-34ha" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Building Accessible Grids 2&lt;/h2&gt;
      &lt;h3&gt;Tryggvi Gylfason ・ Jun 21 '20 ・ 4 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#a11y&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#html&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#react&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
 




&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Building accessible lists and grids on the web is unfortunately difficult.&lt;/p&gt;

&lt;p&gt;In frameworks like React, where a Grid component does not necessarily know about or control its children, it can be even harder.&lt;/p&gt;

&lt;p&gt;Using a &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; and associated child elements is the right choice sometimes. Other times it's not. These elements are awkward to lay out and more importantly impose limitations on the DOM structure . More complex grids may need to use &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Grids should be navigable with arrow keys. You can read about the expected keyboard support for data grids in the &lt;a href="https://www.w3.org/TR/wai-aria-practices/examples/grid/dataGrids.html"&gt;WAI ARIA practices&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Grids should be skippable with a single Tab key press. They are &lt;em&gt;composite components&lt;/em&gt;. You can read more in this excellent thread &lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--6kf1wOdo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/812765322004443140/PPaqFF07_normal.jpg" alt="Devon Govett profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Devon Govett
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @devongovett
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      There's a very common misconception that pure HTML with no JS is somehow more accessible. Unless you're building a very basic content site with no interactivity, that's just plain false!&lt;br&gt;&lt;br&gt;Until the platform improves, you need JS to properly implement keyboard navigation.&lt;br&gt;&lt;br&gt;Thread.
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      18:15 PM - 20 Jun 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1274405491238531072" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1274405491238531072" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1274405491238531072" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One way to add keyboard support to grids is to base the navigation on the current state of the DOM instead of the framework state (React and Vue for example).&lt;/p&gt;

&lt;h2&gt;
  
  
  Aria attribute DOM based way
&lt;/h2&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;role=&lt;/span&gt;&lt;span class="s"&gt;"grid"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ...
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"presentation"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    ...
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt; &lt;span class="na"&gt;aria-rowindex=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      ...
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"presentation"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        ...
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"gridcell"&lt;/span&gt; &lt;span class="na"&gt;aria-colindex=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          ...
          &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/button&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;/div&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;/div&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;Let's walk through how this is done on a high level. In the next post we'll go more concrete and actually implement this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create the root grid element

&lt;ul&gt;
&lt;li&gt;Give grid the &lt;code&gt;role="grid"&lt;/code&gt; attribute. &lt;/li&gt;
&lt;li&gt;Give grid &lt;code&gt;tab-index="0"&lt;/code&gt; so users can navigate to it (remember to add some CSS focus styles. E.g. a border or an outline that indicates the grid is in focus)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Add rows to the grid

&lt;ul&gt;
&lt;li&gt;Give row &lt;code&gt;role="row"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Give row the correct &lt;code&gt;aria-rowindex&lt;/code&gt; (starts with 1)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Add cells (a row,col combination) to the rows

&lt;ul&gt;
&lt;li&gt;give cell either a &lt;code&gt;role="columnheader"&lt;/code&gt; if it's a column header, or a &lt;code&gt;role="gridcell"&lt;/code&gt; if it's a regular cell&lt;/li&gt;
&lt;li&gt;Give cell the correct &lt;code&gt;aria-colindex&lt;/code&gt; attribute (starts with 1)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Make sure all elements between &lt;code&gt;grid&lt;/code&gt; -&amp;gt; &lt;code&gt;row&lt;/code&gt; -&amp;gt; &lt;code&gt;gridcell&lt;/code&gt; that do not have an explicit role, get &lt;code&gt;role="presentation"&lt;/code&gt; added to them.

&lt;ul&gt;
&lt;li&gt;This removes those elements from the accessibility tree and ensures screen readers can correctly interpret and navigate the grid.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Make sure that &lt;strong&gt;all&lt;/strong&gt; interactive elements in the grid have &lt;code&gt;tabindex="-1"&lt;/code&gt; attribute

&lt;ul&gt;
&lt;li&gt;This removes them from the natural tab order of the page and ensures that users can jump over the whole grid with a single Tab key press if they want to skip it.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Add a keydown listener to the root grid element (&lt;code&gt;role="grid"&lt;/code&gt;) and respond to key presses like the arrow keys.&lt;/li&gt;
&lt;li&gt;Once an arrow key is pressed we choose what to focus next in the DOM. More on that in a bit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The DOM based approach gives us the benefit of the focus management being transparent to the grid and its child components. If a new interactive element is added to a cell in the grid it will work without modification.&lt;/p&gt;

&lt;p&gt;The downside is frameworks like React can change the DOM at any time. Because of this, the first thing that is done in keydown event handler before moving any focus is ensuring that a focusable element (with &lt;code&gt;tabindex="0"&lt;/code&gt; attribute) actually exists in the grid. If it doesn't exist, fall back to the first interactive element in the grid. &lt;/p&gt;

&lt;p&gt;This should not happen much unless your framework is adding/removing cells from the DOM frequently. I have not seen problems with this in a virtually scrolled, sortable/filterable grid that loads more data on scroll so I expect this is will work well in most cases.&lt;/p&gt;
&lt;h3&gt;
  
  
  Roving tabindex
&lt;/h3&gt;

&lt;p&gt;Now that we have found the currently focused element in the grid we need to decide which element to focus next. If we find that element we move the focus to that element:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;tabindex="0"&lt;/code&gt; -&amp;gt; &lt;code&gt;tabindex="-1"&lt;/code&gt; on the currently focused element&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tabindex="-1"&lt;/code&gt; -&amp;gt; &lt;code&gt;tabindex="0"&lt;/code&gt; on the next element to focus&lt;/li&gt;
&lt;li&gt;call &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLOrForeignElement/focus"&gt;&lt;code&gt;focus()&lt;/code&gt;&lt;/a&gt; on the next element.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This technique is called &lt;em&gt;roving tabindex&lt;/em&gt;. You can read more about it in the &lt;a href="https://www.w3.org/TR/wai-aria-practices-1.1/#kbd_roving_tabindex"&gt;WAI-ARIA Authoring Practices&lt;/a&gt; or check out &lt;a href="https://www.youtube.com/watch?v=uCIC2LNt0bk"&gt;this excellent video&lt;/a&gt; from the A11ycasts series on the Google Chrome Developers YouTube channel.&lt;/p&gt;
&lt;h2&gt;
  
  
  Moving focus to the next element
&lt;/h2&gt;

&lt;p&gt;So how do we find the next element to focus in a reliable way? This depends on what the grid actually is. If the grid is a spreadsheet it might make sense to focus the next cell element. But if the grid is a collection of links and buttons structured in a table - for example a playlist on Spotify, then jumping to the next interactive element would make sense.&lt;/p&gt;

&lt;p&gt;In the next post I'll go through a concrete implementation keyboard support of a grid containing &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;button&lt;/code&gt; elements.&lt;/p&gt;
&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;As long as&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This hierarchy of selectors is in place
&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="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"grid"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;aria-rowindex&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
    &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;aria-colindex&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
        &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;button&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;aria-rowindex&lt;/code&gt;/&lt;code&gt;aria-colindex&lt;/code&gt; are 1-based and sequential&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then the keyboard navigation will work. Additional wrapper elements between each layer do not matter.&lt;/p&gt;

&lt;p&gt;Building accessible grids and lists can be daunting and too many don't bother. Fortunately adding keyboard support can be done in a generic, framework and use-case agnostic, repeatable way that is likely to survive future changes to the components without breaking or needing modification.&lt;/p&gt;

&lt;p&gt;In the next post we'll implement a grid with proper keyboard support using this method.&lt;/p&gt;

&lt;p&gt;Part 2: &lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/tryggvigy" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CttI8X_H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--eYC1-GpX--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/153887/bb36a5b0-a5e7-4a78-8e40-8b5b0df908ec.jpg" alt="tryggvigy"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/tryggvigy/building-accessible-grids-2-34ha" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Building Accessible Grids 2&lt;/h2&gt;
      &lt;h3&gt;Tryggvi Gylfason ・ Jun 21 '20 ・ 4 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#a11y&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#html&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#react&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;






</description>
      <category>a11y</category>
      <category>webdev</category>
      <category>html</category>
      <category>react</category>
    </item>
  </channel>
</rss>
