Text & Numbers

Text Shimmer Wave

Text with a wave-like shimmer animation effect.

The TextShimmerWave component creates text with a wave-like shimmer animation effect. It's perfect for creating eye-catching headings and text elements with dynamic 3D transformations.

Each character is its own motion-v element inside a shared perspective container, and the wave comes from staggering those characters: the delay is index × duration / spread / length, so the bump travels from left to right. Every character animates translateZ, translateX, translateY, scale, rotateY, and its colour together, and duration sets how long one character takes to complete the cycle.

Usage

Basic Wave
<template>
  <div class="flex justify-center">
    <MTextShimmerWave text="Basic Wave" />
  </div>
</template>

Examples

Hands-on control

Bumping a :key remounts the wave and restarts the animation from the beginning, which is handy when the effect should play on demand instead of looping forever.

Click Me
<template>
  <div class="p-4 space-y-4 flex flex-col">
    <MTextShimmerWave
      :key="waveKey"
      :text="waveText"
      class="text-xl font-bold"
    />
    <button
      class="px-4 py-2 bg-primary/70 text-white rounded hover:bg-primary transition-colors"
      @click="triggerWave"
    >
      Replay Wave Animation
    </button>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const waveText = ref('Click Me')
const waveKey = ref(0)

const triggerWave = () => {
  waveKey.value++
}
</script>

Custom transition and spread

The transition prop is merged onto every character's animation, so an easing such as backOut gives the wave a bouncier feel. spread controls how far apart the characters are staggered.

SPRINGY

A custom easing and a wider spread give the wave a bouncier character.

<template>
  <div class="flex flex-col items-center gap-3 p-8">
    <MTextShimmerWave
      text="SPRINGY"
      :spread="3"
      :transition="{ ease: 'backOut' }"
      class="text-3xl font-bold"
    />
    <p class="text-sm text-muted">
      A custom easing and a wider spread give the wave a bouncier character.
    </p>
  </div>
</template>

Styled showcase

A fuller page built around the component: live range controls for every distance, element-type switching through as, gradient text via class, and several presets.

Wave Animation

Transform Your Digital Experience

Create stunning interfaces with our collection of animated components that enhance user engagement and drive results.

Wave Controls

Live Preview

Title Preview

Transform Your Digital Experience

Subtitle Preview

Create stunning interfaces with our collection of animated components that enhance user engagement and drive results.

Example Usage

Feature HighlightNew!
Performance99.9% Uptime
Get Started Today

Why Choose Our Wave Effects?

Blazing quick

Tuned for speed with minimal load times

Beautiful Effects

Stunning animations that captivate your audience

Plug-in ready

Quick to add with clean, well-documented code

