Skip to content
Documentation/Hover, tap and focus feedback

Search documentation

Type to search the documentation.

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.

buy-button.ts
@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 {}

View source

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×

Inputs

Events

No events yet. Interact with the preview to see them here.

Template

<button
  moveWhileHover="lift"
  moveWhileTap="press"
  [moveDuration]="200"
>Save</button>

moveWhileHover + moveWhileTap — expanded preview

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.

field.ts
@Component({
  selector: 'app-field',
  imports: [MoveFocusDirective],
  template: `<input [moveWhileFocus]="{ scale: [1, 1.02] }" aria-label="Email" />`,
})
export class Field {}

View source

Reveal on scroll

MoveInViewDirective plays when an element enters the viewport (once by default). It is one-shot, like moveEnter.

product-card.ts
@Component({
  selector: 'app-product-card',
  imports: [MoveInViewDirective, MoveHoverDirective],
  template: `<article moveInView="fade-up" moveWhileHover="lift">Product</article>`,
})
export class ProductCard {}

View source

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