Repository navigation
@wordpress/ui: Icons from @wordpress/icons below 15.0.0 render black inside Button and IconButton #83471
Description
Description
#79320 moved icon colouring into the icons themselves. Every @wordpress/icons icon from 15.0.0 on declares currentColor, and ui's Icon stopped adding fill="currentColor". The ui changelog lists that as a breaking change in 0.16.0 and tells consumers to tint icons with CSS color. Button, IconButton and other components rely on this: they set color and expect the SVG to follow it.
Problem
@wordpress/ui lists @wordpress/icons as a regular dependency, which only controls the copy ui uses internally. Consumers pass icons from their own @wordpress/icons, and nothing checks that version. If a project resolves @wordpress/icons below 15.0.0, or passes a custom SVG with no fill, the icon renders black whatever colour the button sets. There's no warning about it.
It's most visible on a pressed neutral minimal toggle:
<IconButton icon={ seen } label="Toggle" variant="minimal" tone="neutral" aria-pressed />
The button's color switches to the pressed foreground (#f0f0f0), but the icon's fill stays the browser default rgb(0, 0, 0). It disappears against the #2d2d2d pressed background.
Suggested solutions
- Declare @wordpress/icons as a peer dependency (for example >=15.0.0), so package managers warn when a project's copy predates the self-declared colour. @wordpress/build already declares @wordpress/* peers, so this has precedent. One trade-off: ui imports @wordpress/icons internally, so as a peer it would use the consumer's copy. npm 7+ installs missing peers automatically, but Yarn v1 and some pnpm setups don't.
- Document the requirement where consumers will look. The only record of it today is the 0.16.0 changelog entry. The icon prop's JSDoc (packages/ui/src/icon/types.ts) says to pass an SVG, ideally from @wordpress/icons, but not that the SVG must declare currentColor to follow the text colour, or that @wordpress/icons 15.0.0 or later is needed. Adding that to the prop docs (and to Button/IconButton, which accept icon too) would also cover custom SVGs, which a peer dependency can't.
The two can be combined.
Related
- Icons: self declare color #79320: icons self-declare currentColor, and ui's Icon stops setting fill.
- Icons: Redraw 35 prominent icons with consistent stroke widths #78808: stroke icons get an inline fill: none; its review led to Icons: self declare color #79320.
- Icons: Fix color overrides for stroke-based icons #82477 (closed): fixed styles that colour icons with fill so they reach stroke icons. It kept fill as a fallback wherever a consumer may pass a custom icon without currentColor, which is the same gap described here.
- Icon: Reconsider color API for standard SVG icons #40102 (closed): the earlier discussion about giving Icon a colour API with currentColor as the default.
Step-by-step reproduction instructions
- Create a React project:
npm create vite@latest ui-icon-repro -- --template react-ts, thencd ui-icon-repro && npm install. - Install React 18, @wordpress/ui, and an @wordpress/icons release older than 15.0.0 (14.x requires React 18):
npm install react@18 react-dom@18 @types/react@18 @types/react-dom@18 @wordpress/ui @wordpress/icons@14.0.1 - In
src/main.tsx, remove the import './index.css' line so the template's styles don't affect the buttons. - Replace
src/App.tsxwith:
import { IconButton } from '@wordpress/ui';
import { seen } from '@wordpress/icons';
export default function App() {
return (
<>
<IconButton icon={ seen } label="Unpressed" variant="minimal" tone="neutral" />
<IconButton icon={ seen } label="Pressed" variant="minimal" tone="neutral" aria-pressed />
</>
);
}
- Run npm run dev and open the page.
- Look at the second (pressed) button. Its background is dark (rgb(45, 45, 45)) and the icon is black on it, so it's barely visible.
- In DevTools, inspect the pressed button: its computed color is rgb(240, 240, 240). Then inspect the inside it: its computed fill is rgb(0, 0, 0).
- To confirm the cause,
run npm install @wordpress/icons@latest, then restart the dev server withnpm run dev -- --forceso Vite doesn't reuse the old icons. Reload the page. The pressed icon is now light: 17.0.0 draws seen as an outline, so check its stroke, which is rgb(240, 240, 240).
Expected: the icon follows the button's text colour in every state.
Actual: the icon stays black whatever colour the button sets.
Screenshots, screen recording, code snippet
Environment info
No response
Please confirm that you have searched existing issues in the repo.
- Yes
Please confirm that you have tested with all plugins deactivated except Gutenberg.
- Yes
Please confirm which theme type you used for testing.
- Block
- Classic
- Hybrid (e.g. classic with theme.json)
- Not sure
Activity
simison commentedon Sep 24, 2026
FYI @WordPress/gutenberg-components @jasmussen
Since both UI and icons are "bundled" dependencies (instead of being externalised to the WordPress version), things should, in theory, be fine, but it looks like it's still possible to leak old versions into components.
Feels like best option for consumers is to just update in lock-step to latest to avoid issues like these.
jasmussen commentedon Sep 24, 2026
Thanks for the issue, thanks for the ping. I wonder: how can I best help here?
ciampo commentedon Sep 24, 2026
Hey there! I confirm this can be reproduced.
Re. the solutions proposed>
This can help detect an incompatible app-level
@wordpress/iconsversion, but AFAIK it cannot cover custom SVGs or icons supplied by another package’s older copy. As you also mentoined, it will make@wordpress/uipackage use the consumer’s version internally, which needs careful consideration since@wordpress/uiis a bundled package.That's worth doing anyway, we could probably add guidance for the
Icon,Button.Icon, andIconButtoncomponents.@WordPress/gutenberg-components what do you think about the peer dependency proposal? It could be the least invasive option.
The other option that comes to mind is to restore
fill="currentcolor"in theIconcomponent as a fallback (which we removed in #79320), ie.Although I'm not sure we want to do that.
t-hamano commentedon Sep 25, 2026
As far as I understand, this issue is not triggered automatically by a WordPress update, but only occurs when consumers explicitly bump the version of
@wordpress/ui. Therefore, for now, I think it's sufficient to simply add an appropriate explanation to the ui package's documentation files or JSDoc.jasmussen commentedon Sep 25, 2026
Wouldn't this cause bounded icons, like "scheduled" or "draft", to just become black circles?