<template>
  <div class="p-8 bg-gradient-to-br from-cyan-50 to-teal-50 dark:from-gray-900 dark:to-teal-900 rounded-2xl">
    <div class="max-w-4xl mx-auto">
      <!-- Header -->
      <div class="text-center mb-10">
        <div class="inline-flex items-center gap-2 px-4 py-2 bg-teal-500/20 rounded-full mb-4">
          <UIcon
            name="i-lucide-wave"
            class="w-5 h-5 text-teal-400"
          />
          <span class="text-teal-400 font-medium">Wave Animation</span>
        </div>
        <h1 class="text-4xl md:text-5xl font-black mb-4">
          <MTextShimmerWave
            :text="waveSettings.title"
            :as="waveSettings.elementType"
            :duration="waveSettings.duration"
            :z-distance="waveSettings.zDistance"
            :x-distance="waveSettings.xDistance"
            :y-distance="waveSettings.yDistance"
            :scale-distance="waveSettings.scaleDistance"
            :rotate-y-distance="waveSettings.rotateYDistance"
            :class="waveSettings.titleClass"
          />
        </h1>
        <p class="text-xl text-muted max-w-2xl mx-auto">
          <MTextShimmerWave
            :text="waveSettings.subtitle"
            :duration="waveSettings.duration * 1.2"
            :z-distance="waveSettings.zDistance * 0.7"
            :x-distance="waveSettings.xDistance * 0.7"
            :y-distance="waveSettings.yDistance * 0.7"
            class="inline-block"
          />
        </p>
      </div>

      <!-- Controls and Preview -->
      <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-12">
        <!-- Controls Panel -->
        <div class="bg-default rounded-xl p-6 border border-default shadow-lg">
          <h2 class="text-2xl font-bold mb-6 flex items-center">
            <UIcon
              name="i-lucide-sliders"
              class="w-6 h-6 mr-2 text-teal-400"
            />
            Wave Controls
          </h2>

          <div class="space-y-6">
            <!-- Text Inputs -->
            <div>
              <label class="block text-default mb-2">Title Text</label>
              <UInput
                v-model="waveSettings.title"
                placeholder="Enter title text"
                size="lg"
              />
            </div>

            <div>
              <label class="block text-default mb-2">Subtitle Text</label>
              <UInput
                v-model="waveSettings.subtitle"
                placeholder="Enter subtitle text"
                size="lg"
              />
            </div>

            <!-- Element Type -->
            <div>
              <label class="block text-default mb-2">Element Type</label>
              <div class="grid grid-cols-3 gap-2">
                <UButton
                  v-for="type in elementTypes"
                  :key="type.value"
                  :color="waveSettings.elementType === type.value ? 'primary' : 'gray'"
                  variant="solid"
                  size="sm"
                  @click="waveSettings.elementType = type.value"
                >
                  {{ type.label }}
                </UButton>
              </div>
            </div>

            <!-- Animation Controls -->
            <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
              <div>
                <label class="block text-default mb-2">
                  Duration: {{ waveSettings.duration }}s
                </label>
                <input
                  v-model="waveSettings.duration"
                  type="range"
                  min="0.5"
                  max="5"
                  step="0.1"
                  class="w-full h-2 bg-accented rounded-lg appearance-none cursor-pointer"
                >
              </div>

              <div>
                <label class="block text-default mb-2">
                  Z Distance: {{ waveSettings.zDistance }}
                </label>
                <input
                  v-model="waveSettings.zDistance"
                  type="range"
                  min="0"
                  max="50"
                  step="1"
                  class="w-full h-2 bg-accented rounded-lg appearance-none cursor-pointer"
                >
              </div>

              <div>
                <label class="block text-default mb-2">
                  X Distance: {{ waveSettings.xDistance }}
                </label>
                <input
                  v-model="waveSettings.xDistance"
                  type="range"
                  min="-20"
                  max="20"
                  step="1"
                  class="w-full h-2 bg-accented rounded-lg appearance-none cursor-pointer"
                >
              </div>

              <div>
                <label class="block text-default mb-2">
                  Y Distance: {{ waveSettings.yDistance }}
                </label>
                <input
                  v-model="waveSettings.yDistance"
                  type="range"
                  min="-20"
                  max="20"
                  step="1"
                  class="w-full h-2 bg-accented rounded-lg appearance-none cursor-pointer"
                >
              </div>
            </div>

            <!-- Advanced Controls -->
            <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
              <div>
                <label class="block text-default mb-2">
                  Scale: {{ waveSettings.scaleDistance }}
                </label>
                <input
                  v-model="waveSettings.scaleDistance"
                  type="range"
                  min="0.5"
                  max="2"
                  step="0.1"
                  class="w-full h-2 bg-accented rounded-lg appearance-none cursor-pointer"
                >
              </div>

              <div>
                <label class="block text-default mb-2">
                  Rotate Y: {{ waveSettings.rotateYDistance }}
                </label>
                <input
                  v-model="waveSettings.rotateYDistance"
                  type="range"
                  min="0"
                  max="45"
                  step="1"
                  class="w-full h-2 bg-accented rounded-lg appearance-none cursor-pointer"
                >
              </div>
            </div>

            <!-- Preset Styles -->
            <div>
              <label class="block text-default mb-2">Preset Styles</label>
              <div class="grid grid-cols-2 gap-2">
                <UButton
                  v-for="style in presetStyles"
                  :key="style.name"
                  :color="currentPreset === style.name ? 'primary' : 'gray'"
                  variant="solid"
                  size="sm"
                  @click="applyPreset(style)"
                >
                  {{ style.name }}
                </UButton>
              </div>
            </div>
          </div>
        </div>

        <!-- Preview Panel -->
        <div class="bg-default rounded-xl p-6 border border-default shadow-lg">
          <h2 class="text-2xl font-bold mb-6 flex items-center">
            <UIcon
              name="i-lucide-monitor"
              class="w-6 h-6 mr-2 text-teal-400"
            />
            Live Preview
          </h2>

          <div class="space-y-8">
            <!-- Title Preview -->
            <div>
              <h3 class="text-lg font-semibold text-default mb-3">
                Title Preview
              </h3>
              <div class="p-6 bg-gradient-to-r from-cyan-50 to-teal-50 dark:from-gray-700 dark:to-gray-800 rounded-lg">
                <MTextShimmerWave
                  :text="waveSettings.title"
                  :as="waveSettings.elementType"
                  :duration="waveSettings.duration"
                  :z-distance="waveSettings.zDistance"
                  :x-distance="waveSettings.xDistance"
                  :y-distance="waveSettings.yDistance"
                  :scale-distance="waveSettings.scaleDistance"
                  :rotate-y-distance="waveSettings.rotateYDistance"
                  :class="waveSettings.titleClass"
                />
              </div>
            </div>

            <!-- Subtitle Preview -->
            <div>
              <h3 class="text-lg font-semibold text-default mb-3">
                Subtitle Preview
              </h3>
              <div class="p-6 bg-gradient-to-r from-cyan-50 to-teal-50 dark:from-gray-700 dark:to-gray-800 rounded-lg">
                <MTextShimmerWave
                  :text="waveSettings.subtitle"
                  :duration="waveSettings.duration * 1.2"
                  :z-distance="waveSettings.zDistance * 0.7"
                  :x-distance="waveSettings.xDistance * 0.7"
                  :y-distance="waveSettings.yDistance * 0.7"
                  :scale-distance="waveSettings.scaleDistance"
                  :rotate-y-distance="waveSettings.rotateYDistance * 0.7"
                  class="text-xl"
                />
              </div>
            </div>

            <!-- Example Usage -->
            <div>
              <h3 class="text-lg font-semibold text-default mb-3">
                Example Usage
              </h3>
              <div class="p-6 bg-gradient-to-r from-cyan-50 to-teal-50 dark:from-gray-700 dark:to-gray-800 rounded-lg">
                <div class="space-y-6">
                  <div class="flex items-center justify-between p-4 bg-default rounded-lg shadow">
                    <span class="text-default">Feature Highlight</span>
                    <MTextShimmerWave
                      text="New!"
                      :duration="1"
                      :z-distance="10"
                      class="text-sm font-bold px-3 py-1 rounded-full bg-gradient-to-r from-teal-500 to-cyan-500 text-white"
                    />
                  </div>

                  <div class="flex items-center justify-between p-4 bg-default rounded-lg shadow">
                    <span class="text-default">Performance</span>
                    <MTextShimmerWave
                      text="99.9% Uptime"
                      :duration="1.5"
                      :z-distance="15"
                      class="text-lg font-bold text-green-600 dark:text-green-400"
                    />
                  </div>

                  <div class="text-center p-4 bg-default rounded-lg shadow">
                    <MTextShimmerWave
                      text="Get Started Today"
                      :duration="1.2"
                      :z-distance="12"
                      :x-distance="3"
                      :y-distance="-1"
                      class="text-2xl font-bold text-teal-600 dark:text-teal-400"
                    />
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>

      <!-- Feature Showcase -->
      <div class="bg-default rounded-xl p-8 border border-default shadow-lg">
        <h2 class="text-3xl font-bold text-center mb-8 text-highlighted">
          <MTextShimmerWave
            text="Why Choose Our Wave Effects?"
            :duration="1.8"
            :z-distance="15"
            class="inline-block"
          />
        </h2>

        <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
          <UCard class="text-center hover:shadow-xl transition-shadow">
            <div class="flex justify-center mb-4">
              <div class="w-16 h-16 rounded-full bg-teal-100 dark:bg-teal-900 flex items-center justify-center">
                <UIcon
                  name="i-lucide-zap"
                  class="w-8 h-8 text-teal-600 dark:text-teal-400"
                />
              </div>
            </div>
            <h3 class="text-lg font-semibold mb-2">
              <MTextShimmerWave
                text="Blazing quick"
                :duration="1"
                :z-distance="10"
                class="inline-block text-teal-600 dark:text-teal-400"
              />
            </h3>
            <p class="text-muted text-sm">
              Tuned for speed with minimal load times
            </p>
          </UCard>

          <UCard class="text-center hover:shadow-xl transition-shadow">
            <div class="flex justify-center mb-4">
              <div class="w-16 h-16 rounded-full bg-cyan-100 dark:bg-cyan-900 flex items-center justify-center">
                <UIcon
                  name="i-lucide-sparkles"
                  class="w-8 h-8 text-cyan-600 dark:text-cyan-400"
                />
              </div>
            </div>
            <h3 class="text-lg font-semibold mb-2">
              <MTextShimmerWave
                text="Beautiful Effects"
                :duration="1.2"
                :z-distance="12"
                class="inline-block text-cyan-600 dark:text-cyan-400"
              />
            </h3>
            <p class="text-muted text-sm">
              Stunning animations that captivate your audience
            </p>
          </UCard>

          <UCard class="text-center hover:shadow-xl transition-shadow">
            <div class="flex justify-center mb-4">
              <div class="w-16 h-16 rounded-full bg-green-100 dark:bg-green-900 flex items-center justify-center">
                <UIcon
                  name="i-lucide-code"
                  class="w-8 h-8 text-green-600 dark:text-green-400"
                />
              </div>
            </div>
            <h3 class="text-lg font-semibold mb-2">
              <MTextShimmerWave
                text="Plug-in ready"
                :duration="1.1"
                :z-distance="11"
                class="inline-block text-green-600 dark:text-green-400"
              />
            </h3>
            <p class="text-muted text-sm">
              Quick to add with clean, well-documented code
            </p>
          </UCard>
        </div>

        <div class="mt-10 text-center">
          <UButton
            size="lg"
            color="primary"
            class="px-8 py-3 text-lg"
          >
            <MTextShimmerWave
              text="Get Started Now"
              :duration="1.3"
              :z-distance="13"
              class="font-bold"
            />
          </UButton>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const waveSettings = ref({
  title: 'Transform Your Digital Experience',
  subtitle: 'Create stunning interfaces with our collection of animated components that enhance user engagement and drive results.',
  elementType: 'h1',
  duration: 1,
  zDistance: 10,
  xDistance: 2,
  yDistance: -2,
  scaleDistance: 1.1,
  rotateYDistance: 10,
  titleClass: 'text-4xl font-black text-transparent bg-clip-text bg-gradient-to-r from-teal-600 to-cyan-600',
})

