Align icon-only buttons on the shared IconButton component #300
Labels
No Label
area:auth
area:docs
area:export
area:ops
area:storage
area:supply-chain
auth
backend
blocked
collab
deployment
docs
effort:L
effort:M
effort:S
frontend
plugins
qa
vs-nfd
vs-nfd:blocker
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: stwaidele/dorfteich#300
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The notification bell in the top bar sits visibly higher than the search
and theme buttons next to it, and renders larger. The cause is not the
glyph: the bell is the only one of the three that uses neither the shared
IconButtoncomponent nor its class.Root cause
IconButton(apps/web/src/components/IconButton.tsx) renders a buttoncarrying the
icon-buttonclass, whose rule atapps/web/src/styles/base.css:557forces a 2rem box withdisplay: inline-flex; align-items: center; justify-content: center, andnormalises the icon to
1.15rem(base.css:571)..notifications-bell__button(base.css:3814) has neither rule:<svg>is laid out as an inline element on thetext baseline of a
1.1remline box. The space reserved below it fordescenders pushes it optically upwards — this is the misalignment.
of the 18.4px the others use. It is bigger as well as higher.
It is also the only one of the three without a
:focus-visiblerule, andthere is no global one to fall back on (
base.cssdefines:focus-visibleonly per component:.icon-button,.toolbar-button,.skip-link,.sidebar-resizer,.field input,.editor-page__title).The bell therefore shows the browser default outline where every other
icon button shows the 2px accent ring.
Two kinds of outlier
size, inconsistent focus ring.
<button>— e.g.apps/web/src/layout/Sidebar.tsx:400,408,420,apps/web/src/labels/LabelPicker.tsx:166,apps/web/src/files/PondFileManager.tsx:74,apps/web/src/layout/TopBar.tsx:113. These inherit the visuals butbypass the component's contract, which is what guarantees an
aria-labeland atitleon an icon-only control.Scope
Every icon-only button in the web app, not just the top bar.
Out of scope: the editor toolbar's
.toolbar-button(
apps/web/src/editor/LinkMenu.tsxand friends) is a deliberatelyseparate visual system and stays as it is. Icons sitting inside buttons
that already carry a visible text label are not icon-only controls and
stay as they are too.
Files using lucide icons without
IconButtontoday, to be audited one byone — conversion only where the control is genuinely icon-only:
apps/web/src/access/AccessRulesManager.tsxapps/web/src/files/PondFileManager.tsxapps/web/src/import/ImportControl.tsxapps/web/src/labels/LabelManager.tsxapps/web/src/labels/LabelPicker.tsxapps/web/src/layout/Sidebar.tsxapps/web/src/members/MemberManager.tsxapps/web/src/notifications/NotificationsBell.tsxapps/web/src/pages/PageEditorPage.tsxapps/web/src/pages/PluginManager.tsxapps/web/src/watches/WatchesSection.tsxApproach
Route every icon-only button through
IconButtonand drop thehand-written duplicates of its CSS. For the bell this means
.notifications-bell__buttondisappears; the unread badge is positionedabsolutely against that button today, so it has to be re-fitted to the
2rem box (the wrapper
div.notifications-bellkeepsposition: relativefor the dropdown).
IconButtoncurrently acceptslabel,activeand the native buttonprops. The bell additionally needs
aria-haspopupandaria-expanded,which pass through
...restunchanged — no component change expected,but verify rather than assume.
Guard against future drift
Add an ESLint rule that rejects
classNamecontainingicon-buttonon araw JSX
<button>element and points at the component instead. Thiscatches the next hand-glued outlier at authoring time. The rule must not
fire on
IconButton's own implementation.Accessibility
Covered by construction once the controls go through
IconButton:identical focus ring, and
aria-labelplustitleenforced by thecomponent's required
labelprop. To be confirmed during the work:bare icon without a name.
aria-haspopup="menu"and a correctaria-expanded.count belongs in the bell's accessible name or an appropriate live
region rather than as loose text.
Acceptance criteria
vertical alignment in the running app, at the default zoom and at
200%.
<button className="icon-button …">remains outsideIconButtonitself; the ESLint rule fails the build if one isadded.
.notifications-bell__buttonis gone frombase.css.99+ unread items.
pnpm lint,pnpm typecheckand the a11y spec pass.