diff --git a/packages/eslint-plugin/changelogs/upcoming/9436.md b/packages/eslint-plugin/changelogs/upcoming/9436.md
new file mode 100644
index 000000000000..35c8e9de2d84
--- /dev/null
+++ b/packages/eslint-plugin/changelogs/upcoming/9436.md
@@ -0,0 +1 @@
+- Updated `no-unnamed-interactive-element` to include checking `EuiColorPicker`
\ No newline at end of file
diff --git a/packages/eslint-plugin/src/rules/a11y/no_unnamed_interactive_element.test.ts b/packages/eslint-plugin/src/rules/a11y/no_unnamed_interactive_element.test.ts
index 2588150fe624..30428956d051 100644
--- a/packages/eslint-plugin/src/rules/a11y/no_unnamed_interactive_element.test.ts
+++ b/packages/eslint-plugin/src/rules/a11y/no_unnamed_interactive_element.test.ts
@@ -44,6 +44,10 @@ ruleTester.run('NoUnnamedInteractiveElement', NoUnnamedInteractiveElement, {
code: ' ',
languageOptions,
},
+ {
+ code: ' ',
+ languageOptions,
+ },
// Wrapped in EuiFormRow with label
{
code: ' ',
@@ -53,6 +57,10 @@ ruleTester.run('NoUnnamedInteractiveElement', NoUnnamedInteractiveElement, {
code: ' ',
languageOptions,
},
+ {
+ code: ' ',
+ languageOptions,
+ },
],
invalid: [
// Missing a11y prop for interactive components
@@ -101,6 +109,11 @@ ruleTester.run('NoUnnamedInteractiveElement', NoUnnamedInteractiveElement, {
languageOptions,
errors: [{ messageId: 'missingA11y' }],
},
+ {
+ code: ' ',
+ languageOptions,
+ errors: [{ messageId: 'missingA11y' }],
+ },
// Wrapped but missing label
{
code: ' ',
@@ -112,5 +125,10 @@ ruleTester.run('NoUnnamedInteractiveElement', NoUnnamedInteractiveElement, {
languageOptions,
errors: [{ messageId: 'missingA11y' }],
},
+ {
+ code: ' ',
+ languageOptions,
+ errors: [{ messageId: 'missingA11y' }],
+ },
],
});
diff --git a/packages/eslint-plugin/src/rules/a11y/no_unnamed_interactive_element.ts b/packages/eslint-plugin/src/rules/a11y/no_unnamed_interactive_element.ts
index c6425bd17d3c..a04f19c7d88c 100644
--- a/packages/eslint-plugin/src/rules/a11y/no_unnamed_interactive_element.ts
+++ b/packages/eslint-plugin/src/rules/a11y/no_unnamed_interactive_element.ts
@@ -24,6 +24,7 @@ const interactiveComponents = [
'EuiPagination',
'EuiTreeView',
'EuiBreadcrumbs',
+ 'EuiColorPicker',
] as const;
const wrappingComponents = ['EuiFormRow'] as const;
@@ -54,7 +55,10 @@ export const NoUnnamedInteractiveElement = ESLintUtils.RuleCreator.withoutDocs({
function report(opening: TSESTree.JSXOpeningElement) {
if (opening.name.type !== 'JSXIdentifier') return;
const component = opening.name.name;
- const allowed = getAllowedA11yPropNamesForComponent(component, a11yConfig).join(', ');
+ const allowed = getAllowedA11yPropNamesForComponent(
+ component,
+ a11yConfig
+ ).join(', ');
context.report({
node: opening,
messageId: 'missingA11y',
diff --git a/packages/eui/changelogs/upcoming/9436.md b/packages/eui/changelogs/upcoming/9436.md
new file mode 100644
index 000000000000..a892c0642bcb
--- /dev/null
+++ b/packages/eui/changelogs/upcoming/9436.md
@@ -0,0 +1,3 @@
+**Accessibility**
+
+- Fixed `aria-label` not being applied to `EuiColorPicker`'s input element
\ No newline at end of file
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 3d4ffc901d90..1a18fb64effb 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
@@ -26,7 +26,8 @@ exports[`EuiColorPicker color empty string 1`] = `
+
+
+ Press the escape key to close the popover
+
+
+ Press the down key to open a popover containing color options
+
+
@@ -78,7 +95,8 @@ exports[`EuiColorPicker color null 1`] = `
+
+
+ Press the escape key to close the popover
+
+
+ Press the down key to open a popover containing color options
+
+
@@ -130,13 +164,30 @@ exports[`EuiColorPicker color valid string 1`] = `
+
+
+ Press the escape key to close the popover
+
+
+ Press the down key to open a popover containing color options
+
+
@@ -181,13 +232,30 @@ exports[`EuiColorPicker compressed 1`] = `
+
+
+ Press the escape key to close the popover
+
+
+ Press the down key to open a popover containing color options
+
+
@@ -232,7 +300,8 @@ exports[`EuiColorPicker disabled 1`] = `
+
+
+ Press the escape key to close the popover
+
+
+ Press the down key to open a popover containing color options
+
+
@@ -271,13 +356,30 @@ exports[`EuiColorPicker fullWidth 1`] = `
+
+
+ Press the escape key to close the popover
+
+
+ Press the down key to open a popover containing color options
+
+
@@ -531,13 +633,30 @@ exports[`EuiColorPicker isClearable 1`] = `
+
+
+ Press the escape key to close the popover
+
+
+ Press the down key to open a popover containing color options
+
+
@@ -592,7 +711,8 @@ exports[`EuiColorPicker placeholder 1`] = `
+
+
+ Press the escape key to close the popover
+
+
+ Press the down key to open a popover containing color options
+
+
@@ -661,13 +797,30 @@ exports[`EuiColorPicker prepend and append 1`] = `
+
+
+ Press the escape key to close the popover
+
+
+ Press the down key to open a popover containing color options
+
+
@@ -729,7 +882,8 @@ exports[`EuiColorPicker readOnly 1`] = `
+
+
+ Press the escape key to close the popover
+
+
+ Press the down key to open a popover containing color options
+
+
@@ -768,13 +938,30 @@ exports[`EuiColorPicker renders 1`] = `
+
+
+ Press the escape key to close the popover
+
+
+ Press the down key to open a popover containing color options
+
+
@@ -819,13 +1006,30 @@ exports[`EuiColorPicker showAlpha 1`] = `
+
+
+ Press the escape key to close the popover
+
+
+ Press the down key to open a popover containing color options
+
+
diff --git a/packages/eui/src/components/color_picker/color_picker.stories.tsx b/packages/eui/src/components/color_picker/color_picker.stories.tsx
index e7349adc7ecb..3d1fe847c3ae 100644
--- a/packages/eui/src/components/color_picker/color_picker.stories.tsx
+++ b/packages/eui/src/components/color_picker/color_picker.stories.tsx
@@ -10,7 +10,8 @@ import React, { FunctionComponent, useState, useEffect } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { enableFunctionToggleControls } from '../../../.storybook/utils';
-import { euiPaletteColorBlind } from '../..//services';
+import { euiPaletteColorBlind } from '../../services';
+import { EuiFormRow } from '../form';
import { EuiColorPicker, EuiColorPickerProps } from './color_picker';
const meta: Meta = {
@@ -45,6 +46,22 @@ export const Playground: Story = {
render: (args) => ,
};
+export const InFormRow: Story = {
+ name: 'In FormRow',
+ parameters: {
+ loki: {
+ // The visual composition of label + select is tested by form controls separately
+ skip: true,
+ },
+ },
+ ...Playground,
+ render: (args) => (
+
+
+
+ ),
+};
+
export const InlineWithAllElements: Story = {
tags: ['vrt-only'],
args: {
diff --git a/packages/eui/src/components/color_picker/color_picker.tsx b/packages/eui/src/components/color_picker/color_picker.tsx
index e5babd1c12de..58868be0c5e9 100644
--- a/packages/eui/src/components/color_picker/color_picker.tsx
+++ b/packages/eui/src/components/color_picker/color_picker.tsx
@@ -23,6 +23,7 @@ import {
useEuiMemoizedStyles,
keys,
useEuiPaletteColorBlind,
+ useGeneratedHtmlId,
} from '../../services';
import { CommonProps } from '../common';
import {
@@ -209,6 +210,9 @@ export const EuiColorPicker: FunctionComponent = ({
isClearable = false,
placeholder,
'data-test-subj': dataTestSubj,
+ 'aria-label': _ariaLabel,
+ 'aria-labelledby': ariaLabelledby,
+ 'aria-describedby': ariaDescribedby,
}) => {
const [
popoverLabel,
@@ -219,6 +223,7 @@ export const EuiColorPicker: FunctionComponent = ({
alphaLabel,
openLabel,
closeLabel,
+ ariaLabel,
] = useEuiI18n(
[
'euiColorPicker.popoverLabel',
@@ -229,6 +234,7 @@ export const EuiColorPicker: FunctionComponent = ({
'euiColorPicker.alphaLabel',
'euiColorPicker.openLabel',
'euiColorPicker.closeLabel',
+ 'euiColorPicker.ariaLabel',
],
[
'Color selection dialog',
@@ -239,9 +245,19 @@ export const EuiColorPicker: FunctionComponent = ({
'Alpha channel (opacity) value',
'Press the escape key to close the popover',
'Press the down key to open a popover containing color options',
+ 'Select a color',
]
);
+ const openLabelId = useGeneratedHtmlId({
+ prefix: 'colorPicker',
+ suffix: 'openLabel',
+ });
+ const closeLabelId = useGeneratedHtmlId({
+ prefix: 'colorPicker',
+ suffix: 'closeLabel',
+ });
+
const defaultSwatches = useEuiPaletteColorBlind();
const swatches = _swatches ?? defaultSwatches;
@@ -621,9 +637,35 @@ export const EuiColorPicker: FunctionComponent = ({
fullWidth={fullWidth}
autoComplete="off"
data-test-subj={testSubjAnchor}
- aria-label={isColorSelectorShown ? openLabel : closeLabel}
+ // if an id is provided it might be used in combination with `htmlFor` on a label,
+ // so we don't want to override it with a fallback `aria-label`
+ aria-label={
+ _ariaLabel
+ ? _ariaLabel
+ : id || ariaLabelledby
+ ? undefined
+ : ariaLabel
+ }
+ aria-labelledby={ariaLabelledby}
+ aria-describedby={classNames(
+ isColorSelectorShown ? openLabelId : closeLabelId,
+ ariaDescribedby
+ )}
controlOnly // Don't need two EuiFormControlwrappers
/>
+
+
+
+ {/* Separate hint messages that are toggled on the id work more
+ reliably to prevent stale messages in VO/Safari */}
+
+ {openLabel}
+
+
+ {closeLabel}
+
+
+
);
}