Summary
IconButton sets aria-label to the raw icon token name (e.g. sparkle, trash, cross) and there is no way to override it. Because aria-label={iconName} is placed after the {...props} spread, a consumer-supplied aria-label is silently clobbered. This means every IconButton exposes a non-human-meaningful accessible name to screen-reader users, with no escape hatch.
Current behavior
src/components/IconButton/IconButton.tsx (on main):
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
({ type = 'primary', icon, size, disabled, className, ...props }, ref) => {
const iconName = icon ? icon.toString() : 'unknown icon';
return (
<button
{...props}
className={cn(iconButtonVariants({ type, size }), className)}
disabled={disabled}
ref={ref}
role="button"
aria-label={iconName} // <-- after {...props}, so it overrides any consumer aria-label
>
<Icon name={icon} size="sm" />
</button>
);
}
);
Two distinct problems:
- Poor default accessible name. With no label provided, the accessible name falls back to the icon token (
"sparkle", "trash", …), which is meaningless to assistive-tech users describing the action. Unlike Button, IconButton has no label/ariaLabel prop.
- The default can't be overridden.
IconButtonProps extends HTMLAttributes<HTMLButtonElement>, so the type advertises aria-label as a valid prop, but the implementation ignores it at runtime (it's spread first, then overwritten). The only override that currently survives is aria-labelledby (spread via ...props, and per the accessible-name spec it outranks aria-label) — which requires a referenced DOM node and is non-obvious.
This is a type/runtime contract mismatch, not just a styling default — which is why I'd classify it as a bug rather than an enhancement.
Expected behavior
A consumer-provided accessible name should win, and there should be an explicit, discoverable way to set one.
Suggested fix (small, backward-compatible)
Respect a consumer-provided aria-label, falling back to the icon name only when none is given:
<button
{...props}
className={cn(iconButtonVariants({ type, size }), className)}
disabled={disabled}
ref={ref}
role="button"
aria-label={props['aria-label'] ?? iconName}
>
and/or add an explicit label prop mirroring Button:
export interface IconButtonProps extends HTMLAttributes<HTMLButtonElement> {
// ...
/** Accessible name for the button. Falls back to the icon name if omitted. */
label?: string;
}
Impact
Affects every IconButton instance. In the control-plane apps/web app alone there are ~124 instances, each currently announcing an icon token as its accessible name.
Environment
- Observed on
@clickhouse/click-ui v0.2.1-rc.8 (styled-components implementation) and confirmed still present on main (CSS-modules implementation).
Summary
IconButtonsetsaria-labelto the raw icon token name (e.g.sparkle,trash,cross) and there is no way to override it. Becausearia-label={iconName}is placed after the{...props}spread, a consumer-suppliedaria-labelis silently clobbered. This means everyIconButtonexposes a non-human-meaningful accessible name to screen-reader users, with no escape hatch.Current behavior
src/components/IconButton/IconButton.tsx(onmain):Two distinct problems:
"sparkle","trash", …), which is meaningless to assistive-tech users describing the action. UnlikeButton,IconButtonhas nolabel/ariaLabelprop.IconButtonProps extends HTMLAttributes<HTMLButtonElement>, so the type advertisesaria-labelas a valid prop, but the implementation ignores it at runtime (it's spread first, then overwritten). The only override that currently survives isaria-labelledby(spread via...props, and per the accessible-name spec it outranksaria-label) — which requires a referenced DOM node and is non-obvious.This is a type/runtime contract mismatch, not just a styling default — which is why I'd classify it as a bug rather than an enhancement.
Expected behavior
A consumer-provided accessible name should win, and there should be an explicit, discoverable way to set one.
Suggested fix (small, backward-compatible)
Respect a consumer-provided
aria-label, falling back to the icon name only when none is given:and/or add an explicit
labelprop mirroringButton:Impact
Affects every
IconButtoninstance. In the control-planeapps/webapp alone there are ~124 instances, each currently announcing an icon token as its accessible name.Environment
@clickhouse/click-uiv0.2.1-rc.8(styled-components implementation) and confirmed still present onmain(CSS-modules implementation).