fix(desktop): one icon-button and one labelled-button style across the window

Merged
DA
10d ago
+241-349

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-button is 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 --selected and the glyph turns --text-primary. Held down it fills with the new --pressed token. 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-button is the labelled ghost button from #1840: same height, radius, glyph size, states and ring, with a --glyph-gap gap 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/panel glyph 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.css now sets button { padding: 0 }, so Chromium's default 1px 6px can 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

ButtonBeforeAfter
Sidebar toggle, title-bar New plan, Back / Forward (#1921)toolbar-button toolbar-icon-button.icon-button
Side panel "+", panel toggle, expand / exit full screentoolbar-button toolbar-icon-button.icon-button
Plan ⋯ menutoolbar-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 / changesettings .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 newestTailwind 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)

ButtonWhy
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 linesThese 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.
SwitchesKept 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:

ButtonBeforeAfter
Title-bar / panel icon buttons30×30, r8, glyph 15px, stroke 1.19pxunchanged
Settings link (System Settings)30×30 (26 zoomed), r9, filled, glyph 10.3px, stroke 0.82px30×30, r8, transparent, glyph 15px, stroke 1.19px
Settings shortcut reset / remove / change30×30, r9, filled30×30, r8, transparent
Settings search clear18×18 circle, filled, glyph 9px, stroke 0.71px24×24, r6, transparent, glyph 15px, stroke 1.19px
Check for updates / Open ↗30–31px tall, r10, filled, 11.5px text, Open glyph 10.3px30px, r8, ghost, 12.5px text, glyph 15px
Dialog Cancel / Delete32px, r830px, r8
Lucide glyphs in Work / Transcriptstroke 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:
states before
After:
states 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.sh exits 0, including the design contract. Every new padding and gap uses a token.
  • CI could not verify the Mac UI, and ./scripts/verify.sh was 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


Open workspace in Conductor