diff --git a/src-docs/src/views/skeleton/skeleton_example.js b/src-docs/src/views/skeleton/skeleton_example.js
index be4253f7eae..eb6ea409273 100644
--- a/src-docs/src/views/skeleton/skeleton_example.js
+++ b/src-docs/src/views/skeleton/skeleton_example.js
@@ -1,4 +1,5 @@
import React from 'react';
+import { Link } from 'react-router-dom';
import { GuideSectionTypes } from '../../components';
import {
@@ -7,6 +8,7 @@ import {
EuiSkeletonRectangle,
EuiSkeletonCircle,
EuiSkeletonLoading,
+ EuiScreenReaderLive,
EuiText,
EuiSpacer,
EuiCallOut,
@@ -70,6 +72,15 @@ const skeletonLoadingSnippet = ` `;
+import SkeletonLiveProps from './skeleton_live_props';
+const skeletonLivePropsSource = require('!!raw-loader!./skeleton_live_props');
+const skeletonLivePropsSnippet = ` `;
+
export const SkeletonExample = {
title: 'Skeleton',
intro: (
@@ -222,5 +233,42 @@ export const SkeletonExample = {
snippet: skeletonLoadingSnippet,
demo: ,
},
+ {
+ title: 'Customizing screen reader announcements',
+ source: [
+ {
+ type: GuideSectionTypes.JS,
+ code: skeletonLivePropsSource,
+ },
+ ],
+ text: (
+
+
+ EuiSkeleton components assume that the page starts
+ as loading and transitions to loaded, and the default screen reader
+ experience is set up accordingly (
+ announceLoadedStatus={'{true}'} ).
+
+
+ In scenarios where that is not the case (i.e., transitioning to
+ loading), you can customize what statuses are announced to screen
+ readers by setting announceLoadingStatus to true,
+ or announceLoadedStatus to false. Submitting the
+ below example announces a loading status, but not a loaded status.
+
+
+ As an optional escape hatch, ariaLiveProps is
+ also available and accepts any{' '}
+
+ EuiScreenReaderLive
+ {' '}
+ props.
+
+
+ ),
+ props: { EuiSkeletonLoading, EuiScreenReaderLive },
+ snippet: skeletonLivePropsSnippet,
+ demo: ,
+ },
],
};
diff --git a/src-docs/src/views/skeleton/skeleton_live_props.tsx b/src-docs/src/views/skeleton/skeleton_live_props.tsx
new file mode 100644
index 00000000000..22922224ff4
--- /dev/null
+++ b/src-docs/src/views/skeleton/skeleton_live_props.tsx
@@ -0,0 +1,60 @@
+import React, { useState } from 'react';
+
+import {
+ EuiSkeletonLoading,
+ EuiSkeletonRectangle,
+ EuiFieldText,
+ EuiButton,
+ EuiFlexGroup,
+ EuiFlexItem,
+ useEuiTheme,
+} from '../../../../src';
+
+export default () => {
+ const [isLoading, setIsLoading] = useState(false);
+
+ const simulateLoading = () => {
+ setIsLoading(true);
+ setTimeout(() => {
+ setIsLoading(false);
+ }, 3000);
+ };
+
+ const { euiTheme } = useEuiTheme();
+
+ return (
+ <>
+
+ }
+ loadedContent={
+
+ }
+ />
+ >
+ );
+};
diff --git a/src/components/skeleton/__snapshots__/skeleton_loading.test.tsx.snap b/src/components/skeleton/__snapshots__/skeleton_loading.test.tsx.snap
index 64eebfe087a..717965124f5 100644
--- a/src/components/skeleton/__snapshots__/skeleton_loading.test.tsx.snap
+++ b/src/components/skeleton/__snapshots__/skeleton_loading.test.tsx.snap
@@ -1,5 +1,74 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`EuiSkeletonLoading aria-live behavior announceLoadedStatus - allows turning off live announcements 1`] = `
+
+
+
+`;
+
+exports[`EuiSkeletonLoading aria-live behavior announceLoadingStatus - allows enabling live announcements 1`] = `
+
+
+
+ Loading Sample user data
+
+
+
+
+
+`;
+
+exports[`EuiSkeletonLoading aria-live behavior ariaLiveProps 1`] = `
+
+
+
+ Loaded Sample user data
+
+
+
+
+
+`;
+
exports[`EuiSkeletonLoading renders \`loadedContent\` when \`isLoading\` is false 1`] = `
= ({
size = 'm',
className,
contentAriaLabel,
+ announceLoadingStatus,
+ announceLoadedStatus,
+ ariaLiveProps,
ariaWrapperProps,
children,
...rest
@@ -49,6 +52,9 @@ export const EuiSkeletonCircle: FunctionComponent
= ({
}
loadedContent={children || ''}
contentAriaLabel={contentAriaLabel}
+ announceLoadingStatus={announceLoadingStatus}
+ announceLoadedStatus={announceLoadedStatus}
+ ariaLiveProps={ariaLiveProps}
{...ariaWrapperProps}
/>
);
diff --git a/src/components/skeleton/skeleton_loading.test.tsx b/src/components/skeleton/skeleton_loading.test.tsx
index 02d3e0121d2..6a2a774f627 100644
--- a/src/components/skeleton/skeleton_loading.test.tsx
+++ b/src/components/skeleton/skeleton_loading.test.tsx
@@ -43,4 +43,41 @@ describe('EuiSkeletonLoading', () => {
expect(queryByTestSubject('loaded')).toBeTruthy();
expect(container.firstChild).toMatchSnapshot();
});
+
+ describe('aria-live behavior', () => {
+ test('announceLoadingStatus - allows enabling live announcements', () => {
+ const { container, getByText } = render(
+
+ );
+
+ expect(getByText('Loading Sample user data')).toBeTruthy();
+ expect(container.firstChild).toMatchSnapshot();
+ });
+
+ test('announceLoadedStatus - allows turning off live announcements', () => {
+ const { container, queryByText } = render(
+
+ );
+
+ expect(queryByText('Loaded Sample user data')).toBeFalsy();
+ expect(container.firstChild).toMatchSnapshot();
+ });
+
+ test('ariaLiveProps', () => {
+ const { container, getByRole } = render(
+
+ );
+
+ expect(getByRole('alert')).toBeTruthy();
+ expect(container.firstChild).toMatchSnapshot();
+ });
+ });
});
diff --git a/src/components/skeleton/skeleton_loading.tsx b/src/components/skeleton/skeleton_loading.tsx
index 400e8e8a4a9..23711fabab2 100644
--- a/src/components/skeleton/skeleton_loading.tsx
+++ b/src/components/skeleton/skeleton_loading.tsx
@@ -9,7 +9,10 @@
import React, { FunctionComponent, HTMLAttributes, ReactElement } from 'react';
import { CommonProps } from '../common';
-import { EuiScreenReaderLive } from '../accessibility/screen_reader_live';
+import {
+ EuiScreenReaderLive,
+ EuiScreenReaderLiveProps,
+} from '../accessibility/screen_reader_live';
import { useEuiI18n } from '../i18n';
export type _EuiSkeletonAriaProps = {
@@ -24,14 +27,29 @@ export type _EuiSkeletonAriaProps = {
*/
contentAriaLabel?: string;
/**
- * Any optional props to pass to the `aria-busy` wrapper around the skeleton content
+ * Makes a live screen reader announcement when `isLoading` is true
+ * @default false
+ */
+ announceLoadingStatus?: boolean;
+ /**
+ * Makes a live screen reader announcement when `isLoading` is false
+ * @default true
+ */
+ announceLoadedStatus?: boolean;
+ /**
+ * Optional props to pass to the `aria-live` region that announces the loading status to screen readers.
+ * Accepts any `EuiScreenReaderLive` props.
+ */
+ ariaLiveProps?: Partial;
+ /**
+ * Optional props to pass to the `aria-busy` wrapper around the skeleton content
*/
ariaWrapperProps?: HTMLAttributes;
};
export type EuiSkeletonLoadingProps = CommonProps &
_EuiSkeletonAriaProps['ariaWrapperProps'] &
- Pick<_EuiSkeletonAriaProps, 'isLoading' | 'contentAriaLabel'> & {
+ Omit<_EuiSkeletonAriaProps, 'ariaWrapperProps'> & {
/**
* Content to display when loading
*/
@@ -45,24 +63,29 @@ export type EuiSkeletonLoadingProps = CommonProps &
export const EuiSkeletonLoading: FunctionComponent = ({
isLoading = true,
contentAriaLabel,
- loadingContent,
+ loadingContent: _loadingContent,
loadedContent,
+ announceLoadingStatus = false,
+ announceLoadedStatus = true,
+ ariaLiveProps,
...rest
}) => {
- const loadingAriaLabel = useEuiI18n(
- 'euiSkeletonLoading.loadingAriaText',
- 'Loading {contentAriaLabel}',
- { contentAriaLabel }
- );
const loadedAriaLive = useEuiI18n(
'euiSkeletonLoading.loadedAriaText',
'Loaded {contentAriaLabel}',
{ contentAriaLabel }
);
+
+ const loadingAriaLabel = useEuiI18n(
+ 'euiSkeletonLoading.loadingAriaText',
+ 'Loading {contentAriaLabel}',
+ { contentAriaLabel }
+ );
const loadingProps = {
'aria-label': loadingAriaLabel,
role: 'progressbar',
};
+ const loadingContent = React.cloneElement(_loadingContent, loadingProps);
return (
= ({
{...rest}
>
{isLoading ? (
- React.cloneElement(loadingContent, loadingProps)
+ <>
+ {announceLoadingStatus && (
+
+ {loadingAriaLabel}
+
+ )}
+ {loadingContent}
+ >
) : (
<>
- {loadedAriaLive}
+ {announceLoadedStatus && (
+
+ {loadedAriaLive}
+
+ )}
{loadedContent}
>
)}
diff --git a/src/components/skeleton/skeleton_rectangle.tsx b/src/components/skeleton/skeleton_rectangle.tsx
index b0b6d6a0284..154eb1a9a48 100644
--- a/src/components/skeleton/skeleton_rectangle.tsx
+++ b/src/components/skeleton/skeleton_rectangle.tsx
@@ -35,6 +35,9 @@ export const EuiSkeletonRectangle: FunctionComponent
style,
className,
contentAriaLabel,
+ announceLoadingStatus,
+ announceLoadedStatus,
+ ariaLiveProps,
ariaWrapperProps,
children,
...rest
@@ -56,6 +59,9 @@ export const EuiSkeletonRectangle: FunctionComponent
}
loadedContent={children || ''}
contentAriaLabel={contentAriaLabel}
+ announceLoadingStatus={announceLoadingStatus}
+ announceLoadedStatus={announceLoadedStatus}
+ ariaLiveProps={ariaLiveProps}
{...ariaWrapperProps}
/>
);
diff --git a/src/components/skeleton/skeleton_text.tsx b/src/components/skeleton/skeleton_text.tsx
index ac29cbeb8ee..437cccaab08 100644
--- a/src/components/skeleton/skeleton_text.tsx
+++ b/src/components/skeleton/skeleton_text.tsx
@@ -38,6 +38,9 @@ export const EuiSkeletonText: FunctionComponent = ({
size = 'm',
className,
contentAriaLabel,
+ announceLoadingStatus,
+ announceLoadedStatus,
+ ariaLiveProps,
ariaWrapperProps,
children,
...rest
@@ -61,6 +64,9 @@ export const EuiSkeletonText: FunctionComponent = ({
}
loadedContent={children || ''}
contentAriaLabel={contentAriaLabel}
+ announceLoadingStatus={announceLoadingStatus}
+ announceLoadedStatus={announceLoadedStatus}
+ ariaLiveProps={ariaLiveProps}
{...ariaWrapperProps}
/>
);
diff --git a/src/components/skeleton/skeleton_title.tsx b/src/components/skeleton/skeleton_title.tsx
index 4d70210af41..7186bd3ed8e 100644
--- a/src/components/skeleton/skeleton_title.tsx
+++ b/src/components/skeleton/skeleton_title.tsx
@@ -30,6 +30,9 @@ export const EuiSkeletonTitle: FunctionComponent = ({
size = 'm',
className,
contentAriaLabel,
+ announceLoadingStatus,
+ announceLoadedStatus,
+ ariaLiveProps,
ariaWrapperProps,
children,
...rest
@@ -50,6 +53,9 @@ export const EuiSkeletonTitle: FunctionComponent = ({
}
loadedContent={children || ''}
contentAriaLabel={contentAriaLabel}
+ announceLoadingStatus={announceLoadingStatus}
+ announceLoadedStatus={announceLoadedStatus}
+ ariaLiveProps={ariaLiveProps}
{...ariaWrapperProps}
/>
);
diff --git a/upcoming_changelogs/6752.md b/upcoming_changelogs/6752.md
new file mode 100644
index 00000000000..8e902048067
--- /dev/null
+++ b/upcoming_changelogs/6752.md
@@ -0,0 +1 @@
+- Updated all `EuiSkeleton` components with new props that allow for more control over screen reader live announcements: `announceLoadingStatus`, `announceLoadedStatus`, and `ariaLiveProps`