Accessible page title in a single-page React application

Over the summer, we, at N26, got the company Temesis to audit the accessibility of our web application. As part of their comprehensive and exhaustive report, we learnt that we were not handling page titles properly.

Traditionally, following a link causes the page to reload with the content of the new page. This makes it possible for screen-readers to pick up on the new page title and announce it.

With single-page applications using a JavaScript-powered routing system, only the content of the page tends to be reloaded in order to improve the perceived performance of the page.

In this article, I will share what I learnt from Temesis and how to make sure the title of your React SPAs is accessible to assistive technologies.

Overview

We will build a teeny-tiny React application with react-router and react-helmet. Our application will consist of:

  • A top-level component rendering a navigation and the router.
  • Three different pages served under different paths.
  • A “page title announcer”, the core topic of our article.

The main idea is that every page will define its own title. The page title announcer listens for page changes, stores the page title and renders it in a visually hidden paragraph which gets focused. This enables screen-readers to announce the new page title.

You can already look at the code on CodeSandbox.

Boilerplate code

To begin with, let’s create our page components. Each page is a simply React component rendering a

element, and a element with react-helmet.

import React from 'react'
import { Helmet } from 'react-helmet'

const Home = () => (
>
h1>Homeh1>
Helmet>
title>Hometitle>
Helmet>
>
)

const About = () => (
>
h1>Abouth1>
Helmet>
title>Abouttitle>
Helmet>
>
)

const Dashboard = () => (
>
h1>Dashboardh1>
Helmet>
title>Dashboardtitle>
Helmet>
>
)

Now, let’s create a top-level component which will handle the routing to these different pages. To keep it simple, let’s take it (almost) as is from the basic example of react-router. It is our component (described in the next section), a navigation, and a router.

const Root = () => (
Router>
>
TitleAnnouncer />

nav role='navigation'>
Link to='/'>HomeLink>
Link to='/about'>AboutLink>
Link to='/dashboard'>DashboardLink>
nav>

hr />

Switch>
Route exact path='/'>
Home />
Route>
Route path='/about'>
About />
Route>
Route path='/dashboard'>
Dashboard />
Route>
Switch>
>
Router>
)

Title announcer

The last missing piece of the puzzle is the actual title announcer. It does a few things:

  • It holds the page title in a local state.
  • It renders said title in a visually hidden paragraph (here with the .sr-only class).
  • It listens to Helmet data change to update the local state.
  • It listens for page change to focus the hidden paragraph (hence the tabIndex={-1}).
import React from 'react'
import { useLocation } from 'react-helmet'
import { Helmet } from 'react-helmet'

const TitleAnnouncer = props => {
const [title, setTitle] = React.useState('')
const titleRef = React.createRef()
const { pathname } = useLocation()
const onHelmetChange = ({ title }) => setTitle(title)

React.useEffect(() => {
if (titleRef.current) titleRef.current.focus()
}, [pathname])

return (
>
p tabIndex={-1} ref={titleRef} className='sr-only'>
{title}
p>

Helmet onChangeClientState={onHelmetChange} />
>
)
}

Wrapping up

That is all that is needed to handle page titles in an accessible way in a single-page React application. The react-router and react-helmet libraries are not necessary either, and the same pattern should be applicable regardless of the library (or lack thereof) in use.

Note that if you have a simple application and can guarantee there is always a relevant

element (independently of loading states, query errors and such), another, possibly simpler solution arises. It should be possible to skip that hidden element altogether, and focus the

element instead (still with tabIndex={-1}). This solution could not scale for us as we have hundreds of sometimes complex and dynamic pages, some with a visible

element, some with a hidden one, and so on.

Feel free to play with the code on CodeSandbox.

Over the summer, we, at N26, got the company Temesis to audit the accessibility of our web application. As part of their comprehensive and exhaustive report, we learnt that we were not handling page titles properly.

Traditionally, following a link causes the page to reload with the content of the new page. This makes it possible for screen-readers to pick up on the new page title and announce it.

With single-page applications using a JavaScript-powered routing system, only the content of the page tends to be reloaded in order to improve the perceived performance of the page.

In this article, I will share what I learnt from Temesis and how to make sure the title of your React SPAs is accessible to assistive technologies.

Overview

We will build a teeny-tiny React application with react-router and react-helmet. Our application will consist of:

  • A top-level component rendering a navigation and the router.
  • Three different pages served under different paths.
  • A “page title announcer”, the core topic of our article.

The main idea is that every page will define its own title. The page title announcer listens for page changes, stores the page title and renders it in a visually hidden paragraph which gets focused. This enables screen-readers to announce the new page title.

You can already look at the code on CodeSandbox.

Boilerplate code

To begin with, let’s create our page components. Each page is a simply React component rendering a

element, and a </code> element with <code>react-helmet</code>.</p> <pre class="language-jsx"><code class="language-jsx"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span><br><span class="token keyword">import</span> <span class="token punctuation">{</span> Helmet <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react-helmet'</span><br><br><span class="token keyword">const</span> <span class="token function-variable function">Home</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><br> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text">Home</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Helmet</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>title</span><span class="token punctuation">></span></span><span class="token plain-text">Home</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>title</span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Helmet</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span></span><span class="token punctuation">></span></span><br><span class="token punctuation">)</span><br><br><span class="token keyword">const</span> <span class="token function-variable function">About</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><br> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text">About</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Helmet</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>title</span><span class="token punctuation">></span></span><span class="token plain-text">About</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>title</span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Helmet</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span></span><span class="token punctuation">></span></span><br><span class="token punctuation">)</span><br><br><span class="token keyword">const</span> <span class="token function-variable function">Dashboard</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><br> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text">Dashboard</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Helmet</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>title</span><span class="token punctuation">></span></span><span class="token plain-text">Dashboard</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>title</span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Helmet</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span></span><span class="token punctuation">></span></span><br><span class="token punctuation">)</span></code></pre> <p>Now, let’s create a top-level component which will handle the routing to these different pages. To keep it simple, let’s take it (almost) as is from <a href="https://reacttraining.com/react-router/web/example/basic">the basic example of <code>react-router</code></a>. It is our <a href="https://hugogiraudel.com/2020/01/15/accessible-title-in-a-single-page-react-application/#title-announcer"><code><TitleAnnouncer></code> component</a> (described in the next section), a navigation, and a router.</p> <pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">Root</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><br> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Router</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">TitleAnnouncer</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>nav</span> <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>navigation<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Link</span></span> <span class="token attr-name">to</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>/<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><span class="token plain-text">Home</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Link</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Link</span></span> <span class="token attr-name">to</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>/about<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><span class="token plain-text">About</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Link</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Link</span></span> <span class="token attr-name">to</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>/dashboard<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><span class="token plain-text">Dashboard</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Link</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>nav</span><span class="token punctuation">></span></span><span class="token plain-text"><br><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>hr</span> <span class="token punctuation">/></span></span><span class="token plain-text"><br><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Switch</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Route</span></span> <span class="token attr-name">exact</span> <span class="token attr-name">path</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>/<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Home</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Route</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Route</span></span> <span class="token attr-name">path</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>/about<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">About</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Route</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Route</span></span> <span class="token attr-name">path</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>/dashboard<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Dashboard</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Route</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Switch</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Router</span></span><span class="token punctuation">></span></span><br><span class="token punctuation">)</span></code></pre> <h2 id="title-announcer">Title announcer</h2> <p>The last missing piece of the puzzle is the actual title announcer. It does a few things:</p> <ul> <li>It holds the page title in a local state.</li> <li>It renders said title in a visually hidden paragraph (here with the <a href="https://hugogiraudel.com/2016/10/13/css-hide-and-seek/#wrapping-things-up"><code>.sr-only</code> class</a>).</li> <li>It listens to Helmet data change to update the local state.</li> <li>It listens for page change to focus the hidden paragraph (hence the <code>tabIndex={-1}</code>).</li> </ul> <pre class="language-jsx"><code class="language-jsx"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span><br><span class="token keyword">import</span> <span class="token punctuation">{</span> useLocation <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react-helmet'</span><br><span class="token keyword">import</span> <span class="token punctuation">{</span> Helmet <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react-helmet'</span><br><br><span class="token keyword">const</span> <span class="token function-variable function">TitleAnnouncer</span> <span class="token operator">=</span> <span class="token parameter">props</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br> <span class="token keyword">const</span> <span class="token punctuation">[</span>title<span class="token punctuation">,</span> setTitle<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><br> <span class="token keyword">const</span> titleRef <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">createRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br> <span class="token keyword">const</span> <span class="token punctuation">{</span> pathname <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useLocation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br> <span class="token keyword">const</span> <span class="token function-variable function">onHelmetChange</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> title <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setTitle</span><span class="token punctuation">(</span>title<span class="token punctuation">)</span><br><br> React<span class="token punctuation">.</span><span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br> <span class="token keyword">if</span> <span class="token punctuation">(</span>titleRef<span class="token punctuation">.</span>current<span class="token punctuation">)</span> titleRef<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>pathname<span class="token punctuation">]</span><span class="token punctuation">)</span><br><br> <span class="token keyword">return</span> <span class="token punctuation">(</span><br> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span> <span class="token attr-name">tabIndex</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">}</span></span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>titleRef<span class="token punctuation">}</span></span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>sr-only<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token punctuation">{</span>title<span class="token punctuation">}</span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span><span class="token plain-text"><br><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Helmet</span></span> <span class="token attr-name">onChangeClientState</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onHelmetChange<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span></span><span class="token punctuation">></span></span><br> <span class="token punctuation">)</span><br><span class="token punctuation">}</span></code></pre> <h2 id="wrapping-up">Wrapping up</h2> <p>That is all that is needed to handle page titles in an accessible way in a single-page React application. The <code>react-router</code> and <code>react-helmet</code> libraries are not necessary either, and the same pattern should be applicable regardless of the library (or lack thereof) in use.</p> <p>Note that if you have a simple application and can guarantee there is always a relevant <code><h1></code> element (independently of loading states, query errors and such), another, possibly simpler solution arises. It should be possible to skip that hidden element altogether, and focus the <code><h1></code> element instead (still with <code>tabIndex={-1}</code>). This solution could not scale for us as we have hundreds of sometimes complex and dynamic pages, some with a visible <code><h1></code> element, some with a hidden one, and so on.</p> <p>Feel free to <a href="https://codesandbox.io/s/accessible-page-title-in-single-page-react-applications-u9e52">play with the code</a> on CodeSandbox.</p> <br> <style> .ap-print-btn-comment { display:none; } .single .ap-print-btn-comment { display:block; } #respond { visibility:hidden; } .entry-content .ap-print-btn { display:none!important; } .entry-content .singleshow .ap-print-btn { display:block!important; } /* #respond { display:none; } */ </style> <script> function showhidecommentbox() { var x = document.getElementById("respond"); if (x.style.visibility === "visible") { x.style.visibility = "hidden"; } else { x.style.visibility = "visible"; } /* if (x.style.display === "block") { x.style.display = "none"; } else { x.style.display = "block"; } */ } </script> <div class="singleshow"> <a href="?printer_app=1" class="ap-print-btn" style=" padding: 15px; margin-top: 10px; width: 140px; text-align: center; border-radius: 3px; font-size: 14px; box-shadow: none !important; background-color: ; color: #ffffff;float:left; margin-right:10px;text-decoration: none;">Print</a> <a onclick="pop()" class="ap-print-btn ap-print-btn-comment" style="cursor:pointer; padding: 15px; margin-top: 10px; width: 140px; text-align: center; border-radius: 3px; font-size: 14px; box-shadow: none !important; background-color: ; color: #ffffff;float:left; margin-right:10px;text-decoration: none;">Share</a> <a onclick="popcomment()" class="ap-print-btn ap-print-btn-comment" style="cursor:pointer; padding: 15px; margin-top: 10px; width: 140px; text-align: center; border-radius: 3px; font-size: 14px; box-shadow: none !important; background-color: ; color: #ffffff;float:left; margin-right:10px;text-decoration: none;">Comment</a> <a onclick="popcite()" class="ap-print-btn ap-print-btn-comment" style="cursor:pointer; padding: 15px; margin-top: 10px; width: 140px; text-align: center; border-radius: 3px; font-size: 14px; box-shadow: none !important; background-color: ; color: #ffffff;float:left; margin-right:10px;text-decoration: none;">Cite</a> <a onclick="popupload()" class="ap-print-btn ap-print-btn-comment" style="cursor:pointer; padding: 15px; margin-top: 10px; width: 140px; text-align: center; border-radius: 3px; font-size: 14px; box-shadow: none !important; background-color: ; color: #ffffff;float:left; margin-right:10px;text-decoration: none;">Upload</a> <a onclick="poptranslate()" class="ap-print-btn ap-print-btn-comment" style="cursor:pointer; padding: 15px; margin-top: 10px; width: 140px; text-align: center; border-radius: 3px; font-size: 14px; box-shadow: none !important; background-color: ; color: #ffffff;float:left; margin-right:10px;text-decoration: none;">Translate</a> <script> function pop() { var popup = document.getElementById('sharepopup'); popup.classList.toggle('show'); } function popcite() { var popup = document.getElementById('citationarea'); popup.classList.toggle('show'); } function popupload() { var popup = document.getElementById('uploadarea'); popup.classList.toggle('show'); } function poptranslate() { var popup = document.getElementById('translatearea'); popup.classList.toggle('show'); } function popcomment() { var popup = document.getElementById('commentarea'); popup.classList.toggle('show'); var x = document.getElementById("respond"); if (x.style.visibility === "visible") { x.style.visibility = "hidden"; } else { x.style.visibility = "visible"; } } </script> <style> .show{ display: grid !important; grid-template-columns: 70px auto; width: 100%; height: 100%; } #respond { margin-left: -40px; } .popup { display: inline-block; } .popup .popuptext { visibility: hidden; background-color: #000000; color: #fff; border-radius: 3px; padding:10px; position:relative; margin:10px 0; } .popuptext { background-color: #000000; color: #fff; border-radius: 3px; padding:10px; position:relative; margin:10px 0; } .popup { width: 100%; } .popup .show { visibility: visible; } #commentarea{ display: none; } #citationarea { display: none; } #commentarea .show{ display: inline !important; visibility: visible !important; background-color: #ffdb14; } #citationarea { display: none; } #citationarea .show { display: inline !important; visibility: visible !important; background-color: #ffdb14; } #uploadarea { display: none; } #uploadarea .show { display: inline !important; visibility: visible !important; background-color: #ffdb14; } #translatearea { display: none; } #translatearea .show { display: inline !important; visibility: visible !important; background-color: #ffdb14; } .sharedashicons { color: white; text-decoration: none; padding: 5px 0; } #translatearea textarea { max-width: 580px; min-width: 100% !important; } </style> <div class="popup"> <span class="popuptext" id="sharepopup"> <div class="sharelogo" id="emailsharelogo"><a href="mailto:info@example.com?&subject=Accessible page title in a single-page React application&body=https://www.scien.cx/2020/01/15/accessible-page-title-in-a-single-page-react-application/"><span class="sharedashicons dashicons dashicons-email"></span></a></div> <div class="sharelogo" id="facebooksharelogo"><a href="https://www.facebook.com/sharer/sharer.php?u=https://www.scien.cx/2020/01/15/accessible-page-title-in-a-single-page-react-application/"><span class="sharedashicons dashicons dashicons-facebook"></span></a></div> <div class="sharelogo" id="twittersharelogo"><a href="https://twitter.com/intent/tweet?url=https://www.scien.cx/2020/01/15/accessible-page-title-in-a-single-page-react-application/&text=Accessible page title in a single-page React application"><span class="sharedashicons dashicons dashicons-twitter"></span></a></div> <div class="sharelogo" id="urlsharelogo"><a href="https://twitter.com/intent/tweet?url=https://www.scien.cx/2020/01/15/accessible-page-title-in-a-single-page-react-application/&text=Accessible page title in a single-page React application"><span class="sharedashicons dashicons dashicons-admin-links" onclick="myFunction();"></span></a></div> </span> </div> <div class="commentpopup" style"width: 100%;"> <span class="" id="commentarea"> <img src="https://www.radiofree.org/wp-content/plugins/print-app/icon.jpg" width="100%"> <div class="comments-wrapper"> </div><!-- .comments-wrapper --> </span> </div> <div class="citationpopup" style"width: 100%;"> <span class="popuptext" id="citationarea"> CITATION GOES HERE CITATION GOES HERE </span> </div> <div class="citationpopup" style"width: 100%;"> <span class="popuptext" id="uploadarea"> </span> </div> <div class="translatepopup" style"width: 100%;"> <span class="popuptext" id="translatearea"> <form action="/action_page.php"> Select a language: <select data-placeholder="Choose a Language..."> <option value="AF">Afrikaans</option> <option value="SQ">Albanian</option> <option value="AR">Arabic</option> <option value="HY">Armenian</option> <option value="EU">Basque</option> <option value="BN">Bengali</option> <option value="BG">Bulgarian</option> <option value="CA">Catalan</option> <option value="KM">Cambodian</option> <option value="ZH">Chinese (Mandarin)</option> <option value="HR">Croatian</option> <option value="CS">Czech</option> <option value="DA">Danish</option> <option value="NL">Dutch</option> <option value="EN">English</option> <option value="ET">Estonian</option> <option value="FJ">Fiji</option> <option value="FI">Finnish</option> <option value="FR">French</option> <option value="KA">Georgian</option> <option value="DE">German</option> <option value="EL">Greek</option> <option value="GU">Gujarati</option> <option value="HE">Hebrew</option> <option value="HI">Hindi</option> <option value="HU">Hungarian</option> <option value="IS">Icelandic</option> <option value="ID">Indonesian</option> <option value="GA">Irish</option> <option value="IT">Italian</option> <option value="JA">Japanese</option> <option value="JW">Javanese</option> <option value="KO">Korean</option> <option value="LA">Latin</option> <option value="LV">Latvian</option> <option value="LT">Lithuanian</option> <option value="MK">Macedonian</option> <option value="MS">Malay</option> <option value="ML">Malayalam</option> <option value="MT">Maltese</option> <option value="MI">Maori</option> <option value="MR">Marathi</option> <option value="MN">Mongolian</option> <option value="NE">Nepali</option> <option value="NO">Norwegian</option> <option value="FA">Persian</option> <option value="PL">Polish</option> <option value="PT">Portuguese</option> <option value="PA">Punjabi</option> <option value="QU">Quechua</option> <option value="RO">Romanian</option> <option value="RU">Russian</option> <option value="SM">Samoan</option> <option value="SR">Serbian</option> <option value="SK">Slovak</option> <option value="SL">Slovenian</option> <option value="ES">Spanish</option> <option value="SW">Swahili</option> <option value="SV">Swedish </option> <option value="TA">Tamil</option> <option value="TT">Tatar</option> <option value="TE">Telugu</option> <option value="TH">Thai</option> <option value="BO">Tibetan</option> <option value="TO">Tonga</option> <option value="TR">Turkish</option> <option value="UK">Ukrainian</option> <option value="UR">Urdu</option> <option value="UZ">Uzbek</option> <option value="VI">Vietnamese</option> <option value="CY">Welsh</option> <option value="XH">Xhosa</option> </select> <textarea name="paragraph_text" style="::-moz-selection { background: yellow; }; ::selection { background: yellow; };" cols="50" rows="10"><p>Over the summer, we, at N26, got the company Temesis to audit the accessibility of our web application. As part of their comprehensive and exhaustive report, we learnt that we were not handling page titles properly.</p> <p>Traditionally, following a link causes the page to reload with the content of the new page. This makes it possible for screen-readers to pick up on the new page title and announce it.</p> <p>With single-page applications using a JavaScript-powered routing system, only the content of the page tends to be reloaded in order to improve the perceived performance of the page.</p> <p>In this article, I will share what I learnt from Temesis and how to make sure the title of your React <abbr title='Single-Page Applications'>SPAs</abbr> is accessible to assistive technologies.</p> <ul> <li><a href="https://hugogiraudel.com/2020/01/15/accessible-title-in-a-single-page-react-application/#overview">Overview</a></li> <li><a href="https://hugogiraudel.com/2020/01/15/accessible-title-in-a-single-page-react-application/#boilerplate-code">Boilerplate code</a></li> <li><a href="https://hugogiraudel.com/2020/01/15/accessible-title-in-a-single-page-react-application/#title-announcer">Title announcer</a></li> <li><a href="https://hugogiraudel.com/2020/01/15/accessible-title-in-a-single-page-react-application/#wrapping-up">Wrapping up</a></li> </ul> <h2 id="overview">Overview</h2> <p>We will build a teeny-tiny React application with <a href="https://reacttraining.com/react-router"><code>react-router</code></a> and <a href="https://github.com/nfl/react-helmet"><code>react-helmet</code></a>. Our application will consist of:</p> <ul> <li>A top-level component rendering a navigation and the router.</li> <li>Three different pages served under different paths.</li> <li>A “page title announcer”, the core topic of our article.</li> </ul> <p>The main idea is that every page will define its own title. The page title announcer listens for page changes, stores the page title and renders it in a visually hidden paragraph which gets focused. This enables screen-readers to announce the new page title.</p> <p>You can already <a href="https://codesandbox.io/s/accessible-page-title-in-single-page-react-applications-u9e52">look at the code</a> on CodeSandbox.</p> <h2 id="boilerplate-code">Boilerplate code</h2> <p>To begin with, let’s create our page components. Each page is a simply React component rendering a <code><h1></code> element, and a <code><title></code> element with <code>react-helmet</code>.</p> <pre class="language-jsx"><code class="language-jsx"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span><br><span class="token keyword">import</span> <span class="token punctuation">{</span> Helmet <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react-helmet'</span><br><br><span class="token keyword">const</span> <span class="token function-variable function">Home</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><br> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text">Home</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Helmet</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>title</span><span class="token punctuation">></span></span><span class="token plain-text">Home</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>title</span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Helmet</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span></span><span class="token punctuation">></span></span><br><span class="token punctuation">)</span><br><br><span class="token keyword">const</span> <span class="token function-variable function">About</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><br> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text">About</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Helmet</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>title</span><span class="token punctuation">></span></span><span class="token plain-text">About</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>title</span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Helmet</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span></span><span class="token punctuation">></span></span><br><span class="token punctuation">)</span><br><br><span class="token keyword">const</span> <span class="token function-variable function">Dashboard</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><br> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text">Dashboard</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Helmet</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>title</span><span class="token punctuation">></span></span><span class="token plain-text">Dashboard</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>title</span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Helmet</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span></span><span class="token punctuation">></span></span><br><span class="token punctuation">)</span></code></pre> <p>Now, let’s create a top-level component which will handle the routing to these different pages. To keep it simple, let’s take it (almost) as is from <a href="https://reacttraining.com/react-router/web/example/basic">the basic example of <code>react-router</code></a>. It is our <a href="https://hugogiraudel.com/2020/01/15/accessible-title-in-a-single-page-react-application/#title-announcer"><code><TitleAnnouncer></code> component</a> (described in the next section), a navigation, and a router.</p> <pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">Root</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><br> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Router</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">TitleAnnouncer</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>nav</span> <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>navigation<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Link</span></span> <span class="token attr-name">to</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>/<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><span class="token plain-text">Home</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Link</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Link</span></span> <span class="token attr-name">to</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>/about<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><span class="token plain-text">About</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Link</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Link</span></span> <span class="token attr-name">to</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>/dashboard<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><span class="token plain-text">Dashboard</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Link</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>nav</span><span class="token punctuation">></span></span><span class="token plain-text"><br><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>hr</span> <span class="token punctuation">/></span></span><span class="token plain-text"><br><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Switch</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Route</span></span> <span class="token attr-name">exact</span> <span class="token attr-name">path</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>/<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Home</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Route</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Route</span></span> <span class="token attr-name">path</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>/about<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">About</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Route</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Route</span></span> <span class="token attr-name">path</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>/dashboard<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Dashboard</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Route</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Switch</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Router</span></span><span class="token punctuation">></span></span><br><span class="token punctuation">)</span></code></pre> <h2 id="title-announcer">Title announcer</h2> <p>The last missing piece of the puzzle is the actual title announcer. It does a few things:</p> <ul> <li>It holds the page title in a local state.</li> <li>It renders said title in a visually hidden paragraph (here with the <a href="https://hugogiraudel.com/2016/10/13/css-hide-and-seek/#wrapping-things-up"><code>.sr-only</code> class</a>).</li> <li>It listens to Helmet data change to update the local state.</li> <li>It listens for page change to focus the hidden paragraph (hence the <code>tabIndex={-1}</code>).</li> </ul> <pre class="language-jsx"><code class="language-jsx"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span><br><span class="token keyword">import</span> <span class="token punctuation">{</span> useLocation <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react-helmet'</span><br><span class="token keyword">import</span> <span class="token punctuation">{</span> Helmet <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react-helmet'</span><br><br><span class="token keyword">const</span> <span class="token function-variable function">TitleAnnouncer</span> <span class="token operator">=</span> <span class="token parameter">props</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br> <span class="token keyword">const</span> <span class="token punctuation">[</span>title<span class="token punctuation">,</span> setTitle<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><br> <span class="token keyword">const</span> titleRef <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">createRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br> <span class="token keyword">const</span> <span class="token punctuation">{</span> pathname <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useLocation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br> <span class="token keyword">const</span> <span class="token function-variable function">onHelmetChange</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> title <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setTitle</span><span class="token punctuation">(</span>title<span class="token punctuation">)</span><br><br> React<span class="token punctuation">.</span><span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br> <span class="token keyword">if</span> <span class="token punctuation">(</span>titleRef<span class="token punctuation">.</span>current<span class="token punctuation">)</span> titleRef<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>pathname<span class="token punctuation">]</span><span class="token punctuation">)</span><br><br> <span class="token keyword">return</span> <span class="token punctuation">(</span><br> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span> <span class="token attr-name">tabIndex</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">}</span></span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>titleRef<span class="token punctuation">}</span></span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>sr-only<span class="token punctuation">'</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br> </span><span class="token punctuation">{</span>title<span class="token punctuation">}</span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span><span class="token plain-text"><br><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Helmet</span></span> <span class="token attr-name">onChangeClientState</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onHelmetChange<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span></span><span class="token punctuation">></span></span><br> <span class="token punctuation">)</span><br><span class="token punctuation">}</span></code></pre> <h2 id="wrapping-up">Wrapping up</h2> <p>That is all that is needed to handle page titles in an accessible way in a single-page React application. The <code>react-router</code> and <code>react-helmet</code> libraries are not necessary either, and the same pattern should be applicable regardless of the library (or lack thereof) in use.</p> <p>Note that if you have a simple application and can guarantee there is always a relevant <code><h1></code> element (independently of loading states, query errors and such), another, possibly simpler solution arises. It should be possible to skip that hidden element altogether, and focus the <code><h1></code> element instead (still with <code>tabIndex={-1}</code>). This solution could not scale for us as we have hundreds of sometimes complex and dynamic pages, some with a visible <code><h1></code> element, some with a hidden one, and so on.</p> <p>Feel free to <a href="https://codesandbox.io/s/accessible-page-title-in-single-page-react-applications-u9e52">play with the code</a> on CodeSandbox.</p> </textarea> <label for="fname">First name:</label><br> <input type="text" id="fname" name="fname" value="John"><br> <label for="lname">Last name:</label><br> <input type="text" id="lname" name="lname" value="Doe"><br><br> <input type="submit" value="Submit"> </form> </span> </div> </div><!-- .entry-content --> <nav class="pagination-single border-color-border"> <a class="previous-post" href="https://www.scien.cx/2020/01/14/kirby-3-3-3-rc-1/"> <span class="arrow">←</span> <span class="title"><span class="title-inner">Kirby 3.3.3-rc.1</span></span> </a> <a class="next-post" href="https://www.scien.cx/2020/01/15/implementing-an-oauth-server-with-node-js-and-express/"> <span class="arrow">→</span> <span class="title"><span class="title-inner">Implementing an OAuth Server With Node.js and Express</span></span> </a> </nav><!-- .single-pagination --> </div><!-- .post-inner --> </article><!-- .post --> <div class="related-posts section-inner"> <h2 class="related-posts-title heading-size-3">Related Posts</h2> <div class="posts"> <div class="posts-grid related-posts-grid grid mcols-1 tcols-2 tlcols-3 dcols-4"> <div class="grid-item"> <article class="preview preview-post post-70294 post type-post status-publish format-standard hentry category-gatsbyjs category-react" id="post-70294"> <figure class="wp-block-audio"></figure> <figure class="preview-media"> <div class="owl-carousel owl-theme gal-item" id="owl-70294"> <div class="item" style="background-image: url('https://cdn-images-1.medium.com/max/1024/1*CIOyS6PLWm2x6MW4W61eew.jpeg')"><a href="https://www.scien.cx/2021/03/30/upgrading-to-gatsby-3-0/"></a></div> </div> <script> $('#owl-70294').owlCarousel({ loop:true, margin:10, nav:true, items: 1, }); </script> <figure class="wp-block-audio"><audio controls="controls"><source src="" type="audio/mpeg" /></audio> <script>$("audio").mediaelementplayer();</script></figure> </figure> <header class="preview-header"> <h2 class="preview-title heading-size-3"><a href="https://www.scien.cx/2021/03/30/upgrading-to-gatsby-3-0/">Upgrading to Gatsby 3.0</a></h2> <div class="post-meta-wrapper post-meta-archive"> <ul class="post-meta color-accent"> <li class="post-date"> <a class="meta-wrapper" href="https://www.scien.cx/2021/03/30/upgrading-to-gatsby-3-0/"> <span class="meta-icon"> <span class="screen-reader-text">Post date</span> <svg class="svg-icon" aria-hidden="true" role="img" focusable="false" xmlns="http://www.w3.org/2000/svg" width="18" height="19" viewBox="0 0 18 19"><path fill="" d="M4.60069444,4.09375 L3.25,4.09375 C2.47334957,4.09375 1.84375,4.72334957 1.84375,5.5 L1.84375,7.26736111 L16.15625,7.26736111 L16.15625,5.5 C16.15625,4.72334957 15.5266504,4.09375 14.75,4.09375 L13.3993056,4.09375 L13.3993056,4.55555556 C13.3993056,5.02154581 13.0215458,5.39930556 12.5555556,5.39930556 C12.0895653,5.39930556 11.7118056,5.02154581 11.7118056,4.55555556 L11.7118056,4.09375 L6.28819444,4.09375 L6.28819444,4.55555556 C6.28819444,5.02154581 5.9104347,5.39930556 5.44444444,5.39930556 C4.97845419,5.39930556 4.60069444,5.02154581 4.60069444,4.55555556 L4.60069444,4.09375 Z M6.28819444,2.40625 L11.7118056,2.40625 L11.7118056,1 C11.7118056,0.534009742 12.0895653,0.15625 12.5555556,0.15625 C13.0215458,0.15625 13.3993056,0.534009742 13.3993056,1 L13.3993056,2.40625 L14.75,2.40625 C16.4586309,2.40625 17.84375,3.79136906 17.84375,5.5 L17.84375,15.875 C17.84375,17.5836309 16.4586309,18.96875 14.75,18.96875 L3.25,18.96875 C1.54136906,18.96875 0.15625,17.5836309 0.15625,15.875 L0.15625,5.5 C0.15625,3.79136906 1.54136906,2.40625 3.25,2.40625 L4.60069444,2.40625 L4.60069444,1 C4.60069444,0.534009742 4.97845419,0.15625 5.44444444,0.15625 C5.9104347,0.15625 6.28819444,0.534009742 6.28819444,1 L6.28819444,2.40625 Z M1.84375,8.95486111 L1.84375,15.875 C1.84375,16.6516504 2.47334957,17.28125 3.25,17.28125 L14.75,17.28125 C15.5266504,17.28125 16.15625,16.6516504 16.15625,15.875 L16.15625,8.95486111 L1.84375,8.95486111 Z" /></svg> </span> <span class="meta-text"> March 30, 2021 </span> </a> </li> <li class="post-author meta-wrapper"> <span class="meta-icon"> <span class="screen-reader-text">Post author</span> <svg class="svg-icon" aria-hidden="true" role="img" focusable="false" xmlns="http://www.w3.org/2000/svg" width="18" height="20" viewBox="0 0 18 20"><path fill="" d="M18,19 C18,19.5522847 17.5522847,20 17,20 C16.4477153,20 16,19.5522847 16,19 L16,17 C16,15.3431458 14.6568542,14 13,14 L5,14 C3.34314575,14 2,15.3431458 2,17 L2,19 C2,19.5522847 1.55228475,20 1,20 C0.44771525,20 0,19.5522847 0,19 L0,17 C0,14.2385763 2.23857625,12 5,12 L13,12 C15.7614237,12 18,14.2385763 18,17 L18,19 Z M9,10 C6.23857625,10 4,7.76142375 4,5 C4,2.23857625 6.23857625,0 9,0 C11.7614237,0 14,2.23857625 14,5 C14,7.76142375 11.7614237,10 9,10 Z M9,8 C10.6568542,8 12,6.65685425 12,5 C12,3.34314575 10.6568542,2 9,2 C7.34314575,2 6,3.34314575 6,5 C6,6.65685425 7.34314575,8 9,8 Z" /></svg> </span> <span class="meta-text"> By <a href="https://www.scien.cx/author/david-fekke/">David Fekke</a> </span> </li> <li class="post-categories meta-wrapper"> <span class="meta-icon"> <span class="screen-reader-text">Post categories</span> <svg class="svg-icon" aria-hidden="true" role="img" focusable="false" xmlns="http://www.w3.org/2000/svg" width="20" height="19" viewBox="0 0 20 19"><path fill="" d="M2.8,1.85 C2.275329,1.85 1.85,2.27532949 1.85,2.8 L1.85,15.4 C1.85,15.9246705 2.275329,16.35 2.8,16.35 L17.2,16.35 C17.724671,16.35 18.15,15.9246705 18.15,15.4 L18.15,5.5 C18.15,4.97532949 17.724671,4.55 17.2,4.55 L9.1,4.55 C8.8158,4.55 8.550403,4.40796403 8.392757,4.17149517 L6.845094,1.85 L2.8,1.85 Z M17.2,2.85 C18.663555,2.85 19.85,4.03644541 19.85,5.5 L19.85,15.4 C19.85,16.8635546 18.663555,18.05 17.2,18.05 L2.8,18.05 C1.336445,18.05 0.15,16.8635546 0.15,15.4 L0.15,2.8 C0.15,1.33644541 1.336445,0.15 2.8,0.15 L7.3,0.15 C7.5842,0.15 7.849597,0.292035965 8.007243,0.528504833 L9.554906,2.85 L17.2,2.85 Z" /></svg> </span> <span class="meta-text"> In <a href="https://www.scien.cx/category/gatsbyjs/" rel="category tag">gatsbyjs</a>, <a href="https://www.scien.cx/category/react/" rel="category tag">react</a> </span> </li> </ul> </div> </header><!-- .preview-header --> </article><!-- .preview --> </div><!-- .grid-item --> <div class="grid-item"> <article class="preview preview-post post-59457 post type-post status-publish format-standard hentry category-javascript category-react category-redux" id="post-59457"> <figure class="wp-block-audio"></figure> <figure class="preview-media"> </figure> <header class="preview-header"> <h2 class="preview-title heading-size-3"><a href="https://www.scien.cx/2021/03/17/react-redux-in-less-then-7-minutes/">React – Redux In Less Then 7 Minutes</a></h2> <div class="post-meta-wrapper post-meta-archive"> <ul class="post-meta color-accent"> <li class="post-date"> <a class="meta-wrapper" href="https://www.scien.cx/2021/03/17/react-redux-in-less-then-7-minutes/"> <span class="meta-icon"> <span class="screen-reader-text">Post date</span> <svg class="svg-icon" aria-hidden="true" role="img" focusable="false" xmlns="http://www.w3.org/2000/svg" width="18" height="19" viewBox="0 0 18 19"><path fill="" d="M4.60069444,4.09375 L3.25,4.09375 C2.47334957,4.09375 1.84375,4.72334957 1.84375,5.5 L1.84375,7.26736111 L16.15625,7.26736111 L16.15625,5.5 C16.15625,4.72334957 15.5266504,4.09375 14.75,4.09375 L13.3993056,4.09375 L13.3993056,4.55555556 C13.3993056,5.02154581 13.0215458,5.39930556 12.5555556,5.39930556 C12.0895653,5.39930556 11.7118056,5.02154581 11.7118056,4.55555556 L11.7118056,4.09375 L6.28819444,4.09375 L6.28819444,4.55555556 C6.28819444,5.02154581 5.9104347,5.39930556 5.44444444,5.39930556 C4.97845419,5.39930556 4.60069444,5.02154581 4.60069444,4.55555556 L4.60069444,4.09375 Z M6.28819444,2.40625 L11.7118056,2.40625 L11.7118056,1 C11.7118056,0.534009742 12.0895653,0.15625 12.5555556,0.15625 C13.0215458,0.15625 13.3993056,0.534009742 13.3993056,1 L13.3993056,2.40625 L14.75,2.40625 C16.4586309,2.40625 17.84375,3.79136906 17.84375,5.5 L17.84375,15.875 C17.84375,17.5836309 16.4586309,18.96875 14.75,18.96875 L3.25,18.96875 C1.54136906,18.96875 0.15625,17.5836309 0.15625,15.875 L0.15625,5.5 C0.15625,3.79136906 1.54136906,2.40625 3.25,2.40625 L4.60069444,2.40625 L4.60069444,1 C4.60069444,0.534009742 4.97845419,0.15625 5.44444444,0.15625 C5.9104347,0.15625 6.28819444,0.534009742 6.28819444,1 L6.28819444,2.40625 Z M1.84375,8.95486111 L1.84375,15.875 C1.84375,16.6516504 2.47334957,17.28125 3.25,17.28125 L14.75,17.28125 C15.5266504,17.28125 16.15625,16.6516504 16.15625,15.875 L16.15625,8.95486111 L1.84375,8.95486111 Z" /></svg> </span> <span class="meta-text"> March 17, 2021 </span> </a> </li> <li class="post-author meta-wrapper"> <span class="meta-icon"> <span class="screen-reader-text">Post author</span> <svg class="svg-icon" aria-hidden="true" role="img" focusable="false" xmlns="http://www.w3.org/2000/svg" width="18" height="20" viewBox="0 0 18 20"><path fill="" d="M18,19 C18,19.5522847 17.5522847,20 17,20 C16.4477153,20 16,19.5522847 16,19 L16,17 C16,15.3431458 14.6568542,14 13,14 L5,14 C3.34314575,14 2,15.3431458 2,17 L2,19 C2,19.5522847 1.55228475,20 1,20 C0.44771525,20 0,19.5522847 0,19 L0,17 C0,14.2385763 2.23857625,12 5,12 L13,12 C15.7614237,12 18,14.2385763 18,17 L18,19 Z M9,10 C6.23857625,10 4,7.76142375 4,5 C4,2.23857625 6.23857625,0 9,0 C11.7614237,0 14,2.23857625 14,5 C14,7.76142375 11.7614237,10 9,10 Z M9,8 C10.6568542,8 12,6.65685425 12,5 C12,3.34314575 10.6568542,2 9,2 C7.34314575,2 6,3.34314575 6,5 C6,6.65685425 7.34314575,8 9,8 Z" /></svg> </span> <span class="meta-text"> By <a href="https://www.scien.cx/author/matt-eddy/">Matt Eddy</a> </span> </li> <li class="post-categories meta-wrapper"> <span class="meta-icon"> <span class="screen-reader-text">Post categories</span> <svg class="svg-icon" aria-hidden="true" role="img" focusable="false" xmlns="http://www.w3.org/2000/svg" width="20" height="19" viewBox="0 0 20 19"><path fill="" d="M2.8,1.85 C2.275329,1.85 1.85,2.27532949 1.85,2.8 L1.85,15.4 C1.85,15.9246705 2.275329,16.35 2.8,16.35 L17.2,16.35 C17.724671,16.35 18.15,15.9246705 18.15,15.4 L18.15,5.5 C18.15,4.97532949 17.724671,4.55 17.2,4.55 L9.1,4.55 C8.8158,4.55 8.550403,4.40796403 8.392757,4.17149517 L6.845094,1.85 L2.8,1.85 Z M17.2,2.85 C18.663555,2.85 19.85,4.03644541 19.85,5.5 L19.85,15.4 C19.85,16.8635546 18.663555,18.05 17.2,18.05 L2.8,18.05 C1.336445,18.05 0.15,16.8635546 0.15,15.4 L0.15,2.8 C0.15,1.33644541 1.336445,0.15 2.8,0.15 L7.3,0.15 C7.5842,0.15 7.849597,0.292035965 8.007243,0.528504833 L9.554906,2.85 L17.2,2.85 Z" /></svg> </span> <span class="meta-text"> In <a href="https://www.scien.cx/category/javascript/" rel="category tag">JavaScript</a>, <a href="https://www.scien.cx/category/react/" rel="category tag">react</a>, <a href="https://www.scien.cx/category/redux/" rel="category tag">redux</a> </span> </li> </ul> </div> </header><!-- .preview-header --> </article><!-- .preview --> </div><!-- .grid-item --> <div class="grid-item"> <article class="preview preview-post post-262 post type-post status-publish format-standard hentry category-a11y category-accessibility category-tweets category-twitter" id="post-262"> <figure class="wp-block-audio"></figure> <figure class="preview-media"> </figure> <header class="preview-header"> <h2 class="preview-title heading-size-3"><a href="https://www.scien.cx/2017/06/20/100-tweets-on-accessibility/">100 tweets on accessibility</a></h2> <div class="post-meta-wrapper post-meta-archive"> <ul class="post-meta color-accent"> <li class="post-date"> <a class="meta-wrapper" href="https://www.scien.cx/2017/06/20/100-tweets-on-accessibility/"> <span class="meta-icon"> <span class="screen-reader-text">Post date</span> <svg class="svg-icon" aria-hidden="true" role="img" focusable="false" xmlns="http://www.w3.org/2000/svg" width="18" height="19" viewBox="0 0 18 19"><path fill="" d="M4.60069444,4.09375 L3.25,4.09375 C2.47334957,4.09375 1.84375,4.72334957 1.84375,5.5 L1.84375,7.26736111 L16.15625,7.26736111 L16.15625,5.5 C16.15625,4.72334957 15.5266504,4.09375 14.75,4.09375 L13.3993056,4.09375 L13.3993056,4.55555556 C13.3993056,5.02154581 13.0215458,5.39930556 12.5555556,5.39930556 C12.0895653,5.39930556 11.7118056,5.02154581 11.7118056,4.55555556 L11.7118056,4.09375 L6.28819444,4.09375 L6.28819444,4.55555556 C6.28819444,5.02154581 5.9104347,5.39930556 5.44444444,5.39930556 C4.97845419,5.39930556 4.60069444,5.02154581 4.60069444,4.55555556 L4.60069444,4.09375 Z M6.28819444,2.40625 L11.7118056,2.40625 L11.7118056,1 C11.7118056,0.534009742 12.0895653,0.15625 12.5555556,0.15625 C13.0215458,0.15625 13.3993056,0.534009742 13.3993056,1 L13.3993056,2.40625 L14.75,2.40625 C16.4586309,2.40625 17.84375,3.79136906 17.84375,5.5 L17.84375,15.875 C17.84375,17.5836309 16.4586309,18.96875 14.75,18.96875 L3.25,18.96875 C1.54136906,18.96875 0.15625,17.5836309 0.15625,15.875 L0.15625,5.5 C0.15625,3.79136906 1.54136906,2.40625 3.25,2.40625 L4.60069444,2.40625 L4.60069444,1 C4.60069444,0.534009742 4.97845419,0.15625 5.44444444,0.15625 C5.9104347,0.15625 6.28819444,0.534009742 6.28819444,1 L6.28819444,2.40625 Z M1.84375,8.95486111 L1.84375,15.875 C1.84375,16.6516504 2.47334957,17.28125 3.25,17.28125 L14.75,17.28125 C15.5266504,17.28125 16.15625,16.6516504 16.15625,15.875 L16.15625,8.95486111 L1.84375,8.95486111 Z" /></svg> </span> <span class="meta-text"> June 20, 2017 </span> </a> </li> <li class="post-author meta-wrapper"> <span class="meta-icon"> <span class="screen-reader-text">Post author</span> <svg class="svg-icon" aria-hidden="true" role="img" focusable="false" xmlns="http://www.w3.org/2000/svg" width="18" height="20" viewBox="0 0 18 20"><path fill="" d="M18,19 C18,19.5522847 17.5522847,20 17,20 C16.4477153,20 16,19.5522847 16,19 L16,17 C16,15.3431458 14.6568542,14 13,14 L5,14 C3.34314575,14 2,15.3431458 2,17 L2,19 C2,19.5522847 1.55228475,20 1,20 C0.44771525,20 0,19.5522847 0,19 L0,17 C0,14.2385763 2.23857625,12 5,12 L13,12 C15.7614237,12 18,14.2385763 18,17 L18,19 Z M9,10 C6.23857625,10 4,7.76142375 4,5 C4,2.23857625 6.23857625,0 9,0 C11.7614237,0 14,2.23857625 14,5 C14,7.76142375 11.7614237,10 9,10 Z M9,8 C10.6568542,8 12,6.65685425 12,5 C12,3.34314575 10.6568542,2 9,2 C7.34314575,2 6,3.34314575 6,5 C6,6.65685425 7.34314575,8 9,8 Z" /></svg> </span> <span class="meta-text"> By <a href="https://www.scien.cx/author/hugo-kitty-giraudel/">Hugo “Kitty” Giraudel</a> </span> </li> <li class="post-categories meta-wrapper"> <span class="meta-icon"> <span class="screen-reader-text">Post categories</span> <svg class="svg-icon" aria-hidden="true" role="img" focusable="false" xmlns="http://www.w3.org/2000/svg" width="20" height="19" viewBox="0 0 20 19"><path fill="" d="M2.8,1.85 C2.275329,1.85 1.85,2.27532949 1.85,2.8 L1.85,15.4 C1.85,15.9246705 2.275329,16.35 2.8,16.35 L17.2,16.35 C17.724671,16.35 18.15,15.9246705 18.15,15.4 L18.15,5.5 C18.15,4.97532949 17.724671,4.55 17.2,4.55 L9.1,4.55 C8.8158,4.55 8.550403,4.40796403 8.392757,4.17149517 L6.845094,1.85 L2.8,1.85 Z M17.2,2.85 C18.663555,2.85 19.85,4.03644541 19.85,5.5 L19.85,15.4 C19.85,16.8635546 18.663555,18.05 17.2,18.05 L2.8,18.05 C1.336445,18.05 0.15,16.8635546 0.15,15.4 L0.15,2.8 C0.15,1.33644541 1.336445,0.15 2.8,0.15 L7.3,0.15 C7.5842,0.15 7.849597,0.292035965 8.007243,0.528504833 L9.554906,2.85 L17.2,2.85 Z" /></svg> </span> <span class="meta-text"> In <a href="https://www.scien.cx/category/a11y/" rel="category tag">a11y</a>, <a href="https://www.scien.cx/category/accessibility/" rel="category tag">accessibility</a>, <a href="https://www.scien.cx/category/tweets/" rel="category tag">tweets</a>, <a href="https://www.scien.cx/category/twitter/" rel="category tag">twitter</a> </span> </li> </ul> </div> </header><!-- .preview-header --> </article><!-- .preview --> </div><!-- .grid-item --> <div class="grid-item"> <article class="preview preview-post post-43619 post type-post status-publish format-standard hentry category-graphql category-react category-typescript category-webdev" id="post-43619"> <figure class="wp-block-audio"></figure> <figure class="preview-media"> </figure> <header class="preview-header"> <h2 class="preview-title heading-size-3"><a href="https://www.scien.cx/2021/02/28/build-a-chat-app-with-graphql-subscriptions-typescript-part-1/">Build a chat app with GraphQL Subscriptions & TypeScript: Part 1</a></h2> <div class="post-meta-wrapper post-meta-archive"> <ul class="post-meta color-accent"> <li class="post-date"> <a class="meta-wrapper" href="https://www.scien.cx/2021/02/28/build-a-chat-app-with-graphql-subscriptions-typescript-part-1/"> <span class="meta-icon"> <span class="screen-reader-text">Post date</span> <svg class="svg-icon" aria-hidden="true" role="img" focusable="false" xmlns="http://www.w3.org/2000/svg" width="18" height="19" viewBox="0 0 18 19"><path fill="" d="M4.60069444,4.09375 L3.25,4.09375 C2.47334957,4.09375 1.84375,4.72334957 1.84375,5.5 L1.84375,7.26736111 L16.15625,7.26736111 L16.15625,5.5 C16.15625,4.72334957 15.5266504,4.09375 14.75,4.09375 L13.3993056,4.09375 L13.3993056,4.55555556 C13.3993056,5.02154581 13.0215458,5.39930556 12.5555556,5.39930556 C12.0895653,5.39930556 11.7118056,5.02154581 11.7118056,4.55555556 L11.7118056,4.09375 L6.28819444,4.09375 L6.28819444,4.55555556 C6.28819444,5.02154581 5.9104347,5.39930556 5.44444444,5.39930556 C4.97845419,5.39930556 4.60069444,5.02154581 4.60069444,4.55555556 L4.60069444,4.09375 Z M6.28819444,2.40625 L11.7118056,2.40625 L11.7118056,1 C11.7118056,0.534009742 12.0895653,0.15625 12.5555556,0.15625 C13.0215458,0.15625 13.3993056,0.534009742 13.3993056,1 L13.3993056,2.40625 L14.75,2.40625 C16.4586309,2.40625 17.84375,3.79136906 17.84375,5.5 L17.84375,15.875 C17.84375,17.5836309 16.4586309,18.96875 14.75,18.96875 L3.25,18.96875 C1.54136906,18.96875 0.15625,17.5836309 0.15625,15.875 L0.15625,5.5 C0.15625,3.79136906 1.54136906,2.40625 3.25,2.40625 L4.60069444,2.40625 L4.60069444,1 C4.60069444,0.534009742 4.97845419,0.15625 5.44444444,0.15625 C5.9104347,0.15625 6.28819444,0.534009742 6.28819444,1 L6.28819444,2.40625 Z M1.84375,8.95486111 L1.84375,15.875 C1.84375,16.6516504 2.47334957,17.28125 3.25,17.28125 L14.75,17.28125 C15.5266504,17.28125 16.15625,16.6516504 16.15625,15.875 L16.15625,8.95486111 L1.84375,8.95486111 Z" /></svg> </span> <span class="meta-text"> February 28, 2021 </span> </a> </li> <li class="post-author meta-wrapper"> <span class="meta-icon"> <span class="screen-reader-text">Post author</span> <svg class="svg-icon" aria-hidden="true" role="img" focusable="false" xmlns="http://www.w3.org/2000/svg" width="18" height="20" viewBox="0 0 18 20"><path fill="" d="M18,19 C18,19.5522847 17.5522847,20 17,20 C16.4477153,20 16,19.5522847 16,19 L16,17 C16,15.3431458 14.6568542,14 13,14 L5,14 C3.34314575,14 2,15.3431458 2,17 L2,19 C2,19.5522847 1.55228475,20 1,20 C0.44771525,20 0,19.5522847 0,19 L0,17 C0,14.2385763 2.23857625,12 5,12 L13,12 C15.7614237,12 18,14.2385763 18,17 L18,19 Z M9,10 C6.23857625,10 4,7.76142375 4,5 C4,2.23857625 6.23857625,0 9,0 C11.7614237,0 14,2.23857625 14,5 C14,7.76142375 11.7614237,10 9,10 Z M9,8 C10.6568542,8 12,6.65685425 12,5 C12,3.34314575 10.6568542,2 9,2 C7.34314575,2 6,3.34314575 6,5 C6,6.65685425 7.34314575,8 9,8 Z" /></svg> </span> <span class="meta-text"> By <a href="https://www.scien.cx/author/saswata-mukherjee/">Saswata Mukherjee</a> </span> </li> <li class="post-categories meta-wrapper"> <span class="meta-icon"> <span class="screen-reader-text">Post categories</span> <svg class="svg-icon" aria-hidden="true" role="img" focusable="false" xmlns="http://www.w3.org/2000/svg" width="20" height="19" viewBox="0 0 20 19"><path fill="" d="M2.8,1.85 C2.275329,1.85 1.85,2.27532949 1.85,2.8 L1.85,15.4 C1.85,15.9246705 2.275329,16.35 2.8,16.35 L17.2,16.35 C17.724671,16.35 18.15,15.9246705 18.15,15.4 L18.15,5.5 C18.15,4.97532949 17.724671,4.55 17.2,4.55 L9.1,4.55 C8.8158,4.55 8.550403,4.40796403 8.392757,4.17149517 L6.845094,1.85 L2.8,1.85 Z M17.2,2.85 C18.663555,2.85 19.85,4.03644541 19.85,5.5 L19.85,15.4 C19.85,16.8635546 18.663555,18.05 17.2,18.05 L2.8,18.05 C1.336445,18.05 0.15,16.8635546 0.15,15.4 L0.15,2.8 C0.15,1.33644541 1.336445,0.15 2.8,0.15 L7.3,0.15 C7.5842,0.15 7.849597,0.292035965 8.007243,0.528504833 L9.554906,2.85 L17.2,2.85 Z" /></svg> </span> <span class="meta-text"> In <a href="https://www.scien.cx/category/graphql/" rel="category tag">GraphQL</a>, <a href="https://www.scien.cx/category/react/" rel="category tag">react</a>, <a href="https://www.scien.cx/category/typescript/" rel="category tag">typescript</a>, <a href="https://www.scien.cx/category/webdev/" rel="category tag">webdev</a> </span> </li> </ul> </div> </header><!-- .preview-header --> </article><!-- .preview --> </div><!-- .grid-item --> </div><!-- .posts-grid --> </div><!-- .posts --> </div><!-- .related-posts --> </main><!-- #site-content --> <script type="text/javascript"> jQuery(document).ready(function($){ // using jQuery if( $(document).find('.wp-block-video video').length ) { $(document).find('.wp-block-video video').mediaelementplayer(/* Options */); } if( $(document).find('.wp-block-audio audio').length ) { $(document).find('.wp-block-audio audio').mediaelementplayer(/* Options */); } //$(window).load(function(){ $('.wp-block-audio figcaption').each(function(){ var htm = $(this).html(); $(this).html('<div>' +htm+ '</div>'); }); //}); }); </script> <div class="wdg-bottom"></div> <style> /* ADDING SUPPORT FOR THE NEWSLETTER PLUGIN WIDGET */ .tnp-email::-webkit-input-placeholder::before { content:"Line 1\A";align-content } .tnp-email { top: -0px; position: relative; line-height: 2.0em !important; color: inherit; margin: 0 0 .8rem .8rem; display: flex; justify-content: space-between; width:72% !important; border: 2px; border-color: white; } .tnp-field label { display: none !important; } input.tnp-submit { display: block; top: -40px; position: relative; line-height: 2.0em !important; margin: 0 0 .8rem .8rem; display: flex; justify-content: space-between; width: 26% !important; float: right !important; } .tnp-field.tnp-field-button { top: -20px !important; position: relative; } .menu-modal-inner { box-shadow: 0 0 2rem 0 rgba( 0, 0, 0, .1 ); min-width: 64rem; opacity: 0; transform: translateX(25rem); transition: transform .2s ease-in, opacity .2s ease-in; width: 50%; margin-left: auto; height: 100%; } .t_menu .menu-modal-inner { opacity: 1; transform: none; } .menu-modal { display: block !important; transition: none; } .menu-item ul.sub-menu { position: relative !important; } @media (min-width: 1000px){ .menu-modal { background: rgb(0 0 0 / 30%) !important;} } .tnp-submit { background-color: !important; border-color: !important; } .tnp-email { color: !important; background-color: transparent !important; border-color: !important; } </style> <footer id="site-footer" role="contentinfo"> <div class="footer-inner section-inner has-footer-menu"> <ul class="footer-menu reset-list-style"> </ul><!-- .site-nav --> <!-- .footer-credits --> <div class="left-footer-menu"><ul id="menu-left-footer" class="menu"><li id="menu-item-3230" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-home menu-item-has-children menu-item-3230"><a href="https://www.scien.cx/">Sciencx</a> <ul class="sub-menu"> <li id="menu-item-3256" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-home menu-item-3256"><a href="https://www.scien.cx/">Fuel Innovation</a></li> <li id="menu-item-3233" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-3233"><a href="https://www.scien.cx/about/">About</a></li> <li id="menu-item-3232" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-3232"><a href="https://www.scien.cx/contact/">Contact</a></li> </ul> </li> <li id="menu-item-3231" class="menu-item menu-item-type-post_type menu-item-object-page current_page_parent menu-item-3231"><a href="https://www.scien.cx/blog/">News</a></li> <li id="menu-item-3257" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-3257"><a href="https://secure.actblue.com/donate/sciencx">Donate</a></li> </ul></div> </div><!-- .footer-bottom --> </footer><!-- #site-footer --> <script type='text/javascript' src='https://www.scien.cx/wp-includes/js/wp-embed.min.js?ver=5.8.1' id='wp-embed-js'></script> <script> jQuery(function(){ jQuery('li.menu-item-has-children > a:first-child').on('click',function(event){ event.preventDefault() jQuery(this).parent().find('ul').toggle(300); //Hide menu when clicked outside jQuery(this).parent().find('ul').onclick(function(){ var thisUI = jQuery(this); jQuery('html').click(function(){ thisUI.hide(); jQuery('html').unbind('click'); }); }); }); }); </script> <script> jQuery('.myLinkToTop').click(function () { jQuery('html, body').animate({scrollTop:jQuery(document).height()}, 'slow'); return true; }); jQuery('.myMenuLink').click(function () { jQuery('html, body').animate({scrollTop:0}, 'slow'); return true; }); </script> <script> jQuery(".left-footer-menu .header-toggles").on("click",function(){ jQuery(".left-footer-menu .menu-modal.cover-modal").toggleClass("t_menu") }) jQuery(".left-footer-menu .menu-modal-toggles").on("click",function(){ jQuery(".left-footer-menu .menu-modal.cover-modal").removeClass("t_menu") }) jQuery(".left-footer-menu .menu-item .sub-menu-toggle").on("click",function(){ jQuery(this).toggleClass("active"); jQuery(this).parent().parent().parent().find("ul.sub-menu").toggle() }) /////////////////////////// //this hides images that do not load properly on the homepage $("img").error(function (){ $(this).hide(); // or $(this).css({'display','none'}); }); </script> </body> </html>