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>