Skip to content

Commit

Permalink
[ADR] docs: add live-regions adr (#4611)
Browse files Browse the repository at this point in the history
* docs: add live-regions adr

* chore: add note about inclusion in github.com

* Update contributor-docs/adrs/adr-020-live-regions.md

Co-authored-by: Kate Higa <16447748+khiga8@users.noreply.github.com>

* Update contributor-docs/adrs/adr-020-live-regions.md

Co-authored-by: Kate Higa <16447748+khiga8@users.noreply.github.com>

* docs(live-region): update adr based on feedback, add more examples

* Apply suggestions from code review

Co-authored-by: Owen Niblock <owenniblock@github.com>

* Update adr-020-live-regions.md

* Update contributor-docs/adrs/adr-020-live-regions.md

Co-authored-by: Mike Perrotti <mperrotti@github.com>

* Update adr-020-live-regions.md

* chore: run format

---------

Co-authored-by: Josh Black <joshblack@users.noreply.github.com>
Co-authored-by: Kate Higa <16447748+khiga8@users.noreply.github.com>
Co-authored-by: Owen Niblock <owenniblock@github.com>
Co-authored-by: Mike Perrotti <mperrotti@github.com>
  • Loading branch information
5 people authored Jun 28, 2024
1 parent 55e97a9 commit 897033e
Showing 1 changed file with 230 additions and 0 deletions.
230 changes: 230 additions & 0 deletions contributor-docs/adrs/adr-020-live-regions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
# ADR 020: Live Regions

## Status

| Stage | Status |
| -------- | ------ |
| Approved ||
| Adopted | 🚧 |

## Context

There are several components in Primer React that make use of live regions.
However, the library does not have a central pattern for making accessible live
region announcements. This ADR documents the decision to use a central pattern
for live region announcements that can be used across Primer and GitHub.

ARIA Live regions fall into several buckets that we may want to use as component
authors:

- [ARIA live region roles](https://www.w3.org/TR/wai-aria-1.2/#live_region_roles), such as `alert`, `log`, or `status`
- [The `aria-live` attribute](https://www.w3.org/TR/wai-aria-1.2/#aria-live) to turn an element into a live region

In components, we see the following scenarios in Primer React:

- Announce the contents of an element when it is rendered or on page load, like when a spinner is displayed or a form is submitted
- Announce the changes to the content of an element, like when the count inside of a
button is incremented
- Announce a message programmatically, such as the number of results for a query

Currently, contributors may reach for roles such as `alert` or `status` to
achieve these scenarios. They may also add `aria-live="assertive"` or `aria-live="polite"` with an an `aria-atomic="true"` to an element explicitly.
However, both of these approaches do not announce consistently across screen
readers. This could be due to live regions being injected dynamically into the document (this includes loading content into the document via React), dynamically changing the visibility of a live region, or some other technique causing an announcement to not be announced.

For more information about the ways in which live regions may not work as
expected, visit: [Why are my live regions not working?](https://tetralogical.com/blog/2024/05/01/why-are-my-live-regions-not-working/)

### Links & Resources for ARIA Live regions

- https://www.sarasoueidan.com/blog/accessible-notifications-with-aria-live-regions-part-1/
- https://www.scottohara.me/blog/2022/02/05/are-we-live.html

## Decision

In order to have a common interop point for live region announcements, Primer React will
make use of a `live-region` custom element from `@primer/live-region-element`.
This package will be included and published from the `primer/react` repo.

The custom element exposes a way to make announcements that can be used
across frameworks. This makes it useful not only for Primer but GitHub as a
whole. The `@primer/live-region-element` exports two helpers to use for making
announcements: `announce()` and `announceFromElement`. Both helpers can be used
when working in Primer and by teams at GitHub.

In addition, `@primer/react` will leverage and export the following helpers for
use within Primer React and GitHub:

- The `AriaStatus` component to correspond with `role="status"`
- The `AriaAlert` component to correspond with `role="alert"`

Within `@primer/react`, we should lint against usage of `aria-live` and the
corresponding roles (if possible) and suggest using these alternatives instead.

> [!NOTE]
> Both `AriaStatus` and `AriaAlert` will trigger an announcement when the component is
> rendered. As a result, they should only be used for dynamically rendered
> content. Otherwise, they will trigger announcements on page load. In cases
> where they should always be present, then the first message passed to the
> component should be an empty string. Changes to the content of the component
> will trigger subsequent announcements.
### Impact

This decision will impact existing usage of `aria-live`, `role="status"`, and
`role="alert"` for the components listed below. These components will need to be
updated to use the new approach, using one of the following approaches:

- Use `announce()` or `announceFromElement()`
- Use `AriaStatus` or `AriaAlert`

In addition, we should make sure that `<live-region>` is successfully included
in GitHub.

#### Instances of `aria-live="polite"`

- InputValidation
- SelectPanel
- TreeView

#### Instances of `aria-live="assertive"`

- InlineAutocomplete

### Instances of `role="status"`

- LiveRegion
- TreeView
- ActionList examples
- Spinner

### Instances of `role="alert"`

None

### Instances of `LiveRegion`

- DataTable

## Examples

### Announce when content is shown

```tsx
import React from 'react'
import {AriaStatus} from '@primer/react'

function ExampleComponent() {
const [loading, setLoading] = React.useState(true)
if (loading) {
return <AriaStatus>Example loading message</AriaStatus>
}
return <Page />
}
```

### Announce on content change

```tsx
import {AriaStatus} from '@primer/react'
import {useState} from 'react'

function ExampleComponent() {
const [count, setCount] = useState(0)
return (
<button
type="button"
onClick={() => {
setCount(count + 1)
}}
>
<AriaStatus>Count {count}</AriaStatus>
</button>
)
}
```

### Announce programmatically

```tsx
import {announce} from '@primer/live-region-element'
import {useState} from 'react'

function ExampleComponent() {
const [results, setResults] = useState(data)

return (
<>
<input
type="text"
onChange={event => {
const filteredResults = data.filter(item => {
/* ... */
})
setResults(filteredResults)
announce(`${filteredResults.length} results available`)
}}
/>
{/* ... */}
</>
)
}
```

### Use existing live region

The `announce()` and `announceFromElement()` helpers both accept a `from`
argument that allow you to provide a reference from which these helpers should
find an existing live region. This can be useful in contexts like a `dialog`
where a live region must live in the dialog in order for announcements to occur.

```tsx
import {announce} from '@primer/live-region-element'
import React from 'react'

function ExampleComponent() {
const ref = React.useRef<React.ElementRef<'dialog'>>(null)
return (
<dialog ref={ref}>
<h1>Example content</h1>
<button
type="button"
onClick={() => {
announce('Announcement', {
from: ref.current,
})
}}
>
Example button
</button>
</dialog>
)
}
```

The `AriaStatus` and `AriaAlert` components automatically lookup the closest `dialog` so
there is no need to provide a `from` argument.

```tsx
import React from 'react'
import {AriaStatus} from '@primer/react'

function ExampleComponent() {
const [loading, setLoading] = React.useState(true)
return (
<dialog ref={ref}>
<h1>Example content</h1>
{loading ? <AriaStatus>Loading example dialog</AriaStatus> : <DialogContent />}
</dialog>
)
}
```

## Unresolved questions

- What should happen if a component makes excessive announcements due to a
component that it renders? Is it possible to "group" announcements?
- For programmatic announcements, how do we interop with existing proposals in
this space for `ariaNotify`?
- When should something use an ARIA Live region role component versus just using
the helper functions?

0 comments on commit 897033e

Please sign in to comment.