Animations

Animate your UI with CSS transitions, CSS @keyframes, an element's animate() method, or the LynxTransition component for content that appears and disappears.

Choose an approach

You want toUseDetails
Change a color, size or position smoothly when state changesCSS transitionBelow
Loop an animation, or play a fixed sequence (spinner, pulse)CSS @keyframesBelow
Start an animation from code, with keyframes chosen at runtimeThe element's animate() methodAnimate from code
Animate content as it appears and disappearsLynxTransition, LynxTransitionGroupEnter and leave transitions
Move an element with a finger, frame by frameA main-thread scriptMain-thread scripts
Angular's animation APIs

Angular's @angular/animations package (trigger(), state(), transition()) isn't supported. Use the approaches on this page instead.

Lynx elements do have an animate() method that looks like the browser's Web Animations API. It's Lynx's own version: it returns a handle with only play(), pause() and cancel(). See Animate from code.

CSS transitions

Put transition on an element, then change a property by toggling a class from a signal:

src/app/fade.ts
import { Component, signal } from '@angular/core';
import { LYNX_ELEMENTS } from '@blotch/angular-lynx';

@Component({
  selector: 'app-fade',
  imports: [LYNX_ELEMENTS],
  template: `
    <view class="box" [class.active]="active()" (bindtap)="toggle()">
      <text>Tap me</text>
    </view>
  `,
  styles: `
    .box {
      width: 100px;
      height: 100px;
      background-color: #a1a1aa;
      transition: background-color 0.5s ease-in-out; /* [!code highlight] */
    }
    .active {
      background-color: #6366f1;
    }
  `,
})
export class Fade {
  readonly active = signal(false);

  toggle(): void {
    this.active.update((v) => !v);
  }
}
  • The handler only sets a signal, which is always safe in an event handler. Angular updates the class after it returns.
  • A transition runs when a property changes on an element that's already on screen. It doesn't run when the element first appears: use LynxTransition for that.
  • Only some properties animate.
  • Write time values with a unit: 0ms, not 0. Lynx ignores a bare 0.

CSS @keyframes

Define @keyframes in your component styles, and start the animation by adding a class:

src/app/spin.ts
import { Component, signal } from '@angular/core';
import { LYNX_ELEMENTS } from '@blotch/angular-lynx';

@Component({
  selector: 'app-spin',
  imports: [LYNX_ELEMENTS],
  template: `
    <view class="spinner" [class.spinning]="spinning()" (bindtap)="toggle()">
      <text>Spin</text>
    </view>
  `,
  styles: `
    @keyframes rotate {
      from {
        transform: rotate(0deg);
      }
      to {
        transform: rotate(360deg);
      }
    }
    .spinner {
      width: 80px;
      height: 80px;
      background-color: #f97316;
    }
    .spinning {
      animation: rotate 1s linear infinite; /* [!code highlight] */
    }
  `,
})
export class Spin {
  readonly spinning = signal(false);

  toggle(): void {
    this.spinning.update((v) => !v);
  }
}
  • Adding the spinning class starts the animation from its first frame, and removing it stops the animation.
  • infinite in the animation shorthand loops it. Use a number to play it that many times.

Animate from code

Call animate() on an element you get with viewChild():

src/app/pulse.ts
import { Component, type ElementRef, viewChild } from '@angular/core';
import { LYNX_ELEMENTS } from '@blotch/angular-lynx';

@Component({
  selector: 'app-pulse',
  imports: [LYNX_ELEMENTS],
  template: `
    <view #box (bindtap)="pulse()">
      <text>Tap to pulse</text>
    </view>
  `,
})
export class Pulse {
  readonly box = viewChild.required<ElementRef>('box');

  pulse(): void {
    setTimeout(() => {
      this.box().nativeElement.animate(
        [
          { transform: 'scale(1)' },
          { transform: 'scale(1.2)' },
          { transform: 'scale(1)' },
        ],
        { duration: 300, easing: 'ease-out' },
      );
    });
  }
}

The setTimeout is required: start animations after the tap handler returns (see the setTimeout rule). Animate from code covers the options and how to pause or cancel.

Enter and leave

Wrap content in <lynx-transition> to animate it in when show turns true, and out before it's removed:

<lynx-transition [show]="visible()" name="fade" [duration]="300">
  <view class="panel">
    <text>I animate in and out</text>
  </view>
</lynx-transition>

The fade-enter and fade-leave classes each need a @keyframes animation. See Enter and leave transitions.

What can animate

Both transition and @keyframes animate only these properties:

GroupProperties
Positionleft, right, top, bottom
Sizewidth, height, min-width, max-width, min-height, max-height, flex-basis, flex-grow
Spacingpadding-top, padding-right, padding-bottom, padding-left, and the same four for margin
Bordersborder-top-width and the other three sides, border-top-color and the other three sides
Appearanceopacity, background-color, color, filter
Transformtransform, transform-origin

Any other property, such as border-radius, font-size or letter-spacing, doesn't animate.