const elementTypes = [
  { value: 'h1', label: 'H1' },
  { value: 'h2', label: 'H2' },
  { value: 'h3', label: 'H3' },
  { value: 'p', label: 'P' },
  { value: 'span', label: 'Span' },
  { value: 'div', label: 'Div' },
]

const presetStyles = [
  {
    name: 'Ocean',
    class: 'text-4xl font-black text-transparent bg-clip-text bg-gradient-to-r from-teal-600 to-cyan-600',
  },
  {
    name: 'Sunset',
    class: 'text-4xl font-black text-transparent bg-clip-text bg-gradient-to-r from-orange-500 to-pink-500',
  },
  {
    name: 'Forest',
    class: 'text-4xl font-black text-transparent bg-clip-text bg-gradient-to-r from-green-600 to-emerald-600',
  },
  {
    name: 'Purple',
    class: 'text-4xl font-black text-transparent bg-clip-text bg-gradient-to-r from-purple-600 to-indigo-600',
  },
]

const currentPreset = ref('Ocean')

const applyPreset = (style) => {
  currentPreset.value = style.name
  waveSettings.value.titleClass = style.class
}
</script>

Customize

Wave text

Customize

TextWave text
Asspan
Duration (s)1
Z distance10
X distance2
Y distance-2
Spread1
Scale1.1
Rotate Y10

