Icon buttons: hover text + lint rule
Status summary#
Done, pending review. Button now derives title (hover text) from aria-label
on icon sizes; a custom oxlint rule (iterate/icon-button-has-hover-text)
enforces labels on icon-size Buttons; the three violations it found are fixed.
The off-the-shelf rule turned out to be structurally unable to catch this —
details below.
Ask#
Icon-only buttons (e.g. the Activity/Unplug buttons on integration connection
rows) have aria-labels but no hover text — hovering tells you nothing about
what the button does. Add hover text, and add a lint rule so unlabeled icon
buttons can't sneak in again. Prefer a popular off-the-shelf lint rule.
Decisions (assumptions made while Misha is AFK)#
- Hover text mechanism: native
titleattribute derived automatically fromaria-labelin the design-systemButtonwhensizeis an icon size and no explicittitleis passed. One change gives every labeled icon button hover text; no per-callsite churn, no DOM-structure change (a styled Tooltip wrapper would risk breakingrender-prop composition and adds provider ceremony). Explicittitlestill wins. - Lint rule — off-the-shelf didn't survive contact: the popular option is
eslint-plugin-jsx-a11y'scontrol-has-associated-label, but itsmayHaveAccessibleLabelhelper assumes any uppercase-component child might render a text label and bails (isReactComponent → return true). Since icon buttons' only child is a lucide icon component, the rule can never flag<Button size="icon"><Trash /></Button>. oxlint's native jsx-a11y plugin doesn't implement the rule at all. So: a small custom rule (iterate/icon-button-has-hover-text) in the existing oxlint JS plugin — flags icon-size<Button>s with noaria-label/aria-labelledby/title; dynamic values and spreads are assumed to provide one.
Checklist#
-
Button: derivetitlefromaria-labelfor icon sizes —packages/ui/src/components/button.tsx -
load— the rule structurally can't flag icon-component children (see decision above); custom rule insteadeslint-plugin-jsx-a11yinto oxlint config, enablecontrol-has-associated-label - lint rule enforcing labeled icon buttons —
iterate/icon-button-has-hover-textinlint/oxlint-plugin-iterate.ts, tests inlint/oxlint-plugin-icon-button.test.ts - fix all violations the rule finds — 3 found, all in
packages/ui: dialog + sheet close buttons (movedsr-onlyspan label toaria-labelso they get hover text too), combobox chip-remove (was fully unlabeled — genuine catch) - confirm
pnpm lintred on an unlabeled icon button, green after fix — rule tests spawn the real oxlint binary against fixture files; repo lint green
Implementation log#
- The integrations-page buttons from the screenshot already had
aria-labels, so they get hover text purely from the Button change — no callsite edits. eslint-plugin-jsx-a11ywas installed and then removed after reading its rule source ruled it out (see decision above).- Rule message points people at
aria-labeland notes Button renders it astitle, so the fix is self-explanatory at the lint error.