Skip to content

feat(ui): make Mosaic button hover land instantly and fade out - #9298

Merged
maxyinger merged 2 commits into
mainfrom
max/button-hover-timing
Aug 3, 2026
Merged

feat(ui): make Mosaic button hover land instantly and fade out#9298
maxyinger merged 2 commits into
mainfrom
max/button-hover-timing

Conversation

@maxyinger

@maxyinger maxyinger commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Description

Mosaic Button hover arrived and left at the same 0.1s. It now lands instantly and leaves over 0.15s, matching the press, which already landed instantly.

The arrival is instant because a hover or a press is confirmation of something the pointer just did, and confirmation cannot lag. The exit is the only contextual half: on an isolated control 0.15s costs nothing and takes the edge off, whereas across a traversed collection the same fade strings a wake of dimming rows out behind a fast sweep — so Item keeps none.

Three things follow:

  • Icon inside a Button no longer trails it. Transitions don't inherit, so an icon on its own duration was still catching up a tenth of a second after the button had landed. Button hands its timing down through --_cl-icon-duration, alongside the --_cl-icon-color the icon already reads. Icon's standalone default drops from fast to instant — the arrival never varies, so any non-zero default was wrong for every container at once, and instant also leaves a traversed collection correct in both directions without opting into anything.
  • The link-* variants' underline fades out instead of snapping. text-decoration-line is a keyword and cannot tween, so toggling none → underline gave an instant exit where every other property faded. The line is now always drawn and only text-decoration-color moves, which paints nothing at rest and never participates in layout. Checked in Chrome and Safari.
  • The rule is documented in the Mosaic skill's motion.md: linear for anything that only recolors, an arrival that never varies, an exit decided by traversal rather than by element type, and the two traps — transitions not inheriting, and keywords not tweening. The duration-token comment, which still assigned hover to fast, is corrected to match.

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

@changeset-bot

changeset-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ef12fb4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview Aug 3, 2026 6:34pm
swingset Ready Ready Preview Aug 3, 2026 6:34pm

Request Review

@pkg-pr-new

pkg-pr-new Bot commented Aug 3, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@9298

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@9298

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@9298

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@9298

@clerk/electron

npm i https://pkg.pr.new/@clerk/electron@9298

@clerk/electron-passkeys

npm i https://pkg.pr.new/@clerk/electron-passkeys@9298

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@9298

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@9298

@clerk/expo-google-signin

npm i https://pkg.pr.new/@clerk/expo-google-signin@9298

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@9298

@clerk/express

npm i https://pkg.pr.new/@clerk/express@9298

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@9298

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@9298

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@9298

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@9298

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@9298

@clerk/react

npm i https://pkg.pr.new/@clerk/react@9298

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@9298

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@9298

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@9298

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@9298

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@9298

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@9298

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@9298

commit: ef12fb4

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-08-03T18:35:36.079Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 0
🔴 Breaking changes 0
🟡 Non-breaking changes 0
🟢 Additions 0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on ef12fb4.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change updates Mosaic motion guidance for hover, press, exit, entrance, and color-only state transitions. Button and Icon styles now use contextual durations, instant hover and active arrivals, base default timing, and persistent link underlines with animated color.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: alexcarpenter, austincalvelage

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: instant Mosaic button hover arrival with a fade-out.
Description check ✅ Passed The description directly explains the timing changes, icon propagation, underline behavior, and motion documentation updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/ui/src/mosaic/components/button/button.styles.ts (1)

89-98: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Shorten the inline timing rationale.

This block repeats .claude/skills/mosaic/references/motion.md and restates selector behavior. Keep one terse comment for the non-obvious :not(:active) constraint. Keep the timing explanation in the reference document.

As per coding guidelines, comments must be minimal and must not restate code behavior.

