22 animation primitives — marquee, typewriter, particle systems, text effects, number tickers, and motion wrappers for landing pages, dashboards, and interactive UIs.

Module: PhiaUi.Components.Animation

import PhiaUi.Components.Animation

All components:

  • Respect prefers-reduced-motion — animations disable automatically for users who prefer reduced motion
  • Use vanilla JS hooks — no npm packages
  • Are server-rendered — content is in the DOM before JavaScript runs

Background patterns (gradient mesh, dot grids, bokeh, SVG waves, etc.) live in their own module — see Background.


Table of Contents

Marquee & Orbit

Background Effects

Text Effects

Motion Wrappers

UI Animations


marquee

Infinite horizontal scrolling ticker. Loops child content seamlessly. Hook: PhiaMarquee.

<%!-- Logo cloud --%>
<.marquee speed={40} gap={32} class="py-4">
  <%= for logo <- @logos do %>
    <img src={logo.url} alt={logo.name} class="h-8 grayscale hover:grayscale-0 transition-all" />
  <% end %>
</.marquee>

<%!-- Testimonial ticker --%>
<.marquee speed={30} pause_on_hover={true}>
  <%= for quote <- @quotes do %>
    <.testimonial_card {quote} class="w-72 mx-4 shrink-0" />
  <% end %>
</.marquee>

<%!-- Reverse direction --%>
<.marquee reverse={true} speed={25}>
  <%= for tag <- @tags do %>
    <.badge class="mx-2"><%= tag %></.badge>
  <% end %>
</.marquee>

Attrs: speed (px/s, default 30), gap (px), reverse (boolean), pause_on_hover (boolean), class


orbit

Elements orbit around a central point. Hook: PhiaOrbit.

<.orbit id="skills-orbit" radius={120} speed={20}>
  <:center>
    <.avatar size="xl"><.avatar_image src={@profile.avatar} /></.avatar>
  </:center>
  <:items>
    <img src="/icons/elixir.svg" class="h-8 w-8" />
    <img src="/icons/phoenix.svg" class="h-8 w-8" />
    <img src="/icons/tailwind.svg" class="h-8 w-8" />
    <img src="/icons/postgresql.svg" class="h-8 w-8" />
  </:items>
</.orbit>

Attrs: id (required), radius (px), speed (seconds for full orbit), class


aurora

Animated aurora borealis gradient — soft, shifting colours. Reuses the --animate-aurora theme token.

<div class="relative h-64 overflow-hidden rounded-xl bg-zinc-900">
  <.aurora colors={["#3b82f6", "#8b5cf6", "#ec4899"]} />
  <div class="relative z-10 p-8 text-white">Hero content</div>
</div>

Attrs: colors (list of CSS colors), class


meteor_shower

Animated shooting meteors across a dark background. Hook: PhiaMeteor.

<div class="relative h-96 bg-black overflow-hidden rounded-xl">
  <.meteor_shower count={30} color="rgba(255,255,255,0.8)" />
  <div class="relative z-10">Content</div>
</div>

Attrs: count (integer, default 20), color, class


dot_pattern

Repeating SVG dot pattern background with optional radial fade mask.

<div class="relative bg-white dark:bg-zinc-950">
  <.dot_pattern class="opacity-50" />
  <div class="relative z-10 p-12">Page content</div>
</div>

grid_pattern

SVG grid line pattern with optional fade mask.

<div class="relative">
  <.grid_pattern stroke_color="rgba(0,0,0,0.08)" />
  <div class="relative z-10">Content</div>
</div>

ripple_bg

Expanding concentric circle ripples from a centre point.

<div class="relative h-64 flex items-center justify-center bg-primary/5 rounded-xl overflow-hidden">
  <.ripple_bg color="rgba(99,102,241,0.15)" count={4} duration={3} />
  <.icon name="wifi" size="lg" class="relative z-10 text-primary" />
</div>

particle_bg

Canvas particle system with connecting lines. Hook: PhiaParticleBg.

<div class="relative h-screen bg-zinc-950">
  <.particle_bg
    id="hero-particles"
    count={80}
    color="rgba(99,102,241,0.5)"
    connect={true}
  />
  <div class="relative z-10 flex items-center justify-center h-full">
    <h1 class="text-white text-5xl font-bold">PhiaUI</h1>
  </div>
</div>

Attrs: id (required), count (integer), color, connect (boolean), speed (float)


shimmer_text

Text with a sweeping shimmer highlight.

<.shimmer_text class="text-4xl font-bold">
  PhiaUI
</.shimmer_text>

typewriter

Types text character by character, with optional blinking cursor. Hook: PhiaTypewriter.

<.typewriter
  id="hero-type"
  phrases={["Build faster.", "Ship confidently.", "Own your UI."]}
  speed={80}
  pause={2000}
