Skip to content

Commit 814a0ea

Browse files
committed
fix(ui-alerts): add variantScreenReaderLabel prop to Alert to improve screenreader usability
1 parent cf52c34 commit 814a0ea

File tree

5 files changed

+57
-6
lines changed

5 files changed

+57
-6
lines changed

.github/workflows/visual-regression.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
start: npm run dev
2727
working-directory: regression-test
2828
- name: Upload cypress artifact for chromatic
29-
- uses: actions/upload-artifact@v4
29+
uses: actions/upload-artifact@v4
3030
with:
3131
name: test-results
3232
path: regression-test/cypress/downloads

packages/ui-alerts/src/Alert/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,23 @@ type: example
2020
renderCloseButtonLabel="Close"
2121
margin="small"
2222
transition="none"
23+
variantScreenReaderLabel="Success, "
2324
>
2425
Sample success alert text. I will close w/o a transition out if you close me
2526
</Alert>
2627
<Alert
2728
variant="info"
2829
renderCloseButtonLabel="Close"
2930
margin="small"
31+
variantScreenReaderLabel="Information, "
3032
>
3133
Sample info text. I will fade out if you close me.
3234
</Alert>
3335
<Alert
3436
variant="error"
3537
renderCloseButtonLabel="Close"
3638
margin="small"
39+
variantScreenReaderLabel="Error, "
3740
>
3841
Sample error text that continues for a while
3942
to demonstrate what happens when the content stretches over
@@ -43,6 +46,7 @@ type: example
4346
<Alert
4447
variant="warning"
4548
margin="small"
49+
variantScreenReaderLabel="Warning, "
4650
>
4751
Sample warning text. This alert is not dismissible and cannot be closed.
4852
</Alert>
@@ -59,6 +63,7 @@ type: example
5963
variant="info"
6064
margin="small"
6165
timeout={5000}
66+
variantScreenReaderLabel="Information, "
6267
>
6368
Sample info text. I will fade out after 5 seconds
6469
</Alert>
@@ -111,6 +116,16 @@ For more information about live regions, see
111116
this.setState({ alerts })
112117
}
113118

119+
getScreenReaderLabel(variant) {
120+
const labels = {
121+
info: 'Information, ',
122+
success: 'Success, ',
123+
warning: 'Warning, ',
124+
error: 'Error, '
125+
}
126+
return labels[variant] || ''
127+
}
128+
114129
render() {
115130
return (
116131
<div>
@@ -125,6 +140,9 @@ For more information about live regions, see
125140
liveRegion={() => document.getElementById('flash-messages')}
126141
liveRegionPoliteness={alert.politeness}
127142
margin="small 0"
143+
variantScreenReaderLabel={this.getScreenReaderLabel(
144+
alert.variant
145+
)}
128146
>
129147
This is {alert.politeness === 'polite' ? 'a' : 'an'}{' '}
130148
{alert.politeness} {alert.variant} alert
@@ -325,6 +343,7 @@ type: embed
325343
<Figure.Item>Use the Error alert to notify user of an error</Figure.Item>
326344
<Figure.Item>Use the Warning alert to notify user of a warning</Figure.Item>
327345
<Figure.Item>Use the Success alert to notify user of a success event or action</Figure.Item>
346+
<Figure.Item>Use the `variantScreenReaderLabel` prop to indicate the alert variant to screen reader users</Figure.Item>
328347
</Figure>
329348
<Figure recommendation="no" title="Don't">
330349
<Figure.Item>Have alert messaging that is more than two lines long</Figure.Item>

packages/ui-alerts/src/Alert/index.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,11 @@ class Alert extends Component<AlertProps, AlertState> {
169169
}
170170

171171
createScreenreaderContentNode() {
172-
return <ScreenReaderContent>{this.props.children}</ScreenReaderContent>
172+
return (
173+
<ScreenReaderContent>
174+
{this.props.variantScreenReaderLabel || ''} {this.props.children}
175+
</ScreenReaderContent>
176+
)
173177
}
174178

175179
createScreenreaderAlert() {
@@ -260,7 +264,14 @@ class Alert extends Component<AlertProps, AlertState> {
260264
renderAlert() {
261265
// prevent onDismiss from being passed to the View component
262266
// eslint-disable-next-line @typescript-eslint/no-unused-vars
263-
const { margin, styles, children, onDismiss, ...props } = this.props
267+
const {
268+
margin,
269+
styles,
270+
children,
271+
onDismiss,
272+
variantScreenReaderLabel,
273+
...props
274+
} = this.props
264275
return (
265276
<View
266277
{...passthroughProps({ ...props })}
@@ -271,7 +282,14 @@ class Alert extends Component<AlertProps, AlertState> {
271282
elementRef={this.handleRef}
272283
>
273284
{this.renderIcon()}
274-
<div css={styles?.content}>{children}</div>
285+
<div css={styles?.content}>
286+
{variantScreenReaderLabel && (
287+
<span css={styles?.variantScreenReaderLabel}>
288+
{variantScreenReaderLabel}
289+
</span>
290+
)}
291+
{children}
292+
</div>
275293
{this.renderCloseButton()}
276294
</View>
277295
)

packages/ui-alerts/src/Alert/props.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ type AlertOwnProps = {
4646
* Determines color and icon
4747
*/
4848
variant?: 'info' | 'success' | 'warning' | 'error'
49+
/**
50+
* How the screen reader should announce the alert variant. While the `variant` prop sets the color and icon for the alert component, this label should be a textual representation of that information. So e.g. if the variant is `info`, this label could be "Information," or "Information alert,". Note the `,` at the end of the label which helps the screenreader to be more natural sounding.
51+
*/
52+
variantScreenReaderLabel?: string
4953
/**
5054
* Function that returns the DIV where screenreader alerts will be placed.
5155
*/
@@ -115,7 +119,9 @@ type AlertProps = AlertOwnProps &
115119
WithStyleProps<AlertTheme, AlertStyle> &
116120
WithDeterministicIdProps
117121

118-
type AlertStyle = ComponentStyle<'alert' | 'icon' | 'closeButton' | 'content'>
122+
type AlertStyle = ComponentStyle<
123+
'alert' | 'icon' | 'closeButton' | 'content' | 'variantScreenReaderLabel'
124+
>
119125

120126
const propTypes: PropValidators<PropKeys> = {
121127
children: PropTypes.node,
@@ -130,7 +136,8 @@ const propTypes: PropValidators<PropKeys> = {
130136
onDismiss: PropTypes.func,
131137
transition: PropTypes.oneOf(['none', 'fade']),
132138
open: PropTypes.bool,
133-
hasShadow: PropTypes.bool
139+
hasShadow: PropTypes.bool,
140+
variantScreenReaderLabel: PropTypes.string
134141
}
135142

136143
const allowedProps: AllowedPropKeys = [

packages/ui-alerts/src/Alert/styles.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ const generateStyle = (
114114
fontWeight: componentTheme.contentFontWeight,
115115
lineHeight: componentTheme.contentLineHeight,
116116
padding: componentTheme.contentPadding
117+
},
118+
variantScreenReaderLabel: {
119+
position: 'absolute',
120+
height: '1px',
121+
width: '1px',
122+
overflow: 'hidden',
123+
margin: '-1px'
117124
}
118125
}
119126
}

0 commit comments

Comments
 (0)