Skip to content

feat: Improve keyboard & screen-reader accessibility#286

Open
Pareder wants to merge 2 commits into
react-component:masterfrom
Pareder:feat/improve-accessibility
Open

feat: Improve keyboard & screen-reader accessibility#286
Pareder wants to merge 2 commits into
react-component:masterfrom
Pareder:feat/improve-accessibility

Conversation

@Pareder

@Pareder Pareder commented Jun 5, 2026

Copy link
Copy Markdown

Summary

Makes the color picker fully operable by keyboard and meaningful to assistive technology. The saturation/brightness panel and the hue/alpha sliders previously responded to mouse/touch only; they now expose native slider semantics, full arrow-key control, focus indication, and localizable accessible names.

Motivation

The handles were non-focusable <div>s with no role or value, so keyboard and screen-reader users could not read or change the color. This addresses WCAG 2.1.1 (Keyboard), 2.4.7 (Focus Visible), and 4.1.2 (Name, Role, Value).

What changed

  1. Handles are now real sliders — each handle renders a visually-hidden native <input type="range"> (Handler.tsx). This gives us, for free, the slider role, aria-valuemin/max/now, and native keyboard handling (arrows, Home/End, PageUp/PageDown).
    • Hue / Alpha sliders (1-D): driven entirely by the native input.
    • Saturation/Brightness panel (2-D): one input drives the horizontal axis (saturation) natively; Up/Down are handled in a small onKeyDown to drive the vertical axis (brightness), since a native range is 1-D. Result: one focusable thumb where ←/→ = saturation and ↑/↓ = brightness.
  2. Hidden input ships hidden — the proxy input is hidden via inline styles (opacity: 0; pointer-events: none; position: absolute), not the stylesheet, so consumers who don't import assets/index.less (e.g. antd's own styling) get correct rendering with no setup. opacity (not display:none) keeps it focusable and in the a11y tree.
  3. Focus indication — added a :focus-visible / :focus-within outline on -handler so keyboard focus is visible on the thumb.
  4. New locale prop — all accessible names are localizable via a single locale prop on <ColorPicker>:
    locale?: {
      picker?: string;            // default 'Color picker'
      pickerDescription?: string; // default '2D slider' (aria-roledescription)
      hue?: string;               // default 'Hue'
      alpha?: string;             // default 'Alpha'
      saturation?: string;        // used in the panel's aria-valuetext
      brightness?: string;        // used in the panel's aria-valuetext
    };
    
  5. ColorBlock accepts standard div attributesColorBlockProps now extends React.HTMLAttributes<HTMLDivElement> and spreads them onto the root. This lets consumers attach interaction/ARIA props (role, tabIndex, aria-*, keyboard handlers) when using it as a clickable trigger, keeping the component itself presentational.

API additions (non-breaking)

  • ColorPicker / BaseColorPickerProps: new optional locale.
  • Slider (BaseSliderProps): new optional aria-label.
  • ColorBlock: props widened to HTMLAttributes<HTMLDivElement> (superset of the previous className/style/onClick).
  • New DOM element per handle: <input class="{prefix}-handler-range">.

Related issues

Fixes ant-design/ant-design#57935.

Summary by CodeRabbit

发布说明

  • 新功能
    • 增加颜色选择器文案本地化配置(支持自定义 picker/hue/alpha/saturation/brightness 等文案)。
    • 优化键盘交互,支持方向键进行色相、透明度与饱和度/亮度的调节(含上下限制与结果更新)。
  • 改进
    • 增强无障碍体验:完善手柄与滑块的 aria 标签/值文本展示。
    • 改进键盘聚焦可见性,聚焦状态增加明显描边反馈。
  • 测试
    • 更新并补充无障碍与键盘交互用例,调整颜色渲染断言方式。

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 02e1aa5a-370b-423a-bf71-2783caa25977

📥 Commits

Reviewing files that changed from the base of the PR and between 0a1b080 and 6f36c78.

📒 Files selected for processing (1)
  • tests/index.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/index.test.tsx

Walkthrough

ColorPicker 增加了基于 locale 的无障碍文案、Handler 的隐藏 range 键盘控制、焦点可见样式,以及覆盖这些交互与属性的测试。

Changes

Keyboard Navigation & Accessibility

Layer / File(s) Summary
Type definitions and accessibility contracts
src/interface.ts, src/components/Handler.tsx
BaseColorPickerProps 增加 locale 字段;HandlerAxisHandlerProps 定义了双轴键盘输入所需的值、范围和回调契约。
Handler 2D keyboard navigation
src/components/Handler.tsx
Handler 改为渲染隐藏的 range 输入,并处理横向与纵向按键、步进和边界钳制;onKeyUp 触发对应轴的完成回调。
Picker Handler integration with HSB channels
src/components/Picker.tsx
Picker 引入 generateColorlocale,通过 changeColor 更新 HSB 通道,并为 Handler 配置 x/y 轴的 aria 属性和变更回调。
Locale and aria-label propagation
src/ColorPicker.tsx, src/components/Slider.tsx
ColorPicker 定义默认 locale 并与外部 locale 合并后下传;Hue 和 Alpha 滑块补充 aria-label,并将相关属性传给子组件。
Focus styles and HTML attribute passthrough
assets/index.less, src/components/ColorBlock.tsx
Handler 新增 :focus-visible / :focus-within 描边样式;ColorBlock 改为透传标准 HTML 属性。
Accessibility test coverage
tests/index.test.tsx
测试更新了背景色断言方式,并新增 aria 文案、locale 覆写和键盘交互的可访问性测试。

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 我蹦进焦点里,键盘轻轻响,
隐形小滑条,带我去色彩上。
亮度与色相,跟着指尖转,
a11y 亮晶晶,胡萝卜都点赞。

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive 多数无障碍目标已覆盖,但未见对自定义触发器打开弹层后自动聚焦的明确实现证据。 补充或确认弹层打开时的内部聚焦处理,确保自定义触发器场景也满足 WCAG 2.4.3。
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题简洁且准确概括了本次无障碍键盘与读屏改进。
Out of Scope Changes check ✅ Passed 变更主要围绕可访问性、焦点样式、ARIA 属性和相关测试,没有明显无关改动。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed: private package registry requires authentication. Disable ESLint in CodeRabbit settings or use public packages.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enhances the accessibility of the ColorPicker component by introducing keyboard navigation (2D slider handler) for saturation and brightness, adding customizable locale support for screen reader labels, and updating components like Handler, Picker, and Slider to support range inputs and ARIA attributes. Feedback was provided to address potential race conditions during rapid keyboard interactions in controlled mode by using mutable refs to track the latest color and vertical axis values.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/components/Picker.tsx
Comment on lines +47 to +55
// ===================== Keyboard (2-D handler) =====================
const hsb = color.toHsb();

