ColorPalette: Fix duplicate-key warnings and incorrect selection with identical color values#78004
Conversation
|
Warning: Type of PR label mismatch To merge this PR, it requires exactly 1 label indicating the type of PR. Other labels are optional and not being checked here.
Read more about Type labels in Gutenberg. Don't worry if you don't have the required permissions to add labels; the PR reviewer should be able to help with the task. |
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Unlinked AccountsThe following contributors have not linked their GitHub and WordPress.org accounts: @DesignerThan95, @metasaman, @timelsass. Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases. If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
ciampo
left a comment
There was a problem hiding this comment.
Thank you for looking into this!
My preference would be to keep this PR focused on the @wordpress/components code changes, and then follow-up with changes to the @wordpress/block-editor package in a separate PR
There was a problem hiding this comment.
Pull request overview
This PR addresses a long-standing issue where duplicate color values in a theme palette cause React duplicate-key warnings and incorrect selection/highlighting behavior in Gutenberg’s color UI. It does so by threading palette slug through ColorPalette selection and through the block editor’s color panel so the chosen palette entry can be uniquely identified even when multiple entries share the same color value.
Changes:
- Extend
ColorPaletteto supportslugper color, prefer slug-based selection via a newselectedSlugprop, and passslugas a third argument toonChange. - Update block editor global styles color panel + color/gradient control plumbing to extract and round-trip the selected preset slug (storing
var:preset|color|{slug}directly). - Add/adjust component tests for duplicate palette colors and update changelog.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/components/src/color-palette/types.ts | Adds optional slug to color objects; extends onChange signature and introduces selectedSlug prop typing. |
| packages/components/src/color-palette/index.tsx | Uses slug for React keys when available, supports slug-based selection, and passes slug through onChange. |
| packages/components/src/color-palette/index.native.js | Adjusts React Native palette item keys to avoid collisions when duplicate colors exist. |
| packages/components/src/color-palette/test/index.tsx | Updates existing expectations for the new onChange signature; adds tests for duplicate-color palettes and slug selection. |
| packages/components/CHANGELOG.md | Adds an Unreleased changelog entry for the ColorPalette duplicate-color fix. |
| packages/block-editor/src/components/global-styles/color-panel.js | Extracts preset slugs from stored values and encodes selected colors using the slug to avoid first-match-by-color issues. |
| packages/block-editor/src/components/colors-gradients/control.js | Threads colorSlug down to ColorPalette as selectedSlug and passes slug back up via onColorChange. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
I see, Then I will split this PR into 2, this one will be related to components and I will follow up with block editor changes. 👍🏻 |
There was a problem hiding this comment.
I feel like we could also add a Storybook example for selectedSlug and duplicate colors.
Something like this (which is also currently buggy, related to the first comment below)
diff --git i/packages/components/src/color-palette/stories/index.story.tsx w/packages/components/src/color-palette/stories/index.story.tsx
index 30671aba62f1..8630faf0da63 100644
--- i/packages/components/src/color-palette/stories/index.story.tsx
+++ w/packages/components/src/color-palette/stories/index.story.tsx
@@ -39,17 +39,21 @@ type ColorPaletteStory = StoryObj< typeof ColorPalette >;
const Template = ( {
onChange,
value,
+ selectedSlug,
...args
}: React.ComponentProps< typeof ColorPalette > ) => {
const [ color, setColor ] = useState< string | undefined >( value );
+ const [ slug, setSlug ] = useState< string | undefined >( selectedSlug );
return (
<ColorPalette
{ ...args }
value={ color }
- onChange={ ( newColor ) => {
+ selectedSlug={ slug }
+ onChange={ ( newColor, index, newSlug ) => {
setColor( newColor );
- onChange?.( newColor );
+ setSlug( newSlug );
+ onChange?.( newColor, index, newSlug );
} }
/>
);
@@ -102,6 +106,19 @@ export const MultipleOrigins: ColorPaletteStory = {
},
};
+export const DuplicateColors: ColorPaletteStory = {
+ render: Template,
+ args: {
+ colors: [
+ { name: 'Dark Background', slug: 'dark-background', color: '#000' },
+ { name: 'Dark Text', slug: 'dark-text', color: '#000' },
+ { name: 'Brand', slug: 'brand', color: '#0073aa' },
+ ],
+ value: '#000',
+ selectedSlug: 'dark-text',
+ },
+};
+
export const CSSVariables: ColorPaletteStory = {
render: ( args ) => (
<div
ciampo
left a comment
There was a problem hiding this comment.
LGTM 🚀
Thank you for working on this.
We can merge once remaining (minor) feedback is addressed


What?
Closes #9357
Fix React duplicate-key warning and incorrect swatch selection when two palette entries share the same color value.
Why?
more information in the issue but in nutshell:
Themes can legitimately have two palette slots with the same color (e.g. "Header Background" and "Body Background" both white, so users can later change them independently). Gutenberg used the CSS color string as both the React key and the selection identifier, causing:
Encountered two children with the same key, rgb(29, 16, 22)How?
ColorPalettenow usesslugas the React key when available, and accepts aselectedSlugprop for accurate swatch highlightingonChangepasses(color, index, slug)so callers can round-trip the slugvar:preset|color|{slug}value down toColorPalette, and passes it back throughencodeColorValueon change, bypassing the first-match color searchTesting Instructions
Use of AI Tools
Developed with GitHub Copilot (Claude Sonnet 4.6). All code reviewed and tested manually.