Skip to content

Commit 897033e

Browse files
joshblackkhiga8owenniblockmperrotti
authored
[ADR] docs: add live-regions adr (#4611)
* 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 <[email protected]> * Update contributor-docs/adrs/adr-020-live-regions.md Co-authored-by: Kate Higa <[email protected]> * docs(live-region): update adr based on feedback, add more examples * Apply suggestions from code review Co-authored-by: Owen Niblock <[email protected]> * Update adr-020-live-regions.md * Update contributor-docs/adrs/adr-020-live-regions.md Co-authored-by: Mike Perrotti <[email protected]> * Update adr-020-live-regions.md * chore: run format --------- Co-authored-by: Josh Black <[email protected]> Co-authored-by: Kate Higa <[email protected]> Co-authored-by: Owen Niblock <[email protected]> Co-authored-by: Mike Perrotti <[email protected]>
1 parent 55e97a9 commit 897033e

File tree

1 file changed

+230
-0
lines changed

1 file changed

+230
-0
lines changed
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# ADR 020: Live Regions
2+
3+
## Status
4+
5+
| Stage | Status |
6+
| -------- | ------ |
7+
| Approved ||
8+
| Adopted | 🚧 |
9+
10+
## Context
11+
12+
There are several components in Primer React that make use of live regions.
13+
However, the library does not have a central pattern for making accessible live
14+
region announcements. This ADR documents the decision to use a central pattern
15+
for live region announcements that can be used across Primer and GitHub.
16+
17+
ARIA Live regions fall into several buckets that we may want to use as component
18+
authors:
19+
20+
- [ARIA live region roles](https://www.w3.org/TR/wai-aria-1.2/#live_region_roles), such as `alert`, `log`, or `status`
21+
- [The `aria-live` attribute](https://www.w3.org/TR/wai-aria-1.2/#aria-live) to turn an element into a live region
22+
23+
In components, we see the following scenarios in Primer React:
24+
25+
- 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
26+
- Announce the changes to the content of an element, like when the count inside of a
27+
button is incremented
28+
- Announce a message programmatically, such as the number of results for a query
29+
30+
Currently, contributors may reach for roles such as `alert` or `status` to
31+
achieve these scenarios. They may also add `aria-live="assertive"` or `aria-live="polite"` with an an `aria-atomic="true"` to an element explicitly.
32+
However, both of these approaches do not announce consistently across screen
33+
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.
34+
35+
For more information about the ways in which live regions may not work as
36+
expected, visit: [Why are my live regions not working?](https://tetralogical.com/blog/2024/05/01/why-are-my-live-regions-not-working/)
37+
38+
### Links & Resources for ARIA Live regions
39+
40+
- https://www.sarasoueidan.com/blog/accessible-notifications-with-aria-live-regions-part-1/
41+
- https://www.scottohara.me/blog/2022/02/05/are-we-live.html
42+
43+
## Decision
44+
45+
In order to have a common interop point for live region announcements, Primer React will
46+
make use of a `live-region` custom element from `@primer/live-region-element`.
47+
This package will be included and published from the `primer/react` repo.
48+
49+
The custom element exposes a way to make announcements that can be used
50+
across frameworks. This makes it useful not only for Primer but GitHub as a
51+
whole. The `@primer/live-region-element` exports two helpers to use for making
52+
announcements: `announce()` and `announceFromElement`. Both helpers can be used
53+
when working in Primer and by teams at GitHub.
54+
55+
In addition, `@primer/react` will leverage and export the following helpers for
56+
use within Primer React and GitHub:
57+
58+
- The `AriaStatus` component to correspond with `role="status"`
59+
- The `AriaAlert` component to correspond with `role="alert"`
60+
61+
Within `@primer/react`, we should lint against usage of `aria-live` and the
62+
corresponding roles (if possible) and suggest using these alternatives instead.
63+
64+
> [!NOTE]
65+
> Both `AriaStatus` and `AriaAlert` will trigger an announcement when the component is
66+
> rendered. As a result, they should only be used for dynamically rendered
67+
> content. Otherwise, they will trigger announcements on page load. In cases
68+
> where they should always be present, then the first message passed to the
69+
> component should be an empty string. Changes to the content of the component
70+
> will trigger subsequent announcements.
71+
72+
### Impact
73+
74+
This decision will impact existing usage of `aria-live`, `role="status"`, and
75+
`role="alert"` for the components listed below. These components will need to be
76+
updated to use the new approach, using one of the following approaches:
77+
78+
- Use `announce()` or `announceFromElement()`
79+
- Use `AriaStatus` or `AriaAlert`
80+
81+
In addition, we should make sure that `<live-region>` is successfully included
82+
in GitHub.
83+
84+
#### Instances of `aria-live="polite"`
85+
86+
- InputValidation
87+
- SelectPanel
88+
- TreeView
89+
90+
#### Instances of `aria-live="assertive"`
91+
92+
- InlineAutocomplete
93+
94+
### Instances of `role="status"`
95+
96+
- LiveRegion
97+
- TreeView
98+
- ActionList examples
99+
- Spinner
100+
101+
### Instances of `role="alert"`
102+
103+
None
104+
105+
### Instances of `LiveRegion`
106+
107+
- DataTable
108+
109+
## Examples
110+
111+
### Announce when content is shown
112+
113+
```tsx
114+
import React from 'react'
115+
import {AriaStatus} from '@primer/react'
116+
117+
function ExampleComponent() {
118+
const [loading, setLoading] = React.useState(true)
119+
if (loading) {
120+
return <AriaStatus>Example loading message</AriaStatus>
121+
}
122+
return <Page />
123+
}
124+
```
125+
126+
### Announce on content change
127+
128+
```tsx
129+
import {AriaStatus} from '@primer/react'
130+
import {useState} from 'react'
131+
132+
function ExampleComponent() {
133+
const [count, setCount] = useState(0)
134+
return (
135+
<button
136+
type="button"
137+
onClick={() => {
138+
setCount(count + 1)
139+
}}
140+
>
141+
<AriaStatus>Count {count}</AriaStatus>
142+
</button>
143+
)
144+
}
145+
```
146+
147+
### Announce programmatically
148+
149+
```tsx
150+
import {announce} from '@primer/live-region-element'
151+
import {useState} from 'react'
152+
153+
function ExampleComponent() {
154+
const [results, setResults] = useState(data)
155+
156+
return (
157+
<>
158+
<input
159+
type="text"
160+
onChange={event => {
161+
const filteredResults = data.filter(item => {
162+
/* ... */
163+
})
164+
setResults(filteredResults)
165+
announce(`${filteredResults.length} results available`)
166+
}}
167+
/>
168+
{/* ... */}
169+
</>
170+
)
171+
}
172+
```
173+
174+
### Use existing live region
175+
176+
The `announce()` and `announceFromElement()` helpers both accept a `from`
177+
argument that allow you to provide a reference from which these helpers should
178+
find an existing live region. This can be useful in contexts like a `dialog`
179+
where a live region must live in the dialog in order for announcements to occur.
180+
181+
```tsx
182+
import {announce} from '@primer/live-region-element'
183+
import React from 'react'
184+
185+
function ExampleComponent() {
186+
const ref = React.useRef<React.ElementRef<'dialog'>>(null)
187+
return (
188+
<dialog ref={ref}>
189+
<h1>Example content</h1>
190+
<button
191+
type="button"
192+
onClick={() => {
193+
announce('Announcement', {
194+
from: ref.current,
195+
})
196+
}}
197+
>
198+
Example button
199+
</button>
200+
</dialog>
201+
)
202+
}
203+
```
204+
205+
The `AriaStatus` and `AriaAlert` components automatically lookup the closest `dialog` so
206+
there is no need to provide a `from` argument.
207+
208+
```tsx
209+
import React from 'react'
210+
import {AriaStatus} from '@primer/react'
211+
212+
function ExampleComponent() {
213+
const [loading, setLoading] = React.useState(true)
214+
return (
215+
<dialog ref={ref}>
216+
<h1>Example content</h1>
217+
{loading ? <AriaStatus>Loading example dialog</AriaStatus> : <DialogContent />}
218+
</dialog>
219+
)
220+
}
221+
```
222+
223+
## Unresolved questions
224+
225+
- What should happen if a component makes excessive announcements due to a
226+
component that it renders? Is it possible to "group" announcements?
227+
- For programmatic announcements, how do we interop with existing proposals in
228+
this space for `ariaNotify`?
229+
- When should something use an ARIA Live region role component versus just using
230+
the helper functions?

0 commit comments

Comments
 (0)