InteractionJuiceManipulator plays Transition Sequence assets on pointer interaction — press, release, hover — on any VisualElement, not just buttons.
USS pseudo-states already give buttons basic feedback (.btn:active { scale: 0.95 }), but they have two hard limits:
:active is Button-only. The active pseudo-state is set by UI Toolkit's Clickable, which only Button has. A card, list row, or plain element never enters :active, and user code cannot set pseudo-states.Simple hover feel stays cheaper in USS (:hover works on every element). Reach for the manipulator when you need press feedback on non-buttons or multi-step effects anywhere.
| Slot | Fires on | Typical asset |
|---|---|---|
PressSlot | pointer down (left/primary) | scale to 0.94, fast ease-out, Persist |
ReleaseSlot | pointer up while pressed, or pointer leaving while pressed (cancel) | scale back with overshoot, RestoreOnEnd |
HoverEnterSlot | pointer enter (mouse-only by default) | scale to 1.05, Persist |
HoverExitSlot | pointer leave | scale to 1.0, RestoreOnEnd |
RestoreMode rule: hold-style slots (Press, HoverEnter) end in the held pose — their assets must use Persist, or the restore would snap the pose back. Return slots (Release, HoverExit) end at rest and should use RestoreOnEnd so the inline value is cleaned up and USS regains ownership.
Sequence nodes are USS-transition-driven, so preemption animates from the current value: releasing mid-press reverses smoothly, exactly like pseudo-state auto-reverse.
InteractionJuiceProfileSO (Create → CupkekGames → Luna UI → Interaction Juice Profile) bundles the four slot assets plus options so one asset styles every button in the game:
TransitionSequenceAsset + optional target selectorMouseOnlyHover (default on) — hover slots ignore touch pointersSuppressUssClasses — USS classes added to the element on attach and removed on detach. Use this to silence a competing built-in USS effect: e.g. no-scale disables .btn's own hover/active scale so the profile owns scale outright.The profile is pure data — all runtime state lives in the manipulator instance, so any number of elements can share one profile.
[SerializeField] private InteractionJuiceProfileSO _buttonJuice;
button.AddManipulator(new InteractionJuiceManipulator(_buttonJuice));Or without a profile, pass assets directly:
element.AddManipulator(new InteractionJuiceManipulator(pressAsset, releaseAsset, hoverInAsset, hoverOutAsset));By default all slots play on the manipulator's element. The manipulator exposes its TransitionSlotDriver for retargeting — CSS selector (downward), closest: prefix (upward), or a direct element:
var juice = new InteractionJuiceManipulator(profile);
juice.Driver.SetTarget(InteractionJuiceManipulator.PressSlot, ".card__icon");
juice.Driver.SetTarget(InteractionJuiceManipulator.ReleaseSlot, "closest: .card-frame");
element.AddManipulator(juice);If a sequence asset animates a property the element's USS still transitions (e.g. scale on .btn without suppression), the inline value wins while the sequence plays — hover stops responding mid-sequence — and restore hands control back to USS afterwards. Recoverable but janky. Either set the suppression class or target a child/wrapper instead.
One element has at most one active sequence player at a time: a hover effect and an attractor arrival aimed at the same element preempt each other by design. Target different elements (wrapper vs. content) when two systems must animate simultaneously.
The Progress Bar Juice showcase scene (Components/Juice/ProgressBarJuice) has three buttons wired with Subtle / Punchy / Bouncy profiles sharing hover assets, all suppressing no-scale. Its sibling scene Components/Juice/UICue demonstrates the declarative alternative — the UI Cue binder.
Settings
Theme
Light
Contrast
Material
Dark
Dim
Material Dark
System
Sidebar(Light & Contrast only)
Font Family
DM Sans
Wix
Inclusive Sans
AR One Sans
Direction