Props

Prop Type Default Description
textstring''Text to shimmer with wave effect.
asstring'span'HTML element to render as.
classstringundefinedCSS classes to apply to the container.
durationnumber1Animation duration in seconds for one character's cycle.
zDistancenumber10Z-axis movement distance.
xDistancenumber2X-axis movement distance.
yDistancenumber-2Y-axis movement distance.
spreadnumber1Spread of the wave effect. Divides the per-character stagger, so larger spreads pull the characters closer together in time.
scaleDistancenumber1.1Scale transformation distance.
rotateYDistancenumber10Y-axis rotation distance, in degrees.
transitionobject{}Custom transition options, merged over the generated per-character transition.
uiobjectundefinedPer-slot classes merged over the theme defaults. See UI.

UI

This component's theme exposes the slots below. Override any of them per instance with the ui prop, or app-wide under ui.textShimmerWave in app.config.ts.

Prop Type Default Description
uiobject-Per-slot classes merged over the theme defaults.
Slot Props Description
root-relative inline-block [perspective:500px] [--base-color:#a1a1aa] [--base-gradient-color:#000] dark:[--base-color:#71717a] dark:[--base-gradient-color:#ffffff]
char-inline-block whitespace-pre [transform-style:preserve-3d]

Accessibility

The text is split into one element per character for animation, so the DOM reads as a run of single-letter spans rather than words. Screen readers still read the string in order, but character-by-character grouping can affect pronunciation and selection — for long or important copy, prefer TextShimmer, which keeps the text in a single node.

The root carries no aria-label, and there is no prefers-reduced-motion handling: the loop runs regardless. For reduced-motion contexts, fall back to static text.

Performance

  • Each character is its own animated element, so cost scales with the length of the string — keep wave text short, especially on pages with several instances.
  • The animations run on transform (translateZ/translateX/translateY/scale/rotateY) and color, and the parent reserves 3D space with perspective: 500px and transform-style: preserve-3d, so the wave is composited rather than laid out each frame.
  • The per-character delay and repeatDelay are derived once from length, duration, and spread; changing any of them re-renders the transition objects but creates no new elements.
Copyright © 2026