Skip to content

Animations

Classes for animating elements. In addition to Alpine's x-transition, those can be used by custom components when extending Harmonia.

Class names

ClassDescription
animate-pulseFades in and out, for loading placeholders
animate-pingScales out and fades, for attention dots
animate-spinRotates continuously, for busy indicators
ClassDescription
scale-95scale: 95% 95%
scale-105scale: 105% 105%
ClassDescription
duration-100transition-duration: 100ms;
duration-200transition-duration: 200ms;
duration-300transition-duration: 300ms;
ClassDescription
ease-outtransition-timing-function: var(--ease-out, cubic-bezier(0, 0, 0.2, 1));
ease-lineartransition-timing-function: linear;
ClassDescription
-translate-x-fulltranslate: -100% var(--tw-translate-y);
translate-x-fulltranslate: 100% var(--tw-translate-y);
-translate-y-fulltranslate: var(--tw-translate-x) -100%;
translate-y-fulltranslate: var(--tw-translate-x) 100%;
-translate-x-4translate: calc(var(--spacing) * -4) var(--tw-translate-y);
translate-x-4translate: calc(var(--spacing) * 4) var(--tw-translate-y);
-translate-y-4translate: var(--tw-translate-x) calc(var(--spacing) * -4);
translate-y-4translate: var(--tw-translate-x) calc(var(--spacing) * 4);
ClassDescription
transition-alltransition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;
transition-timing-function: var(--default-transition-timing-function, cubic-bezier(0.4, 0, 0.2, 1));
transition-duration: var(--default-transition-duration, 150ms);
transition-colorstransition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
transition-timing-function: var(--default-transition-timing-function, cubic-bezier(0.4, 0, 0.2, 1));
transition-duration: var(--default-transition-duration, 150ms);
transition-shadowtransition-property: box-shadow;
transition-timing-function: var(--default-transition-timing-function, cubic-bezier(0.4, 0, 0.2, 1));
transition-duration: var(--default-transition-duration, 150ms);
transition-opacitytransition-property: opacity;
transition-timing-function: var(--default-transition-timing-function, cubic-bezier(0.4, 0, 0.2, 1));
transition-duration: var(--default-transition-duration, 150ms);
transition-transformtransition-property: transform, translate, scale, rotate;
transition-timing-function: var(--default-transition-timing-function, cubic-bezier(0.4, 0, 0.2, 1));
transition-duration: var(--default-transition-duration, 150ms);
transition-[opacity,scale]transition-property: opacity, scale;
transition-timing-function: var(--default-transition-timing-function, cubic-bezier(0.4, 0, 0.2, 1));
transition-duration: var(--default-transition-duration, 150ms);
motion-reduce:transition-noneDisables all transitions when the "Reduce Motion" accessibility setting is enabled

Examples

Looping animations

These three run continuously on their own, with no state to drive them. animate-pulse is what the Skeleton component uses for its loading placeholders, animate-spin is what the Spinner uses, and animate-ping suits a dot drawing attention to something new.

html
<div class="h-4 w-3xs animate-pulse rounded-md bg-primary"></div>
<svg x-h-icon class="size-8 animate-spin text-primary" data-icon="refresh" role="presentation"></svg>
<div class="size-3 animate-ping rounded-full bg-primary"></div>

Scale and Opacity

WARNING

Users with vestibular motion disorders often enable the "Reduce Motion" accessibility setting on their devices.

Be sure to account for this preference in your implementation, as excessive motion can cause discomfort or disorientation for affected users.

html
<div class="vbox gap-4" x-data="ScaleController">
  <button x-h-button @click="toggle()" x-text="isShown ? 'Hide' : 'Show'"></button>
  <div x-h-alert class="hidden scale-95 opacity-0 transition-[opacity,scale] duration-200 ease-out motion-reduce:transition-none" x-ref="alert">
    <svg x-h-icon data-icon="circle-info" role="presentation"></svg>
    <div x-h-alert-title>Alert</div>
  </div>
</div>
<script>
  Alpine.data('ScaleController', () => ({
    isShown: false,
    toggle() {
      this.isShown = !this.isShown;
      // Use differnet logic when the user has disabled animations/enabled reduced motion
      if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
        if (this.isShown) {
          this.$refs.alert.classList.remove('hidden', 'scale-95', 'opacity-0');
        } else {
          this.$refs.alert.classList.add('hidden', 'scale-95', 'opacity-0');
        }
      } else if (this.isShown) {
        this.$refs.alert.classList.remove('hidden');
        Alpine.nextTick(() => {
          // Reading 'offsetHeight' forces the browser to apply pending styles first.
          // This guarantees that the animation will always happen.
          this.$refs.alert.offsetHeight;
          this.$refs.alert.classList.remove('scale-95', 'opacity-0');
        });
      } else {
        this.$refs.alert.addEventListener(
          'transitionend',
          () => {
            this.$refs.alert.classList.add('hidden');
          },
          { once: true }
        );
        this.$refs.alert.classList.add('scale-95', 'opacity-0');
      }
    },
  }));
</script>

Translate

html
<div class="border">
  <svg x-h-icon class="size-12 -translate-x-4" data-link="/harmonia/logo/harmonia.svg" role="img" aria-label="Harmonia logo"></svg>
</div>
<div class="border">
  <svg x-h-icon class="size-12 translate-x-4" data-link="/harmonia/logo/harmonia.svg" role="img" aria-label="Harmonia logo"></svg>
</div>
<div class="border">
  <svg x-h-icon class="size-12 -translate-y-4" data-link="/harmonia/logo/harmonia.svg" role="img" aria-label="Harmonia logo"></svg>
</div>
<div class="border">
  <svg x-h-icon class="size-12 translate-y-4" data-link="/harmonia/logo/harmonia.svg" role="img" aria-label="Harmonia logo"></svg>
</div>