/>

Attrs: id (required), phrases (list of strings), speed (ms per char), pause (ms between phrases), loop (boolean)


word_rotate

Rotates through a list of words with a fade transition. Hook: PhiaWordRotate.

<h1 class="text-4xl font-bold flex gap-3 items-center">
  Build
  <.word_rotate
    id="rotating-word"
    words={["faster", "smarter", "better", "with confidence"]}
    class="text-primary"
  />
</.h1>

text_scramble

Scrambles and resolves text character by character (Matrix-style). Hook: PhiaTextScramble.

<.text_scramble id="scramble-1" text="PhiaUI" class="text-5xl font-mono font-bold" />

fade_in

Fades and slides content into view on scroll. Hook: PhiaScrollReveal.

<.fade_in id="feature-1" direction={:up} delay={0}>
  <.feature_card title="Fast" description="829 components, zero bloat." />
</.fade_in>
<.fade_in id="feature-2" direction={:up} delay={100}>
  <.feature_card title="Accessible" description="Full WAI-ARIA on all interactive components." />
</.fade_in>

Attrs: id (required), direction (:up | :down | :left | :right), delay (ms), duration (ms)


float

Gently floats content up and down continuously.

<.float amplitude={8} duration={3}>
  <img src="/hero-graphic.svg" class="w-64" />
</.float>

Attrs: amplitude (px, default 6), duration (seconds, default 4), class


tilt_card

Card that tilts in 3D on mouse movement. Hook: PhiaTiltCard.

<.tilt_card id="product-card" max_tilt={15} class="rounded-xl overflow-hidden">
  <.image_card src="/product.jpg" title="Product Name" />
</.tilt_card>

spotlight

Radial spotlight that follows cursor inside the container. Hook: PhiaSpotlight.

<.spotlight id="hero-spotlight" color="rgba(99,102,241,0.15)">
  <div class="p-12">
    <.heading level={1}>Build something great</.heading>
  </div>
</.spotlight>

animated_border

Animates a gradient around the element border.

<.animated_border class="rounded-xl p-0.5">
  <div class="bg-card rounded-xl p-6">
    <h3>Special offer</h3>
  </div>
</.animated_border>

pulse_ring

Pulsing ring around an element — draws attention.

<div class="relative">
  <.pulse_ring color="rgba(99,102,241,0.4)" />
  <.button variant="default">New feature</.button>
</div>

number_ticker

Counts up to a target value with easing. Hook: PhiaNumberTicker.

<.number_ticker id="mrr" value={24500} prefix="$" duration={1500} />
<.number_ticker id="users" value={18723} suffix=" users" duration={2000} />

Attrs: id (required), value (number), prefix, suffix, duration (ms), start (initial value)


typing_indicator

Three animated dots — for chat "is typing" states.

<%= if @contact_is_typing do %>
  <div class="flex items-center gap-2">
    <.avatar size="xs"><.avatar_fallback name={@contact.name} /></.avatar>
    <.typing_indicator />
  </div>
<% end %>

wave_loader

Five vertical bars that animate like a sound wave.

<.wave_loader class="text-primary" />
<.wave_loader size="lg" class="text-muted-foreground" />

confetti_burst

Canvas confetti explosion. Hook: PhiaConfetti.

<.confetti_burst id="success-confetti" />

<%!-- Trigger from LiveView --%>
<%!-- push_event(socket, "confetti", %{id: "success-confetti"}) --%>

Real-world: Hero section with animation stack

<section class="relative min-h-screen overflow-hidden bg-zinc-950">
  <%!-- Background layers --%>
  <.particle_bg id="hero-bg" count={60} color="rgba(99,102,241,0.3)" connect={true} />

  <%!-- Foreground content --%>
  <div class="relative z-10 flex flex-col items-center justify-center min-h-screen px-4 text-center">
    <.fade_in id="hero-badge" direction={:down} delay={0}>
      <.badge variant="outline" class="text-white border-white/20 mb-6">
        v0.1.17 — 829 components
      </.badge>
    </.fade_in>

    <.fade_in id="hero-title" direction={:up} delay={100}>
      <h1 class="text-6xl font-bold text-white mb-4">
        Build Phoenix UIs
        <br />
        <.shimmer_text class="text-indigo-400">in minutes</.shimmer_text>
      </h1>
    </.fade_in>

    <.fade_in id="hero-cta" direction={:up} delay={300}>
      <div class="flex gap-4 mt-8">
        <.glow_button color="#6366f1" phx-click="get_started">Get started</.glow_button>
        <.button variant="outline" class="text-white border-white/20">View docs</.button>
      </div>
    </.fade_in>
  </div>
</section>