// Build a new color from the current one with a single HSB channel changed.
const changeColor = (channel: 's' | 'b', percent: number) => {
const next = generateColor({ ...hsb, [channel]: percent / 100 });
colorRef.current = next;
onChange(next);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In a controlled component, rapid keyboard interactions (like pressing or holding arrow keys) can trigger multiple state updates before the parent component re-renders and passes down the updated color prop. Because changeColor derives the next color from the potentially stale color prop, rapid changes to one channel (e.g., saturation) can revert changes made to the other channel (e.g., brightness) in rapid succession.

To prevent this race condition, we should keep colorRef.current in sync with the color prop during render, and derive the next color from colorRef.current instead of the color prop.

Suggested change
// ===================== Keyboard (2-D handler) =====================
const hsb = color.toHsb();
// Build a new color from the current one with a single HSB channel changed.
const changeColor = (channel: 's' | 'b', percent: number) => {
const next = generateColor({ ...hsb, [channel]: percent / 100 });
colorRef.current = next;
onChange(next);
};
// ===================== Keyboard (2-D handler) =====================
colorRef.current = color;
// Build a new color from the current one with a single HSB channel changed.
const changeColor = (channel: 's' | 'b', percent: number) => {
const currentHsb = colorRef.current.toHsb();
const next = generateColor({ ...currentHsb, [channel]: percent / 100 });
colorRef.current = next;
onChange(next);
};

Comment on lines +56 to 107
const Handler: React.FC<HandlerProps> = ({
size = 'default',
color,
prefixCls,
disabled,
x,
y,
}) => {
// The browser ignores Up/Down on a horizontal range, so the vertical axis is
// handled here: clamp to its own [min, max] and emit through its callbacks.
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (!y || !isVerticalKey(event.key)) {
return;
}
event.preventDefault();
const step = Number(y.step ?? 1) || 1;
const min = Number(y.min ?? 0);
const max = Number(y.max ?? 100);
const delta = event.key === 'ArrowUp' ? step : -step;
y.onChange(Math.min(max, Math.max(min, y.value + delta)));
};

return (
<div
className={clsx(`${prefixCls}-handler`, {
[`${prefixCls}-handler-sm`]: size === 'small',
})}
style={{ backgroundColor: color }}
/>
style={{ position: 'relative', backgroundColor: color }}
>
<input
step={1}
{...omit(x, ['onChangeComplete'])}
type="range"
className={`${prefixCls}-handler-range`}
style={RANGE_INPUT_STYLE}
disabled={disabled}
onChange={event => x.onChange(Number(event.target.value))}
onKeyDown={y ? handleKeyDown : undefined}
onKeyUp={event => {
if (!VALUE_KEYS.includes(event.key)) {
return;
}
if (y && isVerticalKey(event.key)) {
y.onChangeComplete(y.value);
} else {
x.onChangeComplete(Number(event.currentTarget.value));
}
}}
/>
</div>
);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Since the vertical axis (y) is not a native range input, its value changes are handled entirely in JavaScript via onKeyDown. In controlled mode, rapid key presses or holding down the arrow keys can cause handleKeyDown and onKeyUp to read stale y.value props before the parent component has committed the state update and re-rendered.

To ensure smooth and correct keyboard navigation for the vertical axis, we should track the latest value in a mutable ref (lastValueRef) and update it synchronously on each key down/up event.

const Handler: React.FC<HandlerProps> = ({
  size = 'default',
  color,
  prefixCls,
  disabled,
  x,
  y,
}) => {
  const lastValueRef = React.useRef(y?.value);
  if (y && y.value !== lastValueRef.current) {
    lastValueRef.current = y.value;
  }

  // The browser ignores Up/Down on a horizontal range, so the vertical axis is
  // handled here: clamp to its own [min, max] and emit through its callbacks.
  const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
    if (!y || !isVerticalKey(event.key)) {
      return;
    }
    event.preventDefault();
    const step = Number(y.step ?? 1) || 1;
    const min = Number(y.min ?? 0);
    const max = Number(y.max ?? 100);
    const delta = event.key === 'ArrowUp' ? step : -step;
    const nextValue = Math.min(max, Math.max(min, (lastValueRef.current ?? y.value) + delta));
    lastValueRef.current = nextValue;
    y.onChange(nextValue);
  };

  return (
    <div
      className={clsx(prefixCls + "-handler", {
        [prefixCls + "-handler-sm"]: size === "small",
      })}
      style={{ position: 'relative', backgroundColor: color }}
    >
      <input
        step={1}
        {...omit(x, ['onChangeComplete'])}
        type="range"
        className={prefixCls + "-handler-range"}
        style={RANGE_INPUT_STYLE}
        disabled={disabled}
        onChange={event => x.onChange(Number(event.target.value))}
        onKeyDown={y ? handleKeyDown : undefined}
        onKeyUp={event => {
          if (!VALUE_KEYS.includes(event.key)) {
            return;
          }
          if (y && isVerticalKey(event.key)) {
            y.onChangeComplete(lastValueRef.current ?? y.value);
          } else {
            x.onChangeComplete(Number(event.currentTarget.value));
          }
        }}
      />
    </div>
  );
};

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/Picker.tsx (1)

20-75: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

请为 locale 增加本地兜底,避免可选值被直接解引用导致崩溃

Line 20 的 locale 是可选,但 Line 71-75 直接读取 locale.picker / locale.saturation 等;一旦该层收到 undefined,会触发运行时异常并中断交互。

建议修复(示例)
 const Picker: FC<PickerProps> = ({
   color,
   onChange,
   prefixCls,
   onChangeComplete,
   disabled,
   locale,
 }) => {
+  const pickerLabel = locale?.picker ?? 'Color picker';
+  const pickerDescription = locale?.pickerDescription;
+  const saturationLabel = locale?.saturation ?? 'Saturation';
+  const brightnessLabel = locale?.brightness ?? 'Brightness';
+
   const pickerRef = useRef();
   const transformRef = useRef();
   const colorRef = useRef(color);
@@
             x={{
-              'aria-label': locale.picker,
-              'aria-roledescription': locale.pickerDescription,
-              'aria-valuetext': `${locale.saturation} ${Math.round(
+              'aria-label': pickerLabel,
+              'aria-roledescription': pickerDescription,
+              'aria-valuetext': `${saturationLabel} ${Math.round(
                 hsb.s * 100,
-              )}%, ${locale.brightness} ${Math.round(hsb.b * 100)}%`,
+              )}%, ${brightnessLabel} ${Math.round(hsb.b * 100)}%`,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Picker.tsx` around lines 20 - 75, The Picker component reads
optional prop locale directly (used in Handler aria attributes like
locale.picker, locale.saturation, locale.brightness) which can be undefined and
cause runtime crashes; fix by providing a safe default fallback for locale at
the top of the component (e.g., const safeLocale = locale ?? DEFAULT_LOCALE) or
via destructuring with defaults, then replace direct uses of locale.* with
safeLocale.* (ensure DEFAULT_LOCALE is defined or imported) so aria labels
always have values.
🧹 Nitpick comments (2)
tests/index.test.tsx (1)

633-642: ⚡ Quick win

建议添加 onChangeComplete 回调验证以保持测试一致性。

前面的键盘导航测试(色调、饱和度、亮度)都会验证 onChangeComplete 是否被调用,但此测试仅检查透明度值的变化,缺少对 onChangeComplete 的验证。

为了保持测试套件的一致性并确保透明度滑块的 onChangeComplete 行为得到覆盖,建议添加相应的断言。

🧪 建议的测试增强
 it('Should change alpha when the alpha slider value changes via keyboard', () => {
-  render(<Controlled />);
+  const onChangeComplete = vi.fn();
+  render(<Controlled onChangeComplete={onChangeComplete} />);

   const alpha = screen.getByLabelText('Alpha');
   fireEvent.change(alpha, { target: { value: '50' } });
+  fireEvent.keyUp(alpha, { key: 'ArrowRight' });

   expect(document.querySelector('.pick-color').innerHTML).toBe(
     'hsba(215, 91%, 100%, 0.50)',
   );
+  expect(onChangeComplete).toHaveBeenCalled();
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/index.test.tsx` around lines 633 - 642, The alpha-slider test ("Should
change alpha when the alpha slider value changes via keyboard") currently only
asserts the DOM color change; add a mock for onChangeComplete and pass it into
the Controlled test component (Controlled) so you can assert it was called when
the alpha input is changed; specifically, create a jest.fn() for
onChangeComplete, render <Controlled onChangeComplete={onChangeCompleteMock} />,
fireEvent.change on the element grabbed by screen.getByLabelText('Alpha'), then
assert onChangeCompleteMock was called (and optionally called with the expected
alpha/hsba payload) in addition to the existing
document.querySelector('.pick-color').innerHTML check.
assets/index.less (1)

58-62: 💤 Low value

建议验证焦点轮廓的对比度。

当前焦点样式使用 outline: 2px solid #1677ff。虽然 `outline-offset: 1px` 和处理器的白色边框(`border: 2px solid `#fff)为轮廓提供了一定的背景对比,但在某些极浅或白色的色调下,蓝色轮廓的可见性可能仍然不足。

考虑添加一个双层轮廓(例如使用 box-shadow 添加外层深色描边)以确保在所有颜色背景下都有足够的对比度。

♻️ 建议的增强方案
 &:focus-visible,
 &:focus-within {
   outline: 2px solid `#1677ff`;
   outline-offset: 1px;
+  box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.8);
 }

这会在蓝色轮廓外添加一个半透明白色光晕,确保在深色和浅色背景下都有良好的可见性。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/index.less` around lines 58 - 62, The focus outline (selectors
&:focus-visible and &:focus-within) uses only outline: 2px solid `#1677ff` which
may lack contrast on some backgrounds; update these selectors to add a
double-layer visual (keep the existing blue outline but add an outer ring via
box-shadow or an extra outline) so the focus is always visible—for example, add
a box-shadow that renders a thin semi-opaque white or dark halo outside the blue
outline (e.g., an outer 2–3px ring using rgba(...) for light/dark backgrounds)
and keep outline-offset as needed; modify the &:focus-visible and &:focus-within
block to include this additional visual ring while preserving the existing
outline and offset.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/components/Picker.tsx`:
- Around line 20-75: The Picker component reads optional prop locale directly
(used in Handler aria attributes like locale.picker, locale.saturation,
locale.brightness) which can be undefined and cause runtime crashes; fix by
providing a safe default fallback for locale at the top of the component (e.g.,
const safeLocale = locale ?? DEFAULT_LOCALE) or via destructuring with defaults,
then replace direct uses of locale.* with safeLocale.* (ensure DEFAULT_LOCALE is
defined or imported) so aria labels always have values.

---

Nitpick comments:
In `@assets/index.less`:
- Around line 58-62: The focus outline (selectors &:focus-visible and
&:focus-within) uses only outline: 2px solid `#1677ff` which may lack contrast on
some backgrounds; update these selectors to add a double-layer visual (keep the
existing blue outline but add an outer ring via box-shadow or an extra outline)
so the focus is always visible—for example, add a box-shadow that renders a thin
semi-opaque white or dark halo outside the blue outline (e.g., an outer 2–3px
ring using rgba(...) for light/dark backgrounds) and keep outline-offset as
needed; modify the &:focus-visible and &:focus-within block to include this
additional visual ring while preserving the existing outline and offset.

In `@tests/index.test.tsx`:
- Around line 633-642: The alpha-slider test ("Should change alpha when the
alpha slider value changes via keyboard") currently only asserts the DOM color
change; add a mock for onChangeComplete and pass it into the Controlled test
component (Controlled) so you can assert it was called when the alpha input is
changed; specifically, create a jest.fn() for onChangeComplete, render
<Controlled onChangeComplete={onChangeCompleteMock} />, fireEvent.change on the
element grabbed by screen.getByLabelText('Alpha'), then assert
onChangeCompleteMock was called (and optionally called with the expected
alpha/hsba payload) in addition to the existing
document.querySelector('.pick-color').innerHTML check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8732dbee-7a87-4d9f-9eb1-9ef62c29c1b9

📥 Commits

Reviewing files that changed from the base of the PR and between 77e1e7e and 0a1b080.

⛔ Files ignored due to path filters (1)
  • tests/__snapshots__/index.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (8)
  • assets/index.less
  • src/ColorPicker.tsx
  • src/components/ColorBlock.tsx
  • src/components/Handler.tsx
  • src/components/Picker.tsx
  • src/components/Slider.tsx
  • src/interface.ts
  • tests/index.test.tsx

@Pareder

Pareder commented Jun 8, 2026

Copy link
Copy Markdown
Author

@afc163 @zombieJ @meet-student @QDyanbing Could you please review?

2 similar comments
@Pareder

Pareder commented Jun 12, 2026

Copy link
Copy Markdown
Author

@afc163 @zombieJ @meet-student @QDyanbing Could you please review?

@Pareder

Pareder commented Jun 18, 2026

Copy link
Copy Markdown
Author

@afc163 @zombieJ @meet-student @QDyanbing Could you please review?

@Pareder

Pareder commented Jun 26, 2026

Copy link
Copy Markdown
Author

@afc163 Could you please take a look?

@codecov

codecov Bot commented Jun 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.66082% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 99.62%. Comparing base (b8eaf1b) to head (6f36c78).

Files with missing lines Patch % Lines
src/components/Handler.tsx 95.65% 4 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##            master     #286      +/-   ##
===========================================
- Coverage   100.00%   99.62%   -0.38%     
===========================================
  Files           15       15              
  Lines          902     1056     +154     
  Branches        89      108      +19     
===========================================
+ Hits           902     1052     +150     
- Misses           0        4       +4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@afc163

afc163 commented Jun 26, 2026

Copy link
Copy Markdown
Member
图片

@Pareder

Pareder commented Jun 26, 2026

Copy link
Copy Markdown
Author

@afc163 Done.

@afc163

afc163 commented Jun 26, 2026

Copy link
Copy Markdown
Member

Thanks for the PR. The overall direction makes sense, but there are a few correctness issues that should be fixed before this is mergeable:

  • Picker treats locale as optional but directly reads locale.picker, locale.saturation, locale.brightness, etc. If locale is omitted, this will throw at runtime. Please add a local/default fallback and use it consistently for the aria labels/value text.
  • The 2D keyboard updates derive the next color from color.toHsb() captured from props. In controlled mode, rapid keyboard input can fire before the parent re-renders with the new color, so one axis can overwrite/revert the other. Please derive from a mutable latest color ref and keep it synced with the prop.
  • The vertical axis in Handler has the same stale-value problem: ArrowUp/ArrowDown and onKeyUp use y.value from props. Please track the latest vertical value in a ref so repeated key presses and onChangeComplete report the actual latest value.

After those are fixed, please add/adjust tests for the optional-locale case and rapid keyboard updates in controlled mode. Codecov is currently failing as well, so the added tests should help there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ColorPicker: Improve accessibility

2 participants