diff --git a/packages/website/docs/components/utilities/_category_.yml b/packages/website/docs/components/utilities/_category_.yml new file mode 100644 index 00000000000..0bd247842f4 --- /dev/null +++ b/packages/website/docs/components/utilities/_category_.yml @@ -0,0 +1,2 @@ +label: Utilities +collapsed: false diff --git a/packages/website/docs/components/utilities/accessibility/_category_.yml b/packages/website/docs/components/utilities/accessibility/_category_.yml new file mode 100644 index 00000000000..b2b6a7825d2 --- /dev/null +++ b/packages/website/docs/components/utilities/accessibility/_category_.yml @@ -0,0 +1,3 @@ +link: + type: doc + id: utilities_accessibility diff --git a/packages/website/docs/components/utilities/accessibility/class_name_preview.tsx b/packages/website/docs/components/utilities/accessibility/class_name_preview.tsx new file mode 100644 index 00000000000..0466b4fff03 --- /dev/null +++ b/packages/website/docs/components/utilities/accessibility/class_name_preview.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +import { EuiText } from '@elastic/eui'; + +export const ClassNamePreview = () => { + return ( + +

The next paragraph is hidden except for screen readers.

+

+ I am hidden except for screen readers +

+
+ ); +}; diff --git a/packages/website/docs/components/utilities/accessibility/function_preview.tsx b/packages/website/docs/components/utilities/accessibility/function_preview.tsx new file mode 100644 index 00000000000..e127c23fbb8 --- /dev/null +++ b/packages/website/docs/components/utilities/accessibility/function_preview.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { css } from '@emotion/react'; + +import { EuiText, euiScreenReaderOnly } from '@elastic/eui'; + +export const FunctionPreview = () => { + return ( + +

The next paragraph is hidden except for screen readers.

+

+ I am hidden except for screen readers +

+
+ ); +}; diff --git a/packages/website/docs/components/utilities/accessibility/hook_preview.tsx b/packages/website/docs/components/utilities/accessibility/hook_preview.tsx new file mode 100644 index 00000000000..4548d8a105e --- /dev/null +++ b/packages/website/docs/components/utilities/accessibility/hook_preview.tsx @@ -0,0 +1,29 @@ +import { css } from '@emotion/react'; + +import { EuiText, EuiCode, useEuiFocusRing, useEuiTheme } from '@elastic/eui'; + +export const HookPreview = () => { + const { euiTheme } = useEuiTheme(); + + return ( + +

+ +

+

+ +

+
+ ); +}; diff --git a/packages/website/docs/components/utilities/accessibility/overview.mdx b/packages/website/docs/components/utilities/accessibility/overview.mdx new file mode 100644 index 00000000000..80a26ff3c0e --- /dev/null +++ b/packages/website/docs/components/utilities/accessibility/overview.mdx @@ -0,0 +1,292 @@ +--- +slug: /utilities/accessibility +id: utilities_accessibility +--- + +import { Example } from '@site/src/components'; + +# Accessibility + +## Screen reader only + +Using `EuiScreenReaderOnly` hides the wrapped element from the page, but keeps it accessible for screen readers to provide more context. It should be used primarily to mask **text** and requires the child to be a single React element for cloning. + +:::warning WebAIM recommendation for screen reader-only content +"In most cases, if content (particularly content that provides functionality or interactivity) is important enough to provide to screen reader users, it should probably be made available to all users." [Learn more about invisible content](https://webaim.org/techniques/css/invisiblecontent/) +::: + +*Using a screen reader, verify that there is a second paragraph.* + + + ```tsx + +

This is the first paragraph. It is visible to all.

+ +

+ This is the second paragraph. It is hidden for sighted users but visible + to screen readers. +

+
+

This is the third paragraph. It is visible to all.

+
+ ``` +
+ +### Showing on focus + +If the wrapped element is **focusable**, you must use the `showOnFocus` prop to visibly show the element to all users when focused. + +*Tab through the following example with your keyboard to verify the element is visible on focus.* + + + ```tsx + +

+ This link is visible to all on focus:{' '} + + Link text + +

+

+ This tooltip + button is visible on focus within:{' '} + + + + + + + +

+
+ ``` +
+ +## Screen reader live region + +Using `EuiScreenReaderLive` to announce dynamic content, such as status changes based on user interaction. + +The configurable `role` and `aria-live` props default to `status` and `polite` respectively for unintrusive but timely update announcements. When not using the default values, be sure to follow [ARIA guidelines](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions) for `role` to `aria-live` mapping. + +Also consider other live region guidelines, such as that live regions must be present on initial page load, and should not be in a conditional JSX wrapper. + +import ScreenReaderLiveRegion from './screen_reader_live_region'; + + + ```tsx + import { useState, useCallback } from 'react'; + + import { + EuiButton, + EuiCode, + EuiScreenReaderLive, + EuiSpacer, + EuiText, + } from '@elastic/eui'; + + export default () => { + const [screenReaderText, setScreenReaderText] = useState( + 'You have no notifications.' + ); + + const startAnnouncements = useCallback(() => { + const randomNumber = Math.floor(Math.random() * 10) + 1; + + setScreenReaderText( + `You have ${randomNumber} new notification${randomNumber > 1 ? 's' : ''}.` + ); + }, []); + + return ( + <> + + Create screen reader announcement + + + +

+ Content announced by screen reader: + {screenReaderText} +

+ +

{screenReaderText}

+
+
+ + ); + }; + ``` +
+ +### Auto-focusing the live region on text change + +The `focusRegionOnTextChange` prop will automatically focus the `EuiScreenReaderLive` region (causing screen readers to read out the text content) whenever `children` changes. + +This is primarily useful for announcing navigation or page changes, when programmatically resetting focus location back to a certain part of the page (where the `EuiScreenReaderLive` is placed) is desired. + +*Using a screen reader, click the following navigation links and notice that when the new page is announced, focus is also set to the top of the body content.* + + + ```tsx + import React, { useState } from 'react'; + + import { + EuiScreenReaderLive, + EuiPageTemplate, + EuiSideNav, + EuiButton, + htmlIdGenerator, + } from '@elastic/eui'; + + export default () => { + const idGenerator = htmlIdGenerator('focusRegionOnTextChange'); + + const [pageTitle, setPageTitle] = useState('Home'); + + const sideNav = [ + { + name: 'Example side nav', + id: idGenerator(), + items: [ + { + name: 'Home', + id: idGenerator(), + onClick: () => setPageTitle('Home'), + }, + { + name: 'About', + id: idGenerator(), + onClick: () => setPageTitle('About'), + }, + { + name: 'Docs', + id: idGenerator(), + onClick: () => setPageTitle('Docs'), + }, + { + name: 'Contact', + id: idGenerator(), + onClick: () => setPageTitle('Contact'), + }, + ], + }, + ]; + + return ( + + + + + + + + {pageTitle} + + + Clicking a nav link and then pressing tab should focus this button + + + + ); + }; + ``` + + +## Skip link + +The `EuiSkipLink` component allows users to bypass navigation, or ornamental elements, and quickly reach the main content of the page. It requires a `destinationId` which should match the `id` of your main content. If your ID does not correspond to a valid element, the skip link will fall back to focusing the `
` tag on your page, if it exists. + +*Tab through the following section to verify the Skip to content button is visible on focus.* + + + ```tsx + import React from 'react'; + + import { EuiSkipLink, EuiText } from '@elastic/eui'; + + export default () => { + return ( + +

The following skip links are only visible on focus:

+ + Skips to this example container + + + Falls back to main container + +
+ ); + }; + ``` +
+ +## Styles helpers + +import { ClassNamePreview } from './class_name_preview'; +import { FunctionPreview } from './function_preview'; +import { HookPreview } from './hook_preview'; + + + + ### `.euiScreenReaderOnly` className + + This utility class allows you to apply the screen reader only CSS styles directly to your component. + + + + + + ```tsx +

+ ``` + + + + + + ### `euiScreenReaderOnly()` function + + This function allows you to apply the screen reader only CSS styles directly to your component. + + + + + + ```tsx + css` + ${euiScreenReaderOnly()} + ` + ``` + + + + + + ### `useEuiFocusRing(offset?, color?)` hook + + By default, all interactive elements will inherit the `outline` property from the reset file. However, some instances require adjustment to the `offset` and `color` of this outline. This helper function allows that customization of the focus outline. + + `offset: 'inset' | 'outset' | 'center' | CSSProperties['outlineOffset'];` + + `color: CSSProperties['outlineColor'];` + + + + + + ```tsx + css` + &:focus { + ${useEuiFocusRing('outset', euiTheme.colors.primary)} + } + ` + ``` + + + +## Props + +import docgen from '@elastic/eui-docgen/dist/components'; + + + + diff --git a/packages/website/docs/components/utilities/accessibility/screen_reader_live_region.tsx b/packages/website/docs/components/utilities/accessibility/screen_reader_live_region.tsx new file mode 100644 index 00000000000..9754c2aaa91 --- /dev/null +++ b/packages/website/docs/components/utilities/accessibility/screen_reader_live_region.tsx @@ -0,0 +1,41 @@ +import { useState, useCallback } from 'react'; + +import { + EuiButton, + EuiCode, + EuiScreenReaderLive, + EuiSpacer, + EuiText, +} from '@elastic/eui'; + +export default () => { + const [screenReaderText, setScreenReaderText] = useState( + 'You have no notifications.' + ); + + const startAnnouncements = useCallback(() => { + const randomNumber = Math.floor(Math.random() * 10) + 1; + + setScreenReaderText( + `You have ${randomNumber} new notification${randomNumber > 1 ? 's' : ''}.` + ); + }, []); + + return ( + <> + + Create screen reader announcement + + + +

+ Content announced by screen reader: + {screenReaderText} +

+ +

{screenReaderText}

+
+ + + ); +};