Proposed comment reduction
-    // The duration a state carries governs the transition INTO it, so asymmetry falls out
-    // of the selector rather than being declared twice. The press reads as contact, not a
-    // fade, so it lands instantly; `:active` stops matching on release and the color heads
-    // back at the hover/resting rate. Hover is quick to acknowledge and unhurried to let
-    // go — `fast` while hovered, so entering takes 0.1s, and `base` at rest, so leaving
-    // takes 0.15s. Instant press, soft settle; quick in, slow out.
-    //
-    // `:not(:active)` is load-bearing: inside `@media (hover: hover)` the hover branch
-    // would otherwise outrank the bare `:active` and steal the press's instant duration.
-    // The resting value also covers release-from-press on a device with no hover at all.
+    // Keep :not(:active) so hover styles do not override instant presses.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/mosaic/components/button/button.styles.ts` around lines 89 -
98, The timing comment block is overly verbose and duplicates external motion
guidance. In the styles near the :not(:active) selector, remove the timing and
selector-behavior explanation, retaining only one brief comment documenting why
:not(:active) is required.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/ui/src/mosaic/components/button/button.styles.ts`:
- Around line 89-98: The timing comment block is overly verbose and duplicates
external motion guidance. In the styles near the :not(:active) selector, remove
the timing and selector-behavior explanation, retaining only one brief comment
documenting why :not(:active) is required.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: eee2dc71-9230-4fd6-8fac-bdb0c294a45a

📥 Commits

Reviewing files that changed from the base of the PR and between 67c81e2 and 2d70852.

📒 Files selected for processing (3)
  • .changeset/button-hover-timing.md
  • .claude/skills/mosaic/references/motion.md
  • packages/ui/src/mosaic/components/button/button.styles.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)

@maxyinger
maxyinger force-pushed the max/button-hover-timing branch from 2d70852 to d22e1e2 Compare August 3, 2026 18:22
@maxyinger maxyinger changed the title feat(ui): make button hover quick to arrive and slow to leave feat(ui): make Mosaic button hover land instantly and fade out Aug 3, 2026
maxyinger and others added 2 commits August 3, 2026 12:30
Hover faded symmetrically at `fast` in both directions. It now arrives instantly and
leaves at `base`, matching the press, which already landed instantly.

The arrival is instant because a hover or a press is confirmation of something the
pointer just did, and confirmation cannot lag. Only the exit is a judgement call:
on an isolated control 0.15s costs nothing and takes the edge off, whereas across a
traversed collection the same fade strings a wake of dimming rows out behind a fast
sweep. Buttons get the exit; `Item` keeps none.

Hover and press now carrying the same duration is what removes the machinery. The
earlier split needed a `@media (hover: hover)` wrapper and a `:not(:active)` guard
purely to stop the hover branch outranking the press; both are gone, and no
hover-media guard is needed either, since a `:hover` that sticks after a tap only
means the color already landed.

`Icon` gets the timing handed to it through `--_cl-icon-duration`, alongside the
`--_cl-icon-color` it already reads, since transitions do not inherit and an icon on
its own duration would trail the button it sits in. Its standalone default drops from
`fast` to `instant`: the arrival never varies, so any non-zero default was wrong for
every container at once, and instant also leaves a traversed collection correct in
both directions without opting into anything.

The link variants' underline follows the same timing. `text-decoration-line` is a
keyword and cannot tween, so toggling it snapped the underline away on exit while
every other property faded. The line is now always drawn and only
`text-decoration-color` moves, which paints nothing at rest, never participates in
layout, and keeps the change a color so it inherits the shared timing unaltered.

Documents the rules in the mosaic skill's motion.md: `linear` for anything that only
recolors, an arrival that never varies, an exit decided by traversal rather than by
element type, the three cases that look like this and are not, and the two traps —
transitions not inheriting, and keywords not tweening. Corrects the duration-token
comment, which still assigned hover to `fast`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mosaic is pre-release, so its changes take empty changesets — matching
`mosaic-item`, `mosaic-button-variants`, and `mosaic-popover`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/skills/mosaic/references/motion.md:
- Around line 10-12: Align the duration tokens in the motion documentation with
the transition example: define the intended press-exit duration, then update the
--cl-duration-fast and --cl-duration-base table entries and the referenced
examples so hover and press exits consistently use the documented token.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: ebc53dee-237a-4639-a70c-9449c1e4b408

📥 Commits

Reviewing files that changed from the base of the PR and between d22e1e2 and ef12fb4.

📒 Files selected for processing (5)
  • .changeset/button-hover-timing.md
  • .claude/skills/mosaic/references/motion.md
  • packages/ui/src/mosaic/components/button/button.styles.ts
  • packages/ui/src/mosaic/components/icon/icon.styles.ts
  • packages/ui/src/mosaic/tokens.stylex.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
🚧 Files skipped from review as they are similar to previous changes (4)
  • .changeset/button-hover-timing.md
  • packages/ui/src/mosaic/components/button/button.styles.ts
  • packages/ui/src/mosaic/tokens.stylex.ts
  • packages/ui/src/mosaic/components/icon/icon.styles.ts

Comment on lines +10 to +12
| `--cl-duration-instant` | `0s` | hover and press arrival |
| `--cl-duration-fast` | `0.1s` | exits |
| `--cl-duration-base` | `0.15s` | entrances, hover exit |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Align the duration-token table with the transition example.

The table describes --cl-duration-fast as the exit duration. The example uses --cl-duration-base for leaving both hover and press states. Define the intended press-exit duration, then update the table and example so implementations cannot diverge.

Also applies to: 96-103, 125-129

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/skills/mosaic/references/motion.md around lines 10 - 12, Align the
duration tokens in the motion documentation with the transition example: define
the intended press-exit duration, then update the --cl-duration-fast and
--cl-duration-base table entries and the referenced examples so hover and press
exits consistently use the documented token.

@maxyinger
maxyinger merged commit 0ba9dbe into main Aug 3, 2026
52 checks passed
@maxyinger
maxyinger deleted the max/button-hover-timing branch August 3, 2026 18:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants