fix(desktop): one icon-button and one labelled-button style across the window
Dean: "I want the link button (and all buttons/icons) to have the same styling as the new icons in our app like the expand/collapse and side panel buttons."
What changed
The window now has two button styles, both defined in desktop.css and described in docs/DESIGN.md:
.icon-buttonis the title bar's square. It is--control-height(30px) square with--control-radius(8px) corners and a--control-glyph(15px) glyph in--text-secondary. At rest it is transparent. On hover it fills with--selectedand the glyph turns--text-primary. Held down it fills with the new--pressedtoken. Disabled is 0.45 opacity. For the keyboard it gets a 2px--focus-ring(the new token, the system blue--message-sent). A button whose menu is open (⋯, "+") keeps its hover fill while the menu is up..toolbar-buttonis the labelled ghost button from #1840: same height, radius, glyph size, states and ring, with a--glyph-gapgap and 12.5px/600 text..primary-button(accent fill) and.danger-button(red fill) use the same geometry and only change the fill.
Other changes:
- Settings zoom. Settings pages draw at
zoom: 1.15, which is why the old link button was the wrong size. Buttons and the voice select on those pages now undo that zoom (--settings-zoom), so they render at the window's own size. Measured: every icon button in Settings is 30×30 with a 15px glyph. - Hover over moving content. The opaque-hover idea from #1914 is now general. A button sets
--button-ground, and its hover fills over that colour. The title bar uses this. So does the transcript's scroll-to-newest button, which floats over the lines. - Voice select. The blue badge behind the up/down chevrons is gone. The chevrons are a muted 15px glyph that brightens on hover. The select is now control height and radius, uses 12.5px/600 text, and gets the shared focus ring.
- Glyphs. Every
@sidecar/panelglyph is drawn in a 24 box at stroke 1.9. Measured in headless Chrome, they all render at a 1.19px stroke inside buttons. Lucide icons (used by the AI Elements in the Work and Transcript tabs) were drawn at 2 and now use 1.9 (svg.lucide). Outliers that were fixed: the old link glyph rendered 10px with a 0.82px stroke, and the search clear × rendered 9px with a 0.71px stroke. Both are now 15px at 1.19px. - No user-agent padding on buttons.
base.cssnow setsbutton { padding: 0 }, so Chromium's default1px 6pxcan never squeeze a glyph again. That padding was #1912's root cause. - Choose folder now has the folder glyph, like the other labelled toolbar buttons.
Inventory
| Button | Before | After |
|---|---|---|
| Sidebar toggle, title-bar New plan, Back / Forward (#1921) | toolbar-button toolbar-icon-button | .icon-button |
| Side panel "+", panel toggle, expand / exit full screen | toolbar-button toolbar-icon-button | .icon-button |
| Plan ⋯ menu | toolbar-button toolbar-icon-button | .icon-button |
| Settings ▸ Voice: open System Settings (link) | settings .icon-button (filled, r9, glyph 10px / 0.82px stroke) | .icon-button |
| Settings ▸ Keyboard shortcuts: reset / remove / change | settings .icon-button (filled, r9) | .icon-button |
| Settings search clear × | 18px filled circle, 9px glyph | .icon-button, nested in the field at 24px (see exceptions) |
| Transcript / Work: scroll to newest | Tailwind 32px circle, 16px Lucide arrow | .icon-button .conversation-scroll on its own ground, with a hairline and shadow |
| Copy plan, Reset to defaults, Close plan | .toolbar-button | .toolbar-button (unchanged) |
| Choose folder | .toolbar-button, no glyph | .toolbar-button with the folder glyph |
| Check for updates, Changelog Open ↗, Allow, Sign out…, Delete account…, Send feedback, Submit a prompt, ConfirmSwap Cancel | .quiet-button (raised fill, 10px text, r10) | .toolbar-button |
| Transcript "Try again" | .plan-button | .toolbar-button |
| Delete-plan dialog Cancel | .confirm-dialog-button (raised) | .toolbar-button |
| Delete-plan dialog Delete, ConfirmSwap Delete | .confirm-dialog-button[data-danger] / tinted .danger-button | .danger-button (solid red) |
| Download, Restart to update | .action-button (cyan tint) | .primary-button |
| Document "Try again" | .primary-button at 36px, r9 | .primary-button at control height |
| Feedback sheet: Add a screenshot, Cancel, Send | .feedback-attach / .quiet-button / .action-button | .toolbar-button / .primary-button |
| Menu icon column (⋯, right-click, "+") | 16px | --control-glyph (15px) |
Removed CSS: .toolbar-icon-button, the settings .icon-button and .icon-button-glyph at 13px, .quiet-button, .action-button, .plan-button, .confirm-dialog-button, .feedback-attach, .feedback-offer, and the per-button hover rule on the title bar.
Exceptions (left as they are on purpose)
| Button | Why |
|---|---|
| Call bar microphone and stop (42px round, filled) | The call's primary controls. They are round and filled so they read as a call, not as toolbar actions. |
| New-plan composer's round start (↑) | A composer's send, drawn the way every chat composer draws it. |
| Tab × | #1912: it brightens without a fill, because it sits on a pill that is already raised. |
| Settings search clear × | Same glyph, colours, hover and ring as .icon-button, but it sits inside the 30px field, so it is 24px with a nested 6px corner. A 30px button can't fit inside a 30px field. |
| Sidebar rows, the account row, settings page rows, tabs, menu items, the composer's folder chip and recent-folder rows, AI Elements disclosure lines | These are rows and chips, not buttons: they keep the row hover and selection of the list they belong to. |
Sidebar "Try again" (.link-button) | An inline text link inside a sentence. |
| Switches | Kept as they are. They don't clash with the buttons. |
| Sign-in's provider buttons and its Cancel link (#1926) | #1926's own new design, merged just before this PR: full-width sign-in calls to action at control height, and a text link. |
The volume hint's "Got it", the feedback screenshot's remove × and RemoveIcon (stroke 3.4) | Owned by the in-flight notch cleanup and the Feedback composer redo. I didn't restyle them so I wouldn't collide with those PRs. The feedback sheet's plain labelled buttons were moved onto the shared classes (a class-name change only). |
There are no dialog close (×) buttons in the app today. The confirm dialog has none.
Measured (headless Chrome, real App on the fixture, built stylesheet, 1440×900 @2x)
Every visible <button> on 21 screens was measured with getBoundingClientRect and its glyph's drawn extent with getBBox:
| Button | Before | After |
|---|---|---|
| Title-bar / panel icon buttons | 30×30, r8, glyph 15px, stroke 1.19px | unchanged |
| Settings link (System Settings) | 30×30 (26 zoomed), r9, filled, glyph 10.3px, stroke 0.82px | 30×30, r8, transparent, glyph 15px, stroke 1.19px |
| Settings shortcut reset / remove / change | 30×30, r9, filled | 30×30, r8, transparent |
| Settings search clear | 18×18 circle, filled, glyph 9px, stroke 0.71px | 24×24, r6, transparent, glyph 15px, stroke 1.19px |
| Check for updates / Open ↗ | 30–31px tall, r10, filled, 11.5px text, Open glyph 10.3px | 30px, r8, ghost, 12.5px text, glyph 15px |
| Dialog Cancel / Delete | 32px, r8 | 30px, r8 |
| Lucide glyphs in Work / Transcript | stroke 2 (1.0px at 12px) | stroke 1.9 (0.95px) |
Button states (forced with CDP CSS.forcePseudoState; rows 2 and 4 sit inside a zoomed Settings page)
Before:
![]()
After:
![]()
Settings
General:
![]()
Voice (some settings changed, so Reset to defaults shows):
![]()
Voice, microphone not granted (Dean's screenshot):
![]()
Appearance:
![]()
Keyboard shortcuts:
![]()
Search with a query (clear ×):
![]()
Plan page with the panel open, folded sidebar, new-plan page
These buttons were already this style, so these screenshots are a regression check:
![]()
![]()
![]()
![]()
Menus and dialog
The "+" and ⋯ openers now stay filled while their menu is open:
![]()
![]()
![]()
![]()
Verification
./scripts/check.shexits 0, including the design contract. Every new padding and gap uses a token.- CI could not verify the Mac UI, and
./scripts/verify.shwas not run. This was built on a Linux VM, so the evidence is the headless Chrome renders and measurements above (the fonts fall back from-apple-system). LUKE-159 is the manual Mac pass. - No test was added. The change is CSS, class names and glyph stroke only, and a test asserting any of them would be a change-detector (AGENTS.md). The evidence is the measurements above.
🤖 Generated with Claude Code