Text Animations

Logo Reveal Set

Five common logo entrances (bloom, stroke-then-fill, shine sweep, lockup slide-out and wordmark morph) triggered on mount, on entering the viewport or manually.

Five brand logo entrances in one component. bloom grows a glowing point past full size, then settles; stroke draws along an SVG path and then deepens the light stroke into the final colour; shine fades in and sweeps a diagonal highlight that stays inside the logo strokes; lockup brings in the mark first and slides the wordmark out from a mask behind it; morph turns the wordmark into another string character by character, with shared characters sliding to their new place instead of reappearing. Play it on mount, when it enters the viewport, or manually with ref.play(); onComplete fires when it finishes.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add logo-reveal-set.en

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

1.2
0.6
Active edited usageDefault props
<LogoRevealSet/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

Installation

npx webberui@latest add https://webberui.com/r/logo-reveal-set.en.json

Or, once registries are configured in components.json, install it as @webberui/logo-reveal-set.

Usage

import * as React from "react";
import {
  LogoRevealSet,
  type LogoRevealSetHandle,
} from "@/components/ui/logo-reveal-set";

const MARK = "M9.5 52C14 52 19 12 24 12C29 12 32 43 37 43C40.5 43 41.5 36 45 36L55 36";

export function Hero() {
  const ref = React.useRef<LogoRevealSetHandle>(null);
  return (
    <>
      <LogoRevealSet
        ref={ref}
        variant="stroke"
        trigger="inView"
        path={MARK}
        strokeWidth={7.5}
        wordmark="Harbor Mist"
        onComplete={() => console.log("entrance finished")}
      />
      <button type="button" onClick={() => ref.current?.play()}>
        Replay
      </button>
    </>
  );
}

// The mark can also be any node (for variants that do not draw a stroke)
<LogoRevealSet variant="lockup" wordmark="Ridge Goods">
  <svg width="40" height="40" viewBox="0 0 40 40" aria-hidden="true">
    <circle cx="20" cy="20" r="16" fill="currentColor" />
  </svg>
</LogoRevealSet>

// Wordmark morph
<LogoRevealSet variant="morph" wordmark="WebberUI" morphTo="Webber UI 元件庫" path={MARK} />

Props

PropTypeDefaultDescription
childrenReactNode—The mark: an SVG or any node; when path is provided the mark is drawn from path and children are not shown
wordmarkReactNode—The wordmark; must be a string for the morph variant
morphTostring—Target wordmark for morph; the wordmark morphs character by character from wordmark into this text
pathstring—SVG path (d attribute) for the mark; the stroke variant needs it to draw
viewBoxstring"0 0 64 64"SVG viewBox for path; must match the path coordinate system
sizenumber48Side length of the path mark (px)
strokeWidthnumber6Stroke width of the path (viewBox units)
fillstring"none"Fill for the path; with none, the fill phase of an open line deepens the stroke colour instead
variant"bloom" | "stroke" | "shine" | "lockup" | "morph""bloom"Entrance variant
trigger"mount" | "inView" | "manual""mount"When to play; with manual, call ref.play()
durationnumber1.2Total entrance duration (s); when omitted, reads --wb-lrs-duration and falls back to 1.2
glownumber0.6Glow strength 0–1, mapped to a two-layer drop-shadow of 0–16px
glowColorstring"var(--wb-lrs-glow, currentColor)"Glow colour
delaynumber0Delay before starting once triggered (s)
onComplete() => void—Called when the entrance has fully finished; with reduce motion it fires immediately when triggered
ariaLabelstringwordmark textAccessible name of the outer role="img"
wordmarkClassNamestring—Classes passed to the wordmark text (size, weight, etc.)
refRef<LogoRevealSetHandle>—Imperative handle: play() and reset()
classNamestring—Passed to the outermost container

LogoRevealSetHandle

FieldTypeDefaultDescription
play() => void—Play the entrance from the start (works in every trigger mode; handy for a replay button)
reset() => void—Stop and return to the hidden pre-entrance state

How it works

  • Each variant splits duration into phases: bloom grows from scale 0.05 to 1.15 with the glow at its peak over the first 45%, then settles to 1 with the glow down to glow×0.3; stroke draws for the first 70% and deepens the colour over the last 30%; shine fades in over 40% and sweeps the highlight over the remaining 60%; lockup scales the mark 0.8→1 over 40%, then slides the wordmark out on a spring (stiffness 220, damping 26); morph switches the text at 35%, with new characters fading in from a blur.
  • The glow is two drop-shadow layers (radius R and 2R), so it follows the outline of the mark rather than drawing a box shadow.
  • The shine highlight is a white copy of the same content, moved under a diagonal mask-image band, so the highlight only shows inside the strokes. If children is an SVG with ids (gradients, clipPaths), the copy duplicates those ids; prefer path or an id-free SVG.
  • Timing and colour can be overridden with CSS variables: --wb-lrs-duration (duration, e.g. 1.6s), --wb-lrs-glow (glow colour) and --wb-lrs-stroke (the light colour during the stroke phase). A duration prop takes precedence.
  • With trigger="inView", an IntersectionObserver plays it once when 35% of it is visible and then stops observing; ref.play() replays in any mode.
  • The first render (server included) is the hidden pre-entrance state, matching the client, so there is no hydration mismatch.

Accessibility

  • The outer element is role="img" with an aria-label that defaults to the wordmark text and can be overridden with ariaLabel; every animated layer is aria-hidden, so morphing characters are not announced one by one
  • The animation never moves focus or captures the keyboard; the replay button is yours to provide, so use type="button" and a clear label
  • When the user has reduce motion enabled, every variant shows its final state directly (no glow pulse, morph shows the target text), and onComplete fires immediately when triggered