A11y Full documentation content.

@react-three/a11y brings accessibility to webGL with easy-to-use react-three-fiber components: - Focus and focus indication - Tab index and keyboard navigation - Screen reader support and alt-text - Roles and cursor shapes - Descriptive links You can try a [live demo here](https://n4rzi.csb.app). Open it with your favourite assistive tech to test it out ! ```bash npm install @react-three/a11y ``` ## Quick overview to get started ### The A11yAnnouncer component First, place the A11yAnnouncer component next to the R3F Canvas component. This component is critical since it manage some screen-reader features. ```jsx import { Canvas } from '@react-three/fiber'; import { A11yAnnouncer } from '@react-three/a11y'; function App() { return ( <> ); } ``` ### Then wrap components you want to make accessible with the A11y component To add accessibility features to your scene you'll have to wrap components you want to make focusable with the `A11y` component: ```jsx import { A11y } from '@react-three/a11y' [...] ``` `MyComponent` can now receive focus. More accurately, the emulated "focus" will be handled at the `A11y` components which acts as a provider for children to access its state. But even if objects are focusable, nothing will be displayed or shown by default. ## Call function on focus The `focusCall` prop of `A11y` will be called each time this component receives focus (usually through tab navigation). ```jsx console.log("in focus")} ... /> ``` ## Call function on click / keyboard Click The `actionCall` prop of `A11y` will be called each time this component gets clicked, focused, keyboard activated etc. ```jsx console.log("clicked")} ... /> ``` ## Provide a description of the currently focused / hovered element When using the `description` prop in combination with the `role` prop, the `A11y` component will provide a description to the screen reader users on focus/hover. Optionally, you can also show the description to the user on hover by setting `showAltText={true}`. ```jsx // Reads "A rotating red square" to screen readers on focus / hover while also showing it on mouseover // Reads "Button, open menu + (description on how to activate depending on the screen reader)" to screen readers on focus / hover {someFunction()}} ... /> ``` ## The four roles of the A11y component Like in HTML, you can focus different kind of elements and expect different things depending on what you're focusing. #### Content ```jsx ``` Uses the `default` cursor. This role is meant to provide information to screen readers or to serve as a step for a user to navigate your site using Tab for instance. It's not meant to trigger anything on click or to be activable with the Keyboard. Therefore it won't show a pointer cursor on hover. [Read more about role content](/a11y/roles/content) #### Button ```jsx ``` Uses the `pointer` cursor. Special attributes: `activationMsg`. This role is meant to emulate the behaviour of a button or a toggleable button. It will display a cursor pointer when your cursor is over the linked 3D object. It will call a function on click but also on any kind of action that would trigger a focused button (Enter, Double-Tap, ...). It is also actionable by user using a screen reader. [Read more about role button](/a11y/roles/button) #### ToggleButton By using the role togglebutton, you'll emulate a button with two state that will have the `aria-pressed` attribute. You'll then be able to use the deactivationMsg property in addition to the usual description and activationMsg properties. ```jsx ``` Special attributes: `deactivationMsg` [Read more about role ToggleButton](/a11y/roles/togglebutton) #### Link ```jsx ``` Uses the `pointer` cursor. Special attributes: `href`. This role is meant to emulate the behaviour of a regular html link. It should be used in combination with something that will trigger navigation on click. > [!NOTE] > Don't forget to provide the href attribute as it is required for screen readers to read it correctly! - It will have no effect on the navigation, it's just used as information [Read more about role link](/a11y/roles/link) ## Screen Reader Support In order to provide informations to screen reader users and use this package at its full potential, fill the `description` prop of all your `A11y` components and use the appropriate `role` prop on each of them. ### Use of section For screen readers, it might be useful to provide additional information on how to use some unconventional UI. You can do it by wrapping the concerned part of your code relative to this UI in the A11ySection like so. ```jsx [...] ``` ## Access user preferences The A11yUserPreferences component is available in order to access user preferences such as - prefers-reduced-motion - prefers-color-scheme Take a look at [the A11yUserPreferences page](/a11y/access-user-preferences) or the [demo](https://n4rzi.csb.app) to see it in action and how to use it. The demo will adapt to your system preferences. ## Additional Features Use a custom tabindex with for your A11y components by providing a number to the tabIndex attribute ```jsx ``` > [!CAUTION] > Avoid using `tabindex` values greater than 0. Doing so makes it difficult for people who rely on assistive technology to navigate and operate page content. > Instead, write the document with the elements in a logical sequence. More about the use of tabIndex on [developer.mozilla.org](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex) ]]>
[...] ``` Then, you can access the preferences in each children component where you might need them. ## Reduce motions / animations for the users that request it Some user on your website might need all animation turned off or limited to what's strictly necessary. For those users, if your app has an animation going somewhere, consider cancelling it for those who request it. You can do it like so. ```jsx const My3dObject = () => { // this const will give you access to the user preferences const { a11yPrefersState } = useUserPreferences() const mesh = useRef() // Rotate mesh every frame useFrame(() => { //unless the user prefers reduced motion if (!a11yPrefersState.prefersReducedMotion) { mesh.current.rotation.x = mesh.current.rotation.y += 0.01 } }) return ( ) } ``` ## Adapt colour scheme depending on the user preference Some user on your website might need a darker / lighter theme. You can adapt your components according to it like so. ```jsx const My3dObject = () => { // this const will give you access to the user preferences const { a11yPrefersState } = useUserPreferences() const mesh = useRef() return ( ) } ``` ## Use the context outside and inside the r3f canvas At the moment React context [can not be readily used between two renderers](https://github.com/pmndrs/react-three-fiber/issues/43), this is due to a problem within React. If react-dom use the A11yUserPreferences provider, you will not be able to consume it within ``. There's a ready-made solution in drei: [useContextBridge](https://github.com/pmndrs/drei#usecontextbridge) which allows you to forward contexts provided above the `` to be consumed within it. You can see how it's used in the [react-three-a11y demo](https://n4rzi.csb.app) ]]> ``` That's it ! Now if you inspect the dom of your app, you will see that a `

` tag has been added with your text inside. That way, user with a screenreader will be able to read that text too. > [!NOTE] > For people using screen readers it will also sync some kind of focus indicator natively where your text is so people so screen readers users will know where they're currently in your page. This role can also be used to serve as a step for a user to navigate your site using Tab for instance. For that you would need to add the tabIndex prop and the focusCall prop like so. ```jsx someFunction()} > ``` On focus, you could rotate the camera to show that second piece of text that would usually have required some scrolling to display. Use it as you please but keep in mind how it might impact the accessibility. For this example, screenreader don't trigger focus when swiping their screen so it would benefit people used to navigate through keyboard without hurting screenreader users. It's not meant to trigger anything on click or to be activable with the Keyboard. Therefore it won't show a pointer cursor on hover. ]]> sendEmail()} ... > ``` Using it like this makes it focusable to all kind of users. You should also use the useA11y() hook within the encapsulated components to adjust the rendering on hover and focus. Doing so greatly improve the accessibility of your page. Take a look at this code sample to see how to use it. You can also play with it in [this demo](https://n4rzi.csb.app) ```jsx function Some3DComponent() { const a11y = useA11y() return ( ) } ``` You could also specify the optional prop `activationMsg`. The message withinh activationMsg will be announced by screenreader when the button is activated. ]]> switchTheme()" ...> ``` Using it like this makes it focusable to all kind of users. > [!NOTE] > You might have noticed the startPressed prop. Depending on your need, you might want to have your button starting in a pressed state. This is what this prop is for. You should also use the useA11y() hook within the encapsulated components to adjust the rendering on hover and focus and pressed state. Doing so greatly improve the accessibility of your page. Take a look at this code sample to see how to use it. You can also play with it in [this demo](https://n4rzi.csb.app) ```jsx function Some3DComponent() { const a11y = useA11y(); // access pressed, hover and focus return ( ); } ``` You could also specify the optional prop `activationMsg` and `deactivationMsg`. Respective message will be announced by screenreader when the button is activated / deactivated. ]]> { router.push(`/page`); }} > ``` Using it like this makes it focusable to all kind of users. It will also show a pointer on mouse over. You should also use the useA11y() hook within the encapsulated components to adjust the rendering on hover and focus. Doing so greatly improve the accessibility of your page. Take a look at this code sample to see how to use it. You can also play with it in [this demo](https://n4rzi.csb.app) ```jsx function Some3DComponent() { const a11y = useA11y(); return ( ); } ``` > [!IMPORTANT] > Don't forget to provide the `href` attribute as he is required for screen readers to read it correctly! > It will have no effect on the navigation, it's just used as information ]]> - role="link" => a - role="button" => button - role="content" => p - role="togglebutton" => button ( + aria-pressed ) The position is synced by a minimalist fork of the [Drei Html component](/drei/misc/html) Inside an A11y component, you can access the hover, focused and pressed state through the useA11y() hook. This hook returns the context of the A11y component. The A11yAnnouncer is used to communicate with screen readers through a div only visible to screen readers. It uses a [zustand](/zustand) store to update the div with each new message. The div is roughly like this. ```html

{message}
``` The A11ySection component appends an HTML section in which a p element describe the content of the section. Wrapped around some A11y components, it will cause the HTML from those component to be inside the section. You would then have a generated DOM that could look something like this. ```html

description

``` For the A11yUserPreferences component, it simply exposes through a context the state of prefers-color-scheme and prefers-reduced-motion media queries. It watches for change through ```javascript window.matchMedia('(prefers-reduced-motion: reduce)') window.matchMedia('(prefers-color-scheme: dark)') ``` ]]>
(state.dark = !snap.dark)} activationMsg="Lower light disabled" deactivationMsg="Lower light enabled" a11yElStyle={{ marginLeft: '-40px' }} > ``` Why is that ? In order to make this "donut" accessible as a button react-three-a11y will keep an html button over it. If we inspect the DOM, you should see something roughly like this for the above example. ```html ``` By default this button is positioned in the center of your 3D object which would cause it to work like this. 1- Mouse is not over

2- Mouse is over the donut, color change is triggered and the cursor pointer is displayed

3- Mouse is not over the donut, but the color change is still triggered as is the cursor pointer

If we display the button we can see that it's caused by the button being positioned in the middle of the donut

4- If we add `a11yElStyle={{ marginLeft: '-40px' }}` to the A11y component, the button is moved to the left and not in the center of the donut anymore

5- And as we can see, it fixes our issue. The cursor is default and no color change while the cursor is in the hole of the donut.

]]>