Skip to content
Domphy

auroraText

A Text block/component from Magic UI — clean-room reimplemented for Domphy (see methodology). Call auroraText() with no arguments for a working demo, or edit the code below live.

Props

PropTypeDescription
childrenstringText content. Defaults to "Aurora Text".
colorsThemeColor[]Gradient color families the sweep cycles through. Defaults to four theme roles standing in for magic UI's pink/purple/blue/cyan palette.
speednumberSpeed multiplier — 2 sweeps twice as fast, 0.5 half as fast. Defaults to 1.
asAuroraTextTagWrapping element tag. Defaults to "span".
styleStyleObject
Implementation notes

Diagonal multi-color background-clip: text gradient that pans back and forth via CSS animation-direction: alternate (8s / speed multiplier, ease-in-out, infinite) -- matches the spec's own description of an alternating, easing ping-pong loop almost exactly, and is simpler/more correct than a hand-authored 3-stop keyframe. First color is repeated at the gradient's end so the pan never shows a seam, per the spec. Default 4-color pink/purple/blue/cyan palette is mapped to theme color-family roles (secondary, highlight, primary, info) instead of literal hex, since Domphy's doctor rules forbid raw hex/rgb colors on style props -- same documented tradeoff this package's existing animatedGradientText block already makes for its own default colors. Accessibility uses the exact sr-only-text + aria-hidden-decorative-gradient-copy structure the spec's own DOM sketch describes, reusing the same pattern already established elsewhere in this package (sidebarInDialog). Direct-source-diff fix (2026-07-05): Was a plain horizontal gradient pan — upstream's real @keyframes pans a 4-corner background-position path AND wobbles each glyph (±5° rotate, 0.9-1.1 scale). Replicated the real keyframe and glyph wobble.

Status: ported · Reference: Magic UI original

// magicui "Aurora Text" — clean-room reimplementation from the public
// behavior/visual spec only (no upstream source viewed or copied). Display
// text filled with a diagonal multi-color gradient that pans back and forth
// across the glyphs on an endless ease-in-out loop — the same
// `background-clip: text` panning technique this package's
// `animatedGradientText`/`animatedShinyText` use, but alternating direction
// each cycle (CSS `animation-direction: alternate`) instead of resetting
// abruptly, and repeating the first color at the gradient's end so the pan
// never shows a visible seam.
//
// The upstream spec's default four-color palette is literal hex
// (pink/magenta, purple, blue, cyan) — Domphy's doctor rules forbid raw
// hex/rgb colors on style props, so the palette is exposed as `ThemeColor`
// roles instead, defaulting to `["secondary", "highlight", "primary", "info"]`
// (this theme's closest four distinct built-in families to that hue spread).
// Same tradeoff `animatedGradientText` documents for its own default colors.
//
// Accessibility: the visible gradient-filled copy is `aria-hidden`, paired
// with a visually-hidden duplicate carrying the plain text — the same
// sr-only-text + aria-hidden-decoration pattern used by this package's
// `sidebarInDialog` block, generalized here to a public component per the
// spec's own DOM sketch.

import type { DomphyElement, Listener, StyleObject } from "@domphy/core";
import { hashString } from "@domphy/core";
import { type ThemeColor, themeColor } from "@domphy/theme";

export type AuroraTextTag =
  | "span"
  | "div"
  | "h1"
  | "h2"
  | "h3"
  | "h4"
  | "h5"
  | "h6";

export interface AuroraTextProps {
  /** Text content. Defaults to `"Aurora Text"`. */
  children?: string;
  /** Gradient color families the sweep cycles through. Defaults to four theme
   * roles standing in for magic UI's pink/purple/blue/cyan palette. */
  colors?: ThemeColor[];
  /** Speed multiplier — `2` sweeps twice as fast, `0.5` half as fast. Defaults to `1`. */
  speed?: number;
  /** Wrapping element tag. Defaults to `"span"`. */
  as?: AuroraTextTag;
  style?: StyleObject;
}

const SR_ONLY_STYLE = {
  position: "absolute",
  width: "1px",
  height: "1px",
  padding: "0",
  margin: "-1px",
  overflow: "hidden",
  clip: "rect(0, 0, 0, 0)",
  whiteSpace: "nowrap",
  border: "0",
} as const;

let auroraTextInstanceCounter = 0;

/**
 * Display text filled with a continuously panning multicolor gradient
 * (an "aurora" sweep), alternating direction on an endless ease-in-out loop.
 * Call with no arguments for a working demo.
 */
function auroraText(props: AuroraTextProps = {}): DomphyElement {
  const text = props.children ?? "Aurora Text";
  const colors =
    props.colors ??
    (["secondary", "highlight", "primary", "info"] as ThemeColor[]);
  const speed = Math.max(props.speed ?? 1, 0.01);
  const wrapperTag = props.as ?? "span";

  const instanceId = ++auroraTextInstanceCounter;
  const durationSeconds = Math.max(0.5, 10 / speed);

  const gradientStops = (listener: Listener): string => {
    const roles = colors.length > 0 ? colors : (["primary"] as ThemeColor[]);
    // Repeat the first stop at the end so the alternating pan never shows a seam.
    const stops = [...roles, roles[0]];
    return stops
      .map((role) => themeColor(listener, "shift-8", role))
      .join(", ");
  };

  const animationName = `aurora-text-sweep-${hashString(JSON.stringify({ instanceId, colors }))}`;
  // Matches upstream's `@keyframes aurora`: the gradient pans while the glyphs
  // themselves wobble with a subtle rotate/scale — that shimmer is what makes
  // the effect read as an "aurora" rather than a plain horizontal gradient pan.
  const keyframes = {
    "0%": {
      backgroundPosition: "0% 50%",
      transform: "rotate(-5deg) scale(0.9)",
    },
    "25%": {
      backgroundPosition: "50% 100%",
      transform: "rotate(5deg) scale(1.1)",
    },
    "50%": {
      backgroundPosition: "100% 50%",
      transform: "rotate(-3deg) scale(0.95)",
    },
    "75%": {
      backgroundPosition: "50% 0%",
      transform: "rotate(3deg) scale(1.05)",
    },
    "100%": {
      backgroundPosition: "0% 50%",
      transform: "rotate(-5deg) scale(0.9)",
    },
  };

  const auroraSpan: DomphyElement<"span"> = {
    span: text,
    ariaHidden: "true",
    _key: "aurora-fill",
    style: {
      backgroundImage: (listener: Listener) =>
        `linear-gradient(135deg, ${gradientStops(listener)})`,
      backgroundSize: "200% auto",
      backgroundClip: "text",
      WebkitBackgroundClip: "text",
      color: "transparent",
      WebkitTextFillColor: "transparent",
      animation: `${animationName} ${durationSeconds}s ease-in-out infinite alternate`,
      [`@keyframes ${animationName}`]: keyframes,
    } as StyleObject,
  };

  const outer = {
    [wrapperTag]: [
      { span: text, _key: "sr-only-text", style: SR_ONLY_STYLE },
      auroraSpan,
    ],
    style: {
      position: "relative",
      display: "inline-block",
      ...(props.style ?? {}),
    } as StyleObject,
  } as unknown as DomphyElement;

  return outer;
}

export { auroraText };

← Back to Magic UI catalog