fix(button): run favorite icon animation only on click or keyboard activation.#8152
fix(button): run favorite icon animation only on click or keyboard activation.#8152tarunvashishth wants to merge 4 commits intopatternfly:mainfrom
Conversation
WalkthroughAdded a CSS variable for animation-fill-mode and paused the favorited icon animation by default; the animation is set to run only during interaction (focus/active or when Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 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. Comment |
|
Preview: https://pf-pr-8152.surge.sh A11y report: https://pf-pr-8152-a11y.surge.sh |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/patternfly/components/Button/button.scss (2)
768-772:⚠️ Potential issue | 🟠 MajorAnimation replays on every focus of an already-favorited button.
When an already-favorited button loses focus and is then focused again (e.g., via Tab navigation), the
pf-m-favorited:focusselector transitions from non-matching → matching, which causesanimation-nameto go from unset back to#{$button}-icon-favorited— theanimation-nameproperty specifies the name of a keyframe animation, and loading or applying a new name causes the animation to execute. This means the "favorited" scale animation will fire repeatedly — once for every time the user Tabs back onto an already-favorited button — rather than only on the one-time transition into the favorited state.The root cause is that
:focusis a momentary state: as soon as focus leaves and returns, CSS sees theanimation-namebeing "freshly applied" again, restarting the animation. This is the same mechanism that causes the original page-load bug; it's just gated by a different condition now.A CSS-only workaround that avoids the replay problem is to keep
animation-namepermanently set on.pf-m-favorited .#{$button}__icon(so it never gets "re-applied"), and instead suppress it at the moment of initial render usinganimation-play-state:💡 Alternative approach using
animation-play-state- // Favorite button will run an animation when favorited - &.pf-m-favorited:focus .#{$button}__icon { - animation-name: #{$button}-icon-favorited; - animation-duration: var(--#{$button}--m-favorited__icon--AnimationDuration); - animation-timing-function: var(--#{$button}--m-favorited__icon--AnimationTimingFunction); - } + // Favorite button runs an animation when transitioning to favorited + &.pf-m-favorited .#{$button}__icon { + animation-name: #{$button}-icon-favorited; + animation-duration: var(--#{$button}--m-favorited__icon--AnimationDuration); + animation-timing-function: var(--#{$button}--m-favorited__icon--AnimationTimingFunction); + animation-play-state: paused; + } + + &.pf-m-favorited:focus .#{$button}__icon, + &.pf-m-favorited:active .#{$button}__icon { + animation-play-state: running; + }Note:
animation-play-state: pausedkeeps theanimation-namealways set (so it is never "re-applied"), whilerunningonly during an active focus/click interaction. Resuming a paused animation will start the animation from where it left off at the time it was paused, rather than starting over from the beginning of the animation sequence — so this still may not perfectly solve all scenarios. The most reliable fix for "play once on state change" requires a JavaScriptanimationendhandler to clear the triggering class, as purely CSS-driven approaches cannot distinguish "first application" from "re-application."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/patternfly/components/Button/button.scss` around lines 768 - 772, The animation is being re-applied on every focus because the rule sets animation-name only for the :focus state; update the selectors so the animation-name for the favorited icon is set permanently on the favorited state (selector .pf-m-favorited .#{$button}__icon) and move the play control into a separate property (use animation-play-state: paused by default and set to running only on :focus/.pf-m-favorited:focus .#{$button}__icon) so the animation-name is not re-applied when focus toggles; modify the rules around .pf-m-favorited .#{$button}__icon and .pf-m-favorited:focus .#{$button}__icon to implement this change.
768-772:⚠️ Potential issue | 🟠 MajorAnimation silently absent on mouse-click in macOS Safari.
On macOS, Chrome and Firefox will focus all interactive controls on click — including inputs, buttons, links, and even elements with
tabindex="-1". Safari, on the other hand, does not focus buttons and links when clicked. The reason is that macOS didn't transfer focus to operating system and application buttons when they were clicked, and the stakeholders decided to be consistent with the operating system instead. The discussions have lasted 13 years and the team at Apple is adamant: buttons, links, and non-text inputs won't get focus.Because the animation is now gated on
:focus, macOS Safari users who click to favorite a button will never see the animation — the:focuspseudo-class simply isn't set on a button click in that browser. The PR title states the animation should fire "on click or keyboard activation", but:focusalone cannot fulfill the click case in Safari.Adding
:activeto the selector partially addresses the click case (:activefires onmousedownin all browsers), though:activeis only held during the press duration andanimation-fill-mode: bothwould be needed to ensure the animation completes:💡 Proposed addition of
:activeto cover mouse-click in all browsers- &.pf-m-favorited:focus .#{$button}__icon { + &.pf-m-favorited:focus .#{$button}__icon, + &.pf-m-favorited:active .#{$button}__icon { animation-name: #{$button}-icon-favorited; animation-duration: var(--#{$button}--m-favorited__icon--AnimationDuration); animation-timing-function: var(--#{$button}--m-favorited__icon--AnimationTimingFunction); + animation-fill-mode: both; }This does not fully resolve the replay-on-re-focus concern raised separately, and ultimately a JS-driven approach (toggling a one-shot class on
click/keydown, then removing it onanimationend) would be the most semantically correct solution.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/patternfly/components/Button/button.scss` around lines 768 - 772, The animation is only triggered on :focus which Safari doesn't apply on click; update the CSS selector for the favorited icon (the .pf-m-favorited rule that targets .#{$button}__icon and sets animation-name/animation-duration/animation-timing-function) to also match :active (e.g. .pf-m-favorited:focus and .pf-m-favorited:active) so mouse clicks in Safari trigger it, and add animation-fill-mode: both (or set the corresponding --#{$button}--m-favorited__icon--AnimationFillMode variable) so the animation completes after :active ends; consider a separate JS-driven one-shot class if replay-on-refocus is required.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@src/patternfly/components/Button/button.scss`:
- Around line 768-772: The animation is being re-applied on every focus because
the rule sets animation-name only for the :focus state; update the selectors so
the animation-name for the favorited icon is set permanently on the favorited
state (selector .pf-m-favorited .#{$button}__icon) and move the play control
into a separate property (use animation-play-state: paused by default and set to
running only on :focus/.pf-m-favorited:focus .#{$button}__icon) so the
animation-name is not re-applied when focus toggles; modify the rules around
.pf-m-favorited .#{$button}__icon and .pf-m-favorited:focus .#{$button}__icon to
implement this change.
- Around line 768-772: The animation is only triggered on :focus which Safari
doesn't apply on click; update the CSS selector for the favorited icon (the
.pf-m-favorited rule that targets .#{$button}__icon and sets
animation-name/animation-duration/animation-timing-function) to also match
:active (e.g. .pf-m-favorited:focus and .pf-m-favorited:active) so mouse clicks
in Safari trigger it, and add animation-fill-mode: both (or set the
corresponding --#{$button}--m-favorited__icon--AnimationFillMode variable) so
the animation completes after :active ends; consider a separate JS-driven
one-shot class if replay-on-refocus is required.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/patternfly/components/Button/button.scss`:
- Around line 768-781: The current CSS uses &:focus and &:active on
.pf-m-favorited .#{$button}__icon to set animation-play-state: running, which
causes animations to play when tabbing to pre-favorited buttons; change the
implementation to toggle animation via a transient class added/removed by the
favoriting action instead: remove the &:focus and &:active animation-play-state
rules for .pf-m-favorited .#{$button}__icon and instead have the favoriting
click/keyboard handler on the button element add a one-shot class (suggested
name pf-m-is-animating) to the button, set animation-play-state: running in the
.pf-m-is-animating .#{$button}__icon rule in this stylesheet, and have the
handler remove pf-m-is-animating on the button after the icon’s animationend
event so only transitions from unfavorited→favorited trigger the animation.
- Around line 769-775: The CSS sets animation-play-state: paused on
.pf-m-favorited .#{$button}__icon which can freeze the icon at an intermediate
scale if focus is lost mid-animation; change that rule so the animation does not
remain paused — either remove the animation-play-state property or set it to
animation-play-state: running for the selector .pf-m-favorited
.#{$button}__icon, or alternatively scope paused only to a focus state (e.g.
.pf-m-favorited:focus .#{$button}__icon) so the favorited animation always
completes and does not remain stuck at scale 1.5.
| // Keep animation definition on favorited state so focus toggles don't reapply animation-name | ||
| &.pf-m-favorited .#{$button}__icon { | ||
| animation-name: #{$button}-icon-favorited; | ||
| animation-duration: var(--#{$button}--m-favorited__icon--AnimationDuration); | ||
| animation-play-state: paused; | ||
| animation-timing-function: var(--#{$button}--m-favorited__icon--AnimationTimingFunction); | ||
| animation-fill-mode: var(--#{$button}--m-favorited__icon--AnimationFillMode); | ||
| } | ||
|
|
||
| // Run animation while the favorited button is keyboard focused or actively pressed | ||
| &.pf-m-favorited:focus .#{$button}__icon, | ||
| &.pf-m-favorited:active .#{$button}__icon { | ||
| animation-play-state: running; | ||
| } |
There was a problem hiding this comment.
Animation fires on Tab navigation to already-favorited buttons, contradicting the linked issue's expected behavior.
Using :focus means the animation triggers every time a keyboard user tabs to a button that is already in the pf-m-favorited state — not just when the button transitions from unfavorited → favorited. A page with many pre-favorited items would play the animation for each as the user Tabs through them. This directly contradicts issue #7894's stated expectation:
"animation should occur only when transitioning from not-favorited to favorited (e.g., via click or keyboard activation)"
Root cause: CSS pseudo-classes cannot distinguish "focus gained because the user just favorited this button" from "focus gained because the user tabbed here." The only pure-CSS escape is :focus-visible, but that also fires on Tab.
Recommended fix (requires JS): Remove animation-play-state toggling from CSS pseudo-classes and instead have the click/keyboard handler on the button element briefly add a one-shot class (e.g., pf-m-is-animating) that sets animation-play-state: running, then remove it after animationend. This precisely scopes the animation to the favoriting action.
- // Run animation while the favorited button is keyboard focused or actively pressed
- &.pf-m-favorited:focus .#{$button}__icon,
- &.pf-m-favorited:active .#{$button}__icon {
- animation-play-state: running;
- }
+ // Run animation only when the is-animating class is added (e.g., on click/keyboard activation)
+ &.pf-m-favorited.pf-m-is-animating .#{$button}__icon {
+ animation-play-state: running;
+ }If a pure-CSS fix is the hard constraint, :focus-visible is marginally better than :focus (avoids triggering on mouse-click focus in browsers that support it), but still fires on Tab and cannot eliminate the Tab-navigation animation. In that case, document the intentional behavior difference from the issue's expectation.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/patternfly/components/Button/button.scss` around lines 768 - 781, The
current CSS uses &:focus and &:active on .pf-m-favorited .#{$button}__icon to
set animation-play-state: running, which causes animations to play when tabbing
to pre-favorited buttons; change the implementation to toggle animation via a
transient class added/removed by the favoriting action instead: remove the
&:focus and &:active animation-play-state rules for .pf-m-favorited
.#{$button}__icon and instead have the favoriting click/keyboard handler on the
button element add a one-shot class (suggested name pf-m-is-animating) to the
button, set animation-play-state: running in the .pf-m-is-animating
.#{$button}__icon rule in this stylesheet, and have the handler remove
pf-m-is-animating on the button after the icon’s animationend event so only
transitions from unfavorited→favorited trigger the animation.
There was a problem hiding this comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/patternfly/components/Button/button.scss`:
- Around line 782-787: The CSS selector rules for triggering the icon animation
currently include focus fallbacks (&.pf-m-favorited:focus-visible
.#{$button}__icon and &.pf-m-favorited:focus:not(:focus-visible)
.#{$button}__icon) which cause Tab navigation to animate pre-favorited buttons;
remove those focus-related selectors and only keep the pf-m-is-animating path
and the :active fallback so animation runs only when transitioning or on press
(i.e., leave &.pf-m-is-animating .#{$button}__icon and &.pf-m-favorited:active
.#{$button}__icon, remove the :focus-visible and :focus:not(:focus-visible)
selectors), or if you must keep focus fallbacks, add explicit documentation
noting they deviate from the “only on transition” requirement.
Fixes #7894
src/patternfly/components/Button/button.scss:765from.pf-m-favoritedto.pf-m-favorited:focus.Summary by CodeRabbit