fix(cards): stop the glass action bar dimming the action you hover - #6403
Conversation
The accessibility fix in #6398 pinned `--button-hover-color` to the same value as `--button-pressed-color` so a held pointer couldn't dip below 3:1. Every icon cleared the threshold, but the accent is always darker than `text-primary`, so the action under the pointer became the dimmest thing in the bar — and the `InteractionCounter` beside it is text, owing SC 1.4.3's 4.5:1 rather than 3:1. In light mode all three counters landed at 3.01-3.08. Hover and pressed were also byte-identical, so hovering an already-upvoted action did nothing at all. Hover is now toned per theme through `--glass-actions-hover-tone`: dark content keeps its accent (5.34-11.16 — bright enough on a near-black bar that it still reads as a lift) light content goes to `text-primary` (15.03), and the accent moves into the surface instead both the accent sits at 22% behind the button rather than the design system's 12%, and active deepens to 32% (the ladder's own active background is 20%, which would have stepped backwards from a 22% hover). Icon and counter always share one colour. The pressed state is untouched. One guard was needed: `buttons.css` resolves hover AFTER `aria-pressed` at equal specificity, so light's neutral hover would otherwise blank the "this is on" accent for as long as the pointer sat on an upvoted action. The counter needs its own rule because it is a sibling of the button and reads the variable off the `.btn-quaternary` wrapper. Copy link regains a `.btn-tertiary-cabbage` block — dropped in the #6398 review as a no-op — purely for the surface and the light hover tone; it still sets no pressed colour. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Review of the hover change caught a regression it introduced, plus a documentation error behind it. Raising the hover surface from the design system's 12% to 22% changes what the icon is measured against. At 12% the chip is invisible enough that the bar is the honest reference; at 22% the chip is the colour adjacent to the glyph, and in dark the icon and chip are the same hue — so every point of tint costs the icon contrast. Dark copy link went from 3.31 to 2.92 (2.55 at the 32% active step), the only cell the change moved from pass to fail. cabbage.40 -> .10 takes it to 3.92 / 3.24, and the worst remaining dark cell is downvote at 3.14. A pressed button now keeps the shallower 12/20% chip. Its glyph is back on the accent the chip is tinted with, so the deeper steps cost contrast for no gain — light pressed-and-hovered returns to 2.65-2.74, exactly where main has it, rather than the 2.37-2.47 the deeper chip caused. The residual miss is a palette limit, not a regression. Also in this pass: - The six mapping blocks were identical bar a variable name. Each button now resolves `--glass-actions-accent` and shared rules turn that into the state properties, which is ~100 lines less CSS. - `copy-post-btn` was a fixed id on a component a feed renders once per card, and QuaternaryButton wires the id to a `<label htmlFor>`. Now post-scoped. - Comments across all four files cut back to the constraints that aren't visible in the code. The measured ratios belong in the Storybook page that computes them live, not in a stylesheet where they go stale silently. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…github.com/dailydotdev/apps into claude/action-glassbar-hover-states-84126e
tsahimatsliah
left a comment
There was a problem hiding this comment.
Summary
Good, tightly-scoped follow-up. The core diagnosis is right — pinning --button-hover-color to the pressed value in #6398 made the hovered action the dimmest thing in the bar, and hover became indistinguishable from pressed. Routing everything through one --glass-actions-accent per button and deriving the four state properties from it removes ~100 lines of near-duplicate CSS, and the second pass catching that a 22% chip changes what the icon is measured against (copy link 3.31 → 2.92) is exactly the right instinct.
I verified the mechanics rather than the numbers:
--button-hover-color/--button-active-color/--button-hover-background/--button-active-backgroundall exist and are consumed bybuttons.css(.btn&:hover, &.hoverand&:active, &.active), and&[aria-pressed="true"]does resolve before the hover block at equal specificity — so the guard rules are genuinely needed.- The sibling-label guard is needed too:
.btn-quaternary & :hover ~ label(0,2,0) is declared after& [aria-pressed="true"] ~ label, and the new rule at 0,3,1 is what hands the pressed colour back. --glass-actions-hover-tone: initialis correct per spec — a custom property set toinitialis guaranteed-invalid, sovar(--x, <accent>)takes the fallback. It also survives nested.invert, since the value is assigned on the pill itself.- Import order is fine:
components.csspullsbuttons.cssbeforefeedCardGlassActions.css. - Bookmark does carry
post-${post.id}-bookmark-btn(viabuttonProps), so the story's[id$='-bookmark-btn']row is live. - CI is green.
No blocking findings. A few things worth resolving or filing before merge.
Non-blocking
1. The pressed counter in light mode is still text below 4.5:1 — and this PR is the one that established that threshold. The description frames the residual light pressed miss (2.65–2.74) as an icon/palette limit, but Upvote, Comment and Impressions carry an InteractionCounter, and the new pressed-label guard hands that label the pressed accent — which is mixed down to ~3.0 against the bar by design. So a light-mode upvoted count is text at roughly 2.7–3.0 against SC 1.4.3's 4.5. That is pre-existing rather than introduced here, but it is the same defect class the PR is fixing, and a paragraph in a PR description is not where it will get found again. Either give pressed counters the neutral treatment hover now uses, or file the "darken the light pressed accents" follow-up as a ticket and link it.
2. The contrast audit table hard-codes the accent hexes. The visual columns read var(--glass-actions-*) off the pill, which is what keeps them honest — but themes[].accents / afterAccents / fill in GlassActionsHoverStates.stories.tsx are literal hexes (#57e087, #039750, #d97efe, #0f1218, …). They are correct today (I checked cabbage.10 = #D97EFE and light accent-cabbage-default = cabbage.60 = #A641CC), but this is the exact staleness the PR cites as the reason for deleting the ratios from the stylesheet — just moved one file over, where nothing flags it either. getComputedStyle(pill).getPropertyValue('--glass-actions-upvote') would make the table as live as the columns above it.
3. The pressed guard wins on source order at equal specificity. html .feed-card-glass-actions .btn[aria-pressed='true'] and the mapping block are both 0,3,1, so reordering the file silently disables the "keep the pressed accent while hovered" behaviour with no build or test signal. Doubling the class as the mapping already does (.feed-card-glass-actions.feed-card-glass-actions) makes it 0,4,1 and order-independent, and the story's own beforeCss uses that trick for the same reason.
4. The id fix is incomplete. ActionButtons.tsx:281 and ActionButtons.v2.tsx:227 still use a fixed id="copy-post-btn", and both are feed-card components rendered once per card — the same duplicate-id situation this PR just fixed in FeedCardGlassActions, with the same <label htmlFor> wiring in QuaternaryButton. Two one-line changes, or a follow-up ticket.
5. The label guard is unconditional. … .btn-quaternary [aria-pressed='true'] ~ label { color: var(--button-pressed-color) } applies in every state at 0,3,1, so it also outranks & [disabled] ~ label (0,2,0) for a pressed-and-disabled counter. Probably unreachable for these six actions, but scoping it to :hover/:active — the states it exists to fix — would keep it from quietly owning the label's colour outright.
6. The 600-line "before" half of the story has a short shelf life. Restoring #6398's values via scoped CSS is a nice way to make the argument, but once this merges the before columns document a state that no longer exists anywhere, and nothing validates that they still reproduce it. Worth deciding now whether the page keeps them permanently or is trimmed to the live matrix plus the measured table after review.
Verification
- Read the PR review guide and code-style rules
- Traced the cascade end to end in
buttons.css(hover / active / pressed blocks,.btn-quaternarylabel rules) and confirmed the specificity and source-order claims in the description - Confirmed the
initialguaranteed-invalid fallback and its behaviour under nested.invert - Checked the story's forced-hover selectors against the component's real ids
- Spot-checked the hard-coded audit hexes against
tailwind/colors.ts - CI inspected — green
- Ratios not independently recomputed; taking the Storybook page's math as given
- Not verified in the extension or companion — the description flags both as unchecked, and while the selector groups are untouched, the companion wrapper is in those selector lists
Verdict
Comment — nothing blocking. Item 1 deserves a decision (fix or ticket) and items 3 and 4 are cheap enough to fold in.
Reviewed by AI.
Addresses the review on #6403. - The pressed guard tied with the mapping block at 0,3,1 and won only on source order, so reordering the file would have silently dropped the "keep the pressed accent while hovered" behaviour with no build or test signal. Doubling the class makes it 0,4,1. - The label guard applied in every state, which also outranked `.btn-quaternary [disabled] ~ label`. Scoped to the hover/active states it exists to correct. - `ActionButtons.tsx` and `ActionButtons.v2.tsx` still carried the fixed `id="copy-post-btn"` this PR had already fixed in FeedCardGlassActions. Both are feed-card components rendered once per card, and QuaternaryButton wires the id to a `<label htmlFor>`. - The story's contrast audit restated the accents as literal hexes — the same staleness this PR cites when taking the ratios out of the stylesheet, just moved one file over. It now reads every colour off two live pills, resolving `color-mix()` through a canvas pixel, and detects the per-theme hover tone through the same `var()` fallback the shipped mapping uses. The remaining review item — light-mode pressed counters sitting at 3.01-3.08 against a 4.5:1 threshold — is pre-existing and needs a design call, so it is filed as #6405 rather than folded in here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
All six items addressed in 6aa7266 — four fixed, one filed, one decided.
On (1). Filed rather than folded in because both exits are design calls, not mechanical fixes: darkening the light pressed accents changes a look signed off against the 90% bar, and giving pressed counters the neutral hover treatment breaks the icon-and-counter-share-one-colour pairing that was explicitly requested for pressed. #6405 records the measured numbers (3.01–3.08 against the bar, 2.65–2.74 against the chip) and both options. On (2) — worth calling out, because your framing was sharper than the fix I'd have written. The audit now renders two zero-size probe pills (one plain, one On (6) — keeping the before columns. Two reasons. It matches the merged "Glass Actions Theme Compare" page, which keeps its own before/after for the #6398 change, so trimming this one would leave the pair inconsistent. And the restoration is not frozen the way a screenshot would be: every before column reads On the two unchecked verification boxes. Extension and companion are still unverified by me — the selector groups are untouched, including the Re-verified after the changes: shared cards 30 suites / 217 tests, webapp 45 / 336, changed-file strict typecheck, and the rendered matrix in both themes with transitions disabled. One methodology note that cost me real time and is worth recording for anyone verifying this page by hand: |
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Changes
Follow-up to #6398. That PR fixed the rest and pressed states of the feed card floating action bar; this fixes the state in between.
#6398 pinned
--button-hover-colorto the same value as--button-pressed-colorso a held pointer couldn't dip below 3:1. Every icon cleared the threshold, so it was never a regression against the original bug — but the accent is always darker thantext-primary, so the action under the pointer became the dimmest thing in the bar. TheInteractionCounternext to upvote / comment / impressions is text, so it owes SC 1.4.3's 4.5:1, not 3:1 — in light mode all three hovered counters landed at 3.01–3.08. Hover and pressed were also byte-identical, so hovering an already-upvoted action gave no feedback at all.Hover is now toned per theme through a new
--glass-actions-hover-tone:text-primaryIcon and counter always share one colour. The pressed state is unchanged.
Implementation notes
--glass-actions-hover-tone: var(--theme-text-primary); the dark mixin sets it toinitial(guaranteed-invalid), and each button readsvar(--glass-actions-hover-tone, var(--glass-actions-accent)). One fallback instead of a second per-theme colour table.--glass-actions-accentand shared rules derive the four state properties from it — the six near-identical mapping blocks are gone, ~100 lines less CSS. Written flat rather than as a parameterised PostCSS mixin:@define-mixin x $arghas no precedent in this repo and three build pipelines would have to agree on it.cabbage.40 → .10(2.92 → 3.92) to survive its own 22% chip. Worst remaining cell is downvote at 3.14 on the 32% active step.buttons.cssresolves hover after[aria-pressed]at equal specificity, so light's neutral hover would otherwise blank the "this is on" accent while the pointer sat on an upvoted action. Both double the class so they outrank the mapping rather than tying with it and depending on block order, and the label needs its own rule because it is a sibling of the button, reading the variable off the.btn-quaternarywrapper.copy-post-btnwas a fixed id on three components a feed renders once per card, andQuaternaryButtonwires the id to a<label htmlFor>. Now post-scoped inFeedCardGlassActions,ActionButtonsandActionButtons.v2.Storybook
New page Components / Cards / Glass Actions Hover States, built like the existing theme-compare page: the after columns render the live component with no overrides, the before columns restore #6398's values via scoped CSS, so it stays honest as the component changes. It force-hovers every action (through the
.hoverhookbuttons.cssalready exposes) so all six states can be read at once, plus a rest → hover → pressed strip and a measured table.The audit table reads every colour off two live probe pills — accents from
--glass-actions-*, fill from--theme-background-default— resolvingcolor-mix()through a canvas pixel, so it can't drift from the stylesheet. The WCAG math moved toglassContrast.ts, shared with the theme-compare page.Known follow-up
#6405 — light-mode pressed counters are text at 3.01–3.08 against a 4.5:1 threshold. Pre-existing (it arrived with #6398 and is unchanged here), and both fixes are design calls: darkening the light pressed accents, or giving pressed counters the neutral treatment hover now uses.
Events
No.
Experiment
No.
Manual Testing
Storybook only — the bar has no dedicated route, and hover can't be exercised by the unit tests.
html.light,html.dark,html.autowith an OS light preference, and.invertsubtrees.pnpm --filter @dailydotdev/shared test src/components/cards— 30 suites / 217 testspnpm --filter webapp test— 45 suites / 336 testsnode ./scripts/typecheck-strict-changed.jsNote
.btnand.btn-quaternary labeltransitioncolorover 150ms, sogetComputedStyleright after a class or theme change returns the mid-transition value. Disable transitions before verifying this page by hand.On those affected packages:
CSS-only change to an existing component, scoped to
.feed-card-glass-actions; the extension and companion selector groups are untouched.🤖 Generated with Claude Code
Preview domain
https://claude-action-glassbar-hover-sta.preview.app.daily.dev