Guides
Hover, tap and focus feedback
Give buttons, links and cards hover, press and focus feedback that behaves correctly on touch, and reveal content as it scrolls into view.
Interaction directives animate while something is true — the pointer is over the element, it is pressed, it has focus — and reverse when it stops. They follow their inputs after initialisation, so a bound value can change at any time.
Buttons and links
@Component({
selector: 'app-buy-button',
imports: [MoveHoverDirective, MoveTapDirective],
template: `
<!-- lift = a 4px rise (translate), press = a 3% scale-down. Separate channels, so they compose. -->
<button type="button" moveWhileHover="lift" moveWhileTap="press">Buy now</button>
`,
})
export class BuyButton {}
lift and press are the two interaction presets. They write different style channels (translate and scale), so they compose on one element.
Important
Hover means a mouse or a pen: a finger has no hover state, and emulating one is what produces sticky hover and blocked taps. Pair MoveHoverDirective with MoveTapDirective so touch gets press feedback. Neither directive calls preventDefault(), so links, buttons and scrolling behave natively.
moveWhileHover + moveWhileTap
Both directives follow their inputs, so controls apply immediately. Hover responds to a mouse or pen; the tap effect is the touch feedback.
Pressed 0×
No events yet. Interact with the preview to see them here.
Template
<button
moveWhileHover="lift"
moveWhileTap="press"
[moveDuration]="200"
>Save</button>Each control applies immediately, because these directives are reactive. Open the full hover playground or the tap one.
Focus
MoveFocusDirective animates while the element has focus. Do not use it as the only focus indicator: keep a visible :focus-visible style.
@Component({
selector: 'app-field',
imports: [MoveFocusDirective],
template: `<input [moveWhileFocus]="{ scale: [1, 1.02] }" aria-label="Email" />`,
})
export class Field {}
Reveal on scroll
MoveInViewDirective plays when an element enters the viewport (once by default). It is one-shot, like moveEnter.
@Component({
selector: 'app-product-card',
imports: [MoveInViewDirective, MoveHoverDirective],
template: `<article moveInView="fade-up" moveWhileHover="lift">Product</article>`,
})
export class ProductCard {}
Elements that start hidden and animate in are revealed once the app has booted, so content below the fold is a good fit; keep above-the-fold content visible in the server-rendered HTML.
Related: moveWhileHover playground · moveWhileTap playground · moveWhileFocus playground · moveInView playground