diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiColorPicker_EuiColorPicker_High_Contrast.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiColorPicker_EuiColorPicker_High_Contrast.png
index a5be15bcdac..9bfd4a8f2e8 100644
Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiColorPicker_EuiColorPicker_High_Contrast.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiColorPicker_EuiColorPicker_High_Contrast.png differ
diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiColorPicker_EuiColorPicker_High_Contrast_Dark_Mode.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiColorPicker_EuiColorPicker_High_Contrast_Dark_Mode.png
index 8f5c7bb4bdd..e4aa8814db6 100644
Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiColorPicker_EuiColorPicker_High_Contrast_Dark_Mode.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiColorPicker_EuiColorPicker_High_Contrast_Dark_Mode.png differ
diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiColorPicker_EuiColorPicker_Inline_With_All_Elements.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiColorPicker_EuiColorPicker_Inline_With_All_Elements.png
index 2e6d306fa0c..c7b453bb913 100644
Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiColorPicker_EuiColorPicker_Inline_With_All_Elements.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiColorPicker_EuiColorPicker_Inline_With_All_Elements.png differ
diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiColorPicker_EuiColorPicker_High_Contrast.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiColorPicker_EuiColorPicker_High_Contrast.png
index eaca71504a8..e3fb1a382cf 100644
Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiColorPicker_EuiColorPicker_High_Contrast.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiColorPicker_EuiColorPicker_High_Contrast.png differ
diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiColorPicker_EuiColorPicker_High_Contrast_Dark_Mode.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiColorPicker_EuiColorPicker_High_Contrast_Dark_Mode.png
index 77e2257ceb0..a2ba09904db 100644
Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiColorPicker_EuiColorPicker_High_Contrast_Dark_Mode.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiColorPicker_EuiColorPicker_High_Contrast_Dark_Mode.png differ
diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiColorPicker_EuiColorPicker_Inline_With_All_Elements.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiColorPicker_EuiColorPicker_Inline_With_All_Elements.png
index 65921952e89..da512bd5374 100644
Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiColorPicker_EuiColorPicker_Inline_With_All_Elements.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiColorPicker_EuiColorPicker_Inline_With_All_Elements.png differ
diff --git a/packages/eui/changelogs/upcoming/8639.md b/packages/eui/changelogs/upcoming/8639.md
new file mode 100644
index 00000000000..c42154a78f9
--- /dev/null
+++ b/packages/eui/changelogs/upcoming/8639.md
@@ -0,0 +1,7 @@
+**Accessibility**
+
+- Improved the accessibility of `EuiColorPicker` by:
+ - preventing duplicate color output for screen readers
+ - adding tooltips with visual color labels for the selected colors on the saturation and hue sliders
+ - updated accessible labels and announcements to be more descriptive
+
diff --git a/packages/eui/src/components/color_picker/__snapshots__/color_picker.test.tsx.snap b/packages/eui/src/components/color_picker/__snapshots__/color_picker.test.tsx.snap
index 0bf8946cce9..8ebe78405ba 100644
--- a/packages/eui/src/components/color_picker/__snapshots__/color_picker.test.tsx.snap
+++ b/packages/eui/src/components/color_picker/__snapshots__/color_picker.test.tsx.snap
@@ -313,19 +313,17 @@ exports[`EuiColorPicker inline 1`] = `
class="euiSaturation__saturation emotion-euiSaturation__saturation"
/>
-
- #ffeedd
+
Select the HSV color mode 'hue' value
-
- #ffeedd
-
-
+
+
+
+ Selected color
+ :
+ #FFEEDD
+
diff --git a/packages/eui/src/components/color_picker/__snapshots__/hue.test.tsx.snap b/packages/eui/src/components/color_picker/__snapshots__/hue.test.tsx.snap
index 6f0287ef11f..9ae6ebe6e76 100644
--- a/packages/eui/src/components/color_picker/__snapshots__/hue.test.tsx.snap
+++ b/packages/eui/src/components/color_picker/__snapshots__/hue.test.tsx.snap
@@ -11,23 +11,23 @@ exports[`EuiHue accepts a hex value 1`] = `
>
Select the HSV color mode 'hue' value
-
- #00FFFF
-
-
+
+
`;
@@ -43,21 +43,23 @@ exports[`EuiHue accepts a hue value 1`] = `
>
Select the HSV color mode 'hue' value
-
-
+
+
+
`;
@@ -73,21 +75,23 @@ exports[`EuiHue is rendered 1`] = `
>
Select the HSV color mode 'hue' value
-
-
+
+
+
`;
diff --git a/packages/eui/src/components/color_picker/__snapshots__/saturation.test.tsx.snap b/packages/eui/src/components/color_picker/__snapshots__/saturation.test.tsx.snap
index 8c911b9c214..689d1b2605e 100644
--- a/packages/eui/src/components/color_picker/__snapshots__/saturation.test.tsx.snap
+++ b/packages/eui/src/components/color_picker/__snapshots__/saturation.test.tsx.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`EuiHue accepts a color 1`] = `
+exports[`EuiSaturation accepts a color 1`] = `
-
+ class="euiToolTipAnchor emotion-euiToolTipAnchor-inlineBlock-euiSaturation__tooltip"
+ style="inset-inline-start: 0; inset-block-start: 0;"
+ >
+
+
`;
-exports[`EuiHue is rendered 1`] = `
+exports[`EuiSaturation is rendered 1`] = `
-
+ class="euiToolTipAnchor emotion-euiToolTipAnchor-inlineBlock-euiSaturation__tooltip"
+ style="inset-inline-start: 0; inset-block-start: 0;"
+ >
+
+
= ({
const [
popoverLabel,
colorLabel,
+ selectedColorLabel,
colorErrorMessage,
transparent,
alphaLabel,
@@ -221,6 +223,7 @@ export const EuiColorPicker: FunctionComponent = ({
[
'euiColorPicker.popoverLabel',
'euiColorPicker.colorLabel',
+ 'euiColorPicker.selectedColorLabel',
'euiColorPicker.colorErrorMessage',
'euiColorPicker.transparent',
'euiColorPicker.alphaLabel',
@@ -230,6 +233,7 @@ export const EuiColorPicker: FunctionComponent = ({
[
'Color selection dialog',
'Color value',
+ 'Selected color',
'Invalid color value',
'Transparent',
'Alpha channel (opacity) value',
@@ -514,6 +518,13 @@ export const EuiColorPicker: FunctionComponent = ({
onChange={handleHueSelection}
onKeyDown={handleOnKeyDown}
/>
+
+ {/* Note: using EuiScreenReaderLive didn't work as expected for VoiceOver */}
+
+ {/* use uppercase to ensure letters are spoken separately */}
+ {selectedColorLabel}: {chromaColor?.hex().toUpperCase()}
+
+
>
)}
{showSwatches && (
diff --git a/packages/eui/src/components/color_picker/hue.styles.ts b/packages/eui/src/components/color_picker/hue.styles.ts
index 5cbf66c581e..662067f1ebd 100644
--- a/packages/eui/src/components/color_picker/hue.styles.ts
+++ b/packages/eui/src/components/color_picker/hue.styles.ts
@@ -59,7 +59,7 @@ export const euiHueStyles = (euiThemeContext: UseEuiTheme) => {
})}
`,
- euiHue__range: css`
+ euiHue__tooltip: css`
${logicalCSS('height', thumbSize)}
/* Allow for overlap */
${logicalCSS('width', `calc(100% + 2px)`)}
@@ -69,6 +69,12 @@ export const euiHueStyles = (euiThemeContext: UseEuiTheme) => {
'margin-top',
mathWithUnits(height, (x) => x / -2)
)}
+ `,
+
+ euiHue__range: css`
+ ${logicalCSS('height', '100%')}
+ ${logicalCSS('width', '100%')}
+
/* Resets for the range */
appearance: none;
diff --git a/packages/eui/src/components/color_picker/hue.test.tsx b/packages/eui/src/components/color_picker/hue.test.tsx
index 4e191fd5764..bbd3d05213e 100644
--- a/packages/eui/src/components/color_picker/hue.test.tsx
+++ b/packages/eui/src/components/color_picker/hue.test.tsx
@@ -9,9 +9,14 @@
import React from 'react';
import { requiredProps } from '../../test/required_props';
import { shouldRenderCustomStyles } from '../../test/internal';
-import { render } from '../../test/rtl';
+import {
+ render,
+ waitForEuiToolTipHidden,
+ waitForEuiToolTipVisible,
+} from '../../test/rtl';
import { EuiHue } from './hue';
+import { fireEvent } from '@testing-library/react';
const onChange = () => {
/* empty */
@@ -50,4 +55,40 @@ describe('EuiHue', () => {
expect(container).toMatchSnapshot();
});
+
+ test('it renders a color label tooltip on hover', async () => {
+ const { getByText } = render(
+
+ );
+
+ const thumbElement = document.querySelector('.euiHue__range')!;
+
+ fireEvent.mouseOver(thumbElement);
+
+ await waitForEuiToolTipVisible();
+
+ expect(getByText('#00FFFF')).toBeInTheDocument();
+
+ fireEvent.mouseLeave(thumbElement);
+
+ await waitForEuiToolTipHidden();
+ });
+
+ test('it renders a color label tooltip on focus', async () => {
+ const { getByText } = render(
+
+ );
+
+ const thumbElement = document.querySelector('.euiHue__range')!;
+
+ fireEvent.focus(thumbElement);
+
+ await waitForEuiToolTipVisible();
+
+ expect(getByText('#00FFFF')).toBeInTheDocument();
+
+ fireEvent.blur(thumbElement);
+
+ await waitForEuiToolTipHidden();
+ });
});
diff --git a/packages/eui/src/components/color_picker/hue.tsx b/packages/eui/src/components/color_picker/hue.tsx
index fad046a5544..0c111961e90 100644
--- a/packages/eui/src/components/color_picker/hue.tsx
+++ b/packages/eui/src/components/color_picker/hue.tsx
@@ -16,9 +16,10 @@ import classNames from 'classnames';
import { useEuiMemoizedStyles } from '../../services';
import { CommonProps } from '../common';
import { EuiScreenReaderOnly } from '../accessibility';
-import { EuiI18n } from '../i18n';
+import { EuiI18n, useEuiI18n } from '../i18n';
import { euiHueStyles } from './hue.styles';
+import { EuiToolTip } from '../tool_tip';
const HUE_RANGE = 359;
@@ -43,10 +44,20 @@ export const EuiHue: FunctionComponent = ({
const classes = classNames('euiHue', className);
const styles = useEuiMemoizedStyles(euiHueStyles);
+ const [ariaValueText, ariaRoleDescription] = useEuiI18n(
+ ['euiHue.ariaValueText', 'euiHue.ariaRoleDescription'],
+ ['Hue', 'Hue slider']
+ );
+
const handleChange = (e: ChangeEvent) => {
onChange(Number(e.target.value));
};
+ const hueValue = typeof hue === 'string' ? parseInt(hue) : hue;
+ // align the tooltip contextually closer to the thumb
+ const tooltipPosition =
+ hueValue < Math.floor(HUE_RANGE / 2) ? 'left' : 'right';
+
return (
);
};
diff --git a/packages/eui/src/components/color_picker/saturation.styles.ts b/packages/eui/src/components/color_picker/saturation.styles.ts
index 39d78f8165b..55d2d042dc6 100644
--- a/packages/eui/src/components/color_picker/saturation.styles.ts
+++ b/packages/eui/src/components/color_picker/saturation.styles.ts
@@ -91,12 +91,18 @@ export const euiSaturationStyles = (euiThemeContext: UseEuiTheme) => {
background: linear-gradient(to top, rgba(0, 0, 0, 1), rgba(0, 0, 0, 0));
`,
- euiSaturation__indicator: css`
+ euiSaturation__tooltip: css`
z-index: 2;
position: absolute;
${logicalSizeCSS(indicatorSize)}
transform: translateX(-50%) translateY(-50%);
border-radius: 100%;
+ `,
+ euiSaturation__indicator: css`
+ position: absolute;
+ inset: 0;
+ ${logicalSizeCSS(indicatorSize)}
+ border-radius: 100%;
${highContrastModeStyles(euiThemeContext, {
none: `
diff --git a/packages/eui/src/components/color_picker/saturation.test.tsx b/packages/eui/src/components/color_picker/saturation.test.tsx
index 4d97fa38398..94ee7c19f2a 100644
--- a/packages/eui/src/components/color_picker/saturation.test.tsx
+++ b/packages/eui/src/components/color_picker/saturation.test.tsx
@@ -9,15 +9,20 @@
import React from 'react';
import { requiredProps } from '../../test/required_props';
import { shouldRenderCustomStyles } from '../../test/internal';
-import { render } from '../../test/rtl';
+import {
+ render,
+ waitForEuiToolTipHidden,
+ waitForEuiToolTipVisible,
+} from '../../test/rtl';
import { EuiSaturation } from './saturation';
+import { fireEvent } from '@testing-library/react';
const onChange = () => {
/* empty */
};
-describe('EuiHue', () => {
+describe('EuiSaturation', () => {
shouldRenderCustomStyles();
test('is rendered', () => {
@@ -39,4 +44,40 @@ describe('EuiHue', () => {
expect(container.firstChild).toMatchSnapshot();
});
+
+ test('it renders a color label tooltip on hover', async () => {
+ const { getByText } = render(
+
+ );
+
+ const thumbElement = document.querySelector('.euiSaturation__indicator')!;
+
+ fireEvent.mouseOver(thumbElement);
+
+ await waitForEuiToolTipVisible();
+
+ expect(getByText('#000000')).toBeInTheDocument();
+
+ fireEvent.mouseLeave(thumbElement);
+
+ await waitForEuiToolTipHidden();
+ });
+
+ test('it renders a color label tooltip on focus', async () => {
+ const { getByText } = render(
+
+ );
+
+ const thumbElement = document.querySelector('.euiSaturation__indicator')!;
+
+ fireEvent.focus(thumbElement);
+
+ await waitForEuiToolTipVisible();
+
+ expect(getByText('#000000')).toBeInTheDocument();
+
+ fireEvent.blur(thumbElement);
+
+ await waitForEuiToolTipHidden();
+ });
});
diff --git a/packages/eui/src/components/color_picker/saturation.tsx b/packages/eui/src/components/color_picker/saturation.tsx
index e557b6c5ce4..203c1ff4de4 100644
--- a/packages/eui/src/components/color_picker/saturation.tsx
+++ b/packages/eui/src/components/color_picker/saturation.tsx
@@ -28,6 +28,7 @@ import { isNil } from '../../services/predicate';
import { logicalStyles } from '../../global_styling';
import { CommonProps } from '../common';
import { useEuiI18n } from '../i18n';
+import { EuiToolTip } from '../tool_tip';
import { getEventPosition } from './utils';
import { euiSaturationStyles } from './saturation.styles';
@@ -74,10 +75,15 @@ export const EuiSaturation = forwardRef(
const id = useGeneratedHtmlId({ conditionalId: _id });
const instructionsId = `${id}-instructions`;
const indicatorId = `${id}-saturationIndicator`;
- const [roleDescString, instructionsString] = useEuiI18n(
- ['euiSaturation.ariaLabel', 'euiSaturation.screenReaderInstructions'],
+ const [ariaLabel, roleDescString, instructionsString] = useEuiI18n(
[
- 'HSV color mode saturation and value 2-axis slider',
+ 'euiSaturation.ariaLabel',
+ 'euiSaturation.roleDescription',
+ 'euiSaturation.screenReaderInstructions',
+ ],
+ [
+ 'Select a color',
+ 'HSV color mode saturation and value 2-axis slider.',
"Arrow keys to navigate the square color gradient. Coordinates will be used to calculate HSV color mode 'saturation' and 'value' numbers, in the range of 0 to 1. Left and right to change the saturation. Up and down change the value.",
]
);
@@ -206,18 +212,23 @@ export const EuiSaturation = forwardRef(
className="euiSaturation__saturation"
/>
-
-
- {hex}
-
+
+
+
{instructionsString}
diff --git a/packages/eui/src/components/tool_tip/tool_tip.tsx b/packages/eui/src/components/tool_tip/tool_tip.tsx
index 0cec26b9ac1..edd863febb0 100644
--- a/packages/eui/src/components/tool_tip/tool_tip.tsx
+++ b/packages/eui/src/components/tool_tip/tool_tip.tsx
@@ -292,7 +292,10 @@ export class EuiToolTip extends Component {
onEscapeKey = (event: React.KeyboardEvent) => {
if (event.key === keys.ESCAPE) {
- if (this.state.visible) event.stopPropagation();
+ // when the tooltip is only visual, we don't want it to add an additional key stop
+ if (!this.props.disableScreenReaderOutput) {
+ if (this.state.visible) event.stopPropagation();
+ }
this.setState({ hasFocus: false }); // Allows mousing over back into the tooltip to work correctly
this.hideToolTip();
}