diff --git a/package.json b/package.json
index b0b608f013..9739e5e37d 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"lint:fix": "biome check --write && foundry run eslint . --ext .js,.jsx,.ts,.tsx --fix",
"lint:ci": "biome ci && foundry run eslint . --ext .js,.jsx,.ts,.tsx --quiet ",
"lint:css": "foundry run stylelint '**/*.css'",
+ "lint:css:fix": "foundry run stylelint '**/*.css' --fix",
"dev": "npm run docs:start",
"docs": "npm run docs:start",
"docs:start": "storybook dev -p 6006",
@@ -95,4 +96,4 @@
"vitest": "^2.0.3",
"vitest-github-actions-reporter": "^0.11.1"
}
-}
+}
\ No newline at end of file
diff --git a/packages/circuit-ui/components/ColorInput/ColorInput.module.css b/packages/circuit-ui/components/ColorInput/ColorInput.module.css
index 34f19ca918..99c4ecc5be 100644
--- a/packages/circuit-ui/components/ColorInput/ColorInput.module.css
+++ b/packages/circuit-ui/components/ColorInput/ColorInput.module.css
@@ -1,15 +1,15 @@
.suffix {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: var(--cui-spacings-exa);
+ height: var(--cui-spacings-exa);
overflow: hidden;
- border-top-right-radius: var(--cui-border-radius-byte);
- border-bottom-right-radius: var(--cui-border-radius-byte);
pointer-events: auto !important;
border: none;
border-left: 1px solid var(--cui-border-normal);
- width: var(--cui-spacings-exa);
- height: var(--cui-spacings-exa);
- position: absolute;
- top: 0;
- right: 0;
+ border-top-right-radius: var(--cui-border-radius-byte);
+ border-bottom-right-radius: var(--cui-border-radius-byte);
box-shadow: none;
}
@@ -20,13 +20,13 @@
line-height: var(--cui-spacings-mega);
}
-.colorInput {
- opacity: 0;
+.color-input {
width: var(--cui-spacings-exa);
height: var(--cui-spacings-exa);
+ padding: 0;
border: none;
box-shadow: none;
- padding: 0;
+ opacity: 0;
}
.input {
@@ -43,4 +43,4 @@
.colorpick:focus-within input {
box-shadow: 0 0 0 2px var(--cui-border-accent);
-}
\ No newline at end of file
+}
diff --git a/packages/circuit-ui/components/ColorInput/ColorInput.spec.tsx b/packages/circuit-ui/components/ColorInput/ColorInput.spec.tsx
index e323c5f449..6ed9b6aafc 100644
--- a/packages/circuit-ui/components/ColorInput/ColorInput.spec.tsx
+++ b/packages/circuit-ui/components/ColorInput/ColorInput.spec.tsx
@@ -16,7 +16,7 @@
import { describe, expect, it } from 'vitest';
import { createRef } from 'react';
-import { render, axe } from '../../util/test-utils.js';
+import { render, axe, screen } from '../../util/test-utils.js';
import type { InputElement } from '../Input/index.js';
import { ColorInput } from './ColorInput.js';
@@ -36,4 +36,29 @@ describe('ColorInput', () => {
const actual = await axe(container);
expect(actual).toHaveNoViolations();
});
+
+ describe('Labeling', () => {
+ const HEX_SYMBOL = '#';
+
+ it('should have the currency symbol as part of its accessible description', () => {
+ render();
+ expect(screen.getByRole('textbox')).toHaveAccessibleDescription(
+ HEX_SYMBOL,
+ );
+ });
+
+ it('should accept a custom description via aria-describedby', () => {
+ const customDescription = 'Custom description';
+ const customDescriptionId = 'customDescriptionId';
+ render(
+ <>
+ {customDescription}
+
+ >,
+ );
+ expect(screen.getByRole('textbox')).toHaveAccessibleDescription(
+ `${HEX_SYMBOL} ${customDescription}`,
+ );
+ });
+ });
});
diff --git a/packages/circuit-ui/components/ColorInput/ColorInput.tsx b/packages/circuit-ui/components/ColorInput/ColorInput.tsx
index 7f22e1b0c4..7c2ccaf2f6 100644
--- a/packages/circuit-ui/components/ColorInput/ColorInput.tsx
+++ b/packages/circuit-ui/components/ColorInput/ColorInput.tsx
@@ -67,7 +67,17 @@ export interface ColorInputProps
export const ColorInput = forwardRef(
(
- { onChange, className, value, defaultValue, pickerLabel, ...props },
+ {
+ onChange,
+ className,
+ value,
+ defaultValue,
+ pickerLabel,
+ readOnly,
+ 'aria-describedby': descriptionId,
+ id,
+ ...props
+ },
ref,
) => {
const [currentColor, setCurrentColor] = useState(
@@ -76,6 +86,10 @@ export const ColorInput = forwardRef(
const colorDisplayRef = useRef(null);
const colorPickerRef = useRef(null);
const pickerId = useId();
+ const hexSymbolId = useId();
+ const inputFallbackId = useId();
+ const inputId = id || inputFallbackId;
+ const descriptionIds = clsx(hexSymbolId, descriptionId);
const onPickerColorChange: ChangeEventHandler = (e) => {
setCurrentColor(e.target.value);
@@ -97,6 +111,8 @@ export const ColorInput = forwardRef(
}
}, [currentColor]);
+ // render suffix only once, otherwise if it gets re-rendered on color change
+ // the native color-picker widget might get mistakenly dismissed by the browser
const renderSuffix = useCallback(
() => (
(
),
@@ -124,7 +142,7 @@ export const ColorInput = forwardRef(
className={styles.colorpick}
renderPrefix={({ className: cn }) => (
- #
+ #
)}
renderSuffix={renderSuffix}
@@ -133,6 +151,9 @@ export const ColorInput = forwardRef(
maxLength={6}
pattern="[0-9a-f]{3,6}"
onChange={onInputChange}
+ aria-describedby={descriptionIds}
+ id={inputId}
+ readOnly={readOnly}
{...props}
/>
);