Skip to content

Commit

Permalink
[terra-list] Add programmatic context for list instructions (cerner#3688
Browse files Browse the repository at this point in the history
)

This change adds ariaDescribedBy, ariaDescription, and ariaDetails props by which consumers can provide information and instructions about how to interact with a list so that screen readers are able to convey the information to the user.

UXPLATFORM-7596
  • Loading branch information
sycombs authored Dec 16, 2022
1 parent ae5a20b commit 7698853
Show file tree
Hide file tree
Showing 15 changed files with 498 additions and 1 deletion.
3 changes: 3 additions & 0 deletions packages/terra-core-docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@
* Added
* Added Higher Order examples for `terra-profile-image`.
* Added Accessibility Guide for `terra-Profile-image`.

* Changed
* Added ariaLabel and ariaLabelledBy examples for `terra-toolbar`.
* Added Accordion Examples for `terra-section-header`.
* Added accessibility guide for `terra-divider`.
* Added tests and example for adding row header to `terra-html-table`.
* Added `terra-list` tests and examples for `ariaDescribedBy`, `ariaDescription`, and `ariaDetails` props.
* Added accessibility guide for `terra-html-table`.
* Added an accessibility hooks example for `terra-form-field` and `terra-input`.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { Badge } from 'terra-list/package.json?dev-site-package';
import ListExample from './example/List?dev-site-example';
import ListDividedExample from './example/ListDivided?dev-site-example';
import ListPaddedExample from './example/ListPadded?dev-site-example';
import AriaDescribedByVisibleList from './example/AriaDescribedByVisibleList?dev-site-example';
import AriaDescribedByHiddenList from './example/AriaDescribedByHiddenList?dev-site-example';
import AriaDescriptionList from './example/AriaDescriptionList?dev-site-example';
import AriaDetailsList from './example/AriaDetailsList?dev-site-example';

import ListPropsTable from 'terra-list/src/List?dev-site-props-table';

Expand Down Expand Up @@ -48,6 +52,29 @@ import List, { Item } from 'terra-list';
<ListExample title="Default List" />
<ListDividedExample title="Divided List" />
<ListPaddedExample title="Padded List" />
<AriaDescribedByVisibleList
title="Patient List with Visible Instructions using the ariaDescribedBy prop for accessibility"
description="For assistive technology users, this example adds the aria-describedby attribute
(in reference to the visible text above the list) to give additional instructions for how the user
should use this list."
/>
<AriaDescribedByHiddenList
title="Patient List with Visually Hidden Instructions using the ariaDescribedBy prop for accessibility"
description="For assistive technology users, this example adds the aria-describedby attribute
(in reference to visually hidden text) to give additional instructions for how the user
should use this list."
/>
<AriaDescriptionList
title="Patient List with instructions using the ariaDescription prop for accessibility"
description="For assistive technology users, this example adds the aria-description attribute
to give additional instructions for how the user should use this list."
/>
<AriaDetailsList
title="Patient List with Visible Instructions using the ariaDetails prop for accessibility"
description="For assistive technology users, this example adds the aria-details attribute
(in reference to the visible text above the list) to give additional instructions for how the user
should use this list."
/>

## List Props
<ListPropsTable />
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import List, { Item } from 'terra-list/lib/index';
import { Placeholder } from '@cerner/terra-docs';
import VisuallyHiddenText from 'terra-visually-hidden-text';
import classNames from 'classnames/bind';
import styles from './ListDocCommon.module.scss';

const cx = classNames.bind(styles);

const AriaDescribedByHiddenList = () => (
<>
<VisuallyHiddenText
id="list-help"
text="Select a patient from the list to view patient details."
/>
<List ariaDescribedBy="list-help">
<Item key="123">
<Placeholder title="John Smith" className={cx('placeholder')} />
</Item>
<Item key="124">
<Placeholder title="Mary Jones" className={cx('placeholder')} />
</Item>
<Item key="125">
<Placeholder title="Sam Brown" className={cx('placeholder')} />
</Item>
</List>
</>
);

export default AriaDescribedByHiddenList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import List, { Item } from 'terra-list/lib/index';
import { Placeholder } from '@cerner/terra-docs';
import classNames from 'classnames/bind';
import styles from './ListDocCommon.module.scss';

const cx = classNames.bind(styles);

const AriaDescribedByVisibleList = () => (
<>
<p id="list-help">
Select a patient from the list to view patient details.
</p>
<List ariaDescribedBy="list-help">
<Item key="123">
<Placeholder title="John Smith" className={cx('placeholder')} />
</Item>
<Item key="124">
<Placeholder title="Mary Jones" className={cx('placeholder')} />
</Item>
<Item key="125">
<Placeholder title="Sam Brown" className={cx('placeholder')} />
</Item>
</List>
</>
);

export default AriaDescribedByVisibleList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import List, { Item } from 'terra-list/lib/index';
import { Placeholder } from '@cerner/terra-docs';
import classNames from 'classnames/bind';
import styles from './ListDocCommon.module.scss';

const cx = classNames.bind(styles);

const AriaDescriptionList = () => (
<List ariaDescription="Select a patient from the list to view patient details.">
<Item key="123">
<Placeholder title="John Smith" className={cx('placeholder')} />
</Item>
<Item key="124">
<Placeholder title="Mary Jones" className={cx('placeholder')} />
</Item>
<Item key="125">
<Placeholder title="Sam Brown" className={cx('placeholder')} />
</Item>
</List>
);

export default AriaDescriptionList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import List, { Item } from 'terra-list/lib/index';
import { Placeholder } from '@cerner/terra-docs';
import classNames from 'classnames/bind';
import styles from './ListDocCommon.module.scss';

const cx = classNames.bind(styles);

const AriaDetailsList = () => (
<>
<p id="list-details">
Select a patient from the list to view patient details.
</p>
<List ariaDetails="list-details">
<Item key="123">
<Placeholder title="John Smith" className={cx('placeholder')} />
</Item>
<Item key="124">
<Placeholder title="Mary Jones" className={cx('placeholder')} />
</Item>
<Item key="125">
<Placeholder title="Sam Brown" className={cx('placeholder')} />
</Item>
</List>
</>
);

export default AriaDetailsList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import List, { Item } from 'terra-list/lib/index';
import VisuallyHiddenText from 'terra-visually-hidden-text';

const AriaDescribedByHiddenListTest = () => (
<>
<VisuallyHiddenText
id="list-help"
text="Select a patient from the list to view patient details."
/>
<List ariaDescribedBy="list-help">
<Item key="0">
<p>John Smith</p>
</Item>
<Item key="1">
<p>Mary Jones</p>
</Item>
<Item key="2">
<p>Sam Brown</p>
</Item>
</List>
</>
);

export default AriaDescribedByHiddenListTest;
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React, { useState } from 'react';
import List, { Item } from 'terra-list/lib/index';

const AriaDescribedBySelectableListTest = () => {
const [selected, setSelected] = useState({ selectedKey: null });

const handleSelectItem = (event, metaData) => {
if (selected.selectedKey !== metaData.key) {
setSelected({ selectedKey: metaData.key });
}
};

return (
<>
<p id="list-help">
Select a patient from the list to view patient details.
</p>
<List ariaDescribedBy="list-help" role="listbox">
<Item key="1" isSelectable isSelected={selected.selectedKey === '1'} metaData={{ key: '1' }} onSelect={handleSelectItem}>
<p>John Smith</p>
</Item>
<Item key="2" isSelectable isSelected={selected.selectedKey === '2'} metaData={{ key: '2' }} onSelect={handleSelectItem}>
<p>Mary Jones</p>
</Item>
<Item key="3" isSelectable isSelected={selected.selectedKey === '3'} metaData={{ key: '3' }} onSelect={handleSelectItem}>
<p>Sam Brown</p>
</Item>
</List>
</>
);
};

export default AriaDescribedBySelectableListTest;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import List, { Item } from 'terra-list/lib/index';

const AriaDescribedByVisibleListTest = () => (
<>
<p id="list-help">
Select a patient from the list to view patient details.
</p>
<List ariaDescribedBy="list-help">
<Item key="0">
<p>John Smith</p>
</Item>
<Item key="1">
<p>Mary Jones</p>
</Item>
<Item key="2">
<p>Sam Brown</p>
</Item>
</List>
</>
);

export default AriaDescribedByVisibleListTest;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';
import List, { Item } from 'terra-list/lib/index';

const AriaDescriptionListTest = () => (
<List ariaDescription="Select a patient from the list to view patient details.">
<Item key="0">
<p>John Smith</p>
</Item>
<Item key="1">
<p>Mary Jones</p>
</Item>
<Item key="2">
<p>Sam Brown</p>
</Item>
</List>
);

export default AriaDescriptionListTest;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import List, { Item } from 'terra-list/lib/index';

const AriaDetailsListTest = () => (
<>
<p id="list-details">
Select a patient from the list to view patient details.
</p>
<List ariaDetails="list-details">
<Item key="0">
<p>John Smith</p>
</Item>
<Item key="1">
<p>Mary Jones</p>
</Item>
<Item key="2">
<p>Sam Brown</p>
</Item>
</List>
</>
);

export default AriaDetailsListTest;
5 changes: 5 additions & 0 deletions packages/terra-list/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
* Changed
* Sections and subsections updated to add programmatic context to communicate hierarchal relationships

* Added
* Prop `ariaDescribedBy` - Associates a list with an element or elements that convey information about the list and how to interact with it.
* Prop `ariaDescription` - Conveys information about a list and how to interact with it.
* Prop `ariaDetails` - Associates a list with an element or elements that convey information about the list and how to interact with it.

## 4.51.5 - (July 5, 2022)

* Changed
Expand Down
48 changes: 47 additions & 1 deletion packages/terra-list/src/List.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,40 @@ import styles from './List.module.scss';
const cx = classNamesBind.bind(styles);

const propTypes = {
/**
* References a visible or visually hidden text element
* on the page that conveys information about the list
* and how to interact with it. Maps to [aria-describedby](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-describedby).
* Because `aria-describedby` has the most comprehensive
* screen reader support of `aria-describedby`, `aria-description`, and
* `aria-details`, it should be the first choice for conveying
* List instructions to screen readers. (Note: In some screen readers,
* including JAWS, `aria-describedby` is not supported for non-interactable
* elements. In these cases, use `aria-details`.)
*/
ariaDescribedBy: PropTypes.string,
/**
* String that provides information about the list and how to interact
* with it. Maps to [aria-description](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-description).
* When used with the `ariaDescribedBy` prop, `ariaDescribedBy`
* takes precedence in defining the accessible description property.
* Because `aria-description` still has limited screen reader support
* compared to `aria-describedby`, this prop should only be used in cases where
* `aria-describedby` is not sufficient.
*/
ariaDescription: PropTypes.string,
/**
* References a visible or visually hidden text element
* on the page that conveys information about the list
* and how to interact with it. Maps to
* [aria-details](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-details).
* Can be used alongside `ariaDescribedBy` or `ariaDescription`.
* `aria-details` is not supported in all screen reader/browser
* combinations. Consumers should test for different screen
* reader/browser combinations to ensure that instructions
* are consistently read.
*/
ariaDetails: PropTypes.string,
/**
* The children list items passed to the component.
*/
Expand Down Expand Up @@ -40,6 +74,9 @@ const defaultProps = {
};

const List = ({
ariaDescribedBy,
ariaDescription,
ariaDetails,
children,
dividerStyle,
paddingStyle,
Expand All @@ -61,12 +98,21 @@ const List = ({
);

const attrSpread = {};
attrSpread.role = 'list'; // Explicitly set role='list' as it's missing in Safari
if (role && role.length > 0 && role !== 'none') {
attrSpread.role = role;
}

return (
<ul {...customProps} {...attrSpread} className={listClassNames} ref={refCallback}>
<ul
{...customProps}
{...attrSpread}
aria-describedby={ariaDescribedBy}
aria-description={ariaDescription} // eslint-disable-line jsx-a11y/aria-props
aria-details={ariaDetails}
className={listClassNames}
ref={refCallback}
>
{children}
</ul>
);
Expand Down
Loading

0 comments on commit 7698853

Please sign in to comment.