Skip to content

Instantly share code, notes, and snippets.

@ryanflorence
Created February 4, 2019 04:51
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ryanflorence/ef5241726de6d50c2ba18f61bf9585c2 to your computer and use it in GitHub Desktop.
Save ryanflorence/ef5241726de6d50c2ba18f61bf9585c2 to your computer and use it in GitHub Desktop.

Accessibility, IDs, and Server Rendering in React

For better or for worse, many WAI-ARIA patterns depend on ID's to connect elements together.

For example (without ARIA):

<label for="first-name">First Name</label>
<input id="first-name"/>

The browser now knows that when the label is clicked it should focus the input #first-name.

ARIA has adopted this pattern with things like tabs.

<button
  role="tab"
  aria-selected="true"
  aria-controls="ryan-panel"
  id="ryan-tab"
>
  Ryan Florence
</button>

<!-- elsehwere -->

<div
  tabindex="0"
  role="tabpanel"
  id="ryan-panel"
  aria-labelledby="ryan-tab"
>
  <p>
    Ryan just had a really crappy door dash order.
    McDonald's nuggets would have been 100x better.
  </p>
</div>

So the trick is abstracting this away from consumers of a React component library with an API like this:

<Tabs>
  <TabList>
    <Tab>Ryan</Tab>
    <Tab>Dan</Tab>
  </TabList>

  <TabPanels>
    <Tab><p>Ryan wants Chicken McNuggets.</p></Tab>
    <Tab><p>Dan wrote an article today that Ryan wanted to.</p></Tab>
  </TabPanels>
</Tabs>

The resulting DOM needs to have some auto-generated IDs. It's pretty easy to just randomly generate a root id in Tabs, put it on context, and then let the TabList and TabPanels use it + index on each child in cloneElement and tell it the ID: { id: context.rootId + index }.

But that breaks server rendering because the server will generate a different ID than the client.

Back when React dumped data-react-ids on elements I thought we could just expose it as public API and be done. Sounds like things don't work that way anymore.

So now I'm thinking about using context and be able to ask for a new id at any point and get it back on subsequent renders:

function Tabs() {
  let rootId = useUniqueId()
}

And hopefully that thing will give the same ID back on the server pre-render and every client render.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment