← MADLAB / library

MADLAB / TUTORIAL

Build Shiny Text from zero.

A complete, step-by-step breakdown of Shiny Text: from the static contract to a restrained light sweep across a label, responsive behavior, accessibility, and production tuning.

LEVEL / INTERMEDIATETIME / 2–3 HOURSSTACK / REACT + TYPESCRIPT + CSS

00 / Before you code

The mental model.

Shiny Text is a small system: stable structure, explicit state, and one visual rule.

The interaction should earn its cost. If a restrained light sweep across a label is removed, the interface should remain understandable.

01 / Build sequence

From blank file to interaction.

01

Define the component contract.

Write down the smallest public API for Shiny Text. Keep visual decisions in props so the a restrained light sweep across a label can be reused without rewriting the component.

tsx
// Local source: src/ts-tailwind/TextAnimations/ShinyText/ShinyText.tsx
// Start with the smallest visible version of ShinyText.

type ShinyTextProps = {
  className?: string;
  color?: string;
};

export function Example({ className, color = "#ff2a2a" }: ShinyTextProps) {
  return (
    <div className={className} style={{ color }}>
      {/* Add the Shiny Text behavior here. */}
    </div>
  );
}
02

Build the quiet static state first.

Render the readable fallback before adding motion. The component should still communicate its purpose when JavaScript is delayed or motion is reduced.

03

Normalize the input and measurements.

Clamp numbers, handle an empty value, and measure the real container instead of assuming the viewport. This removes most edge-case bugs before the animation starts.

04

Separate structure from motion.

Keep markup, state, and animation calculations in separate layers. For Shiny Text, the visual structure should remain stable while a restrained light sweep across a label changes over time.

05

Implement the one useful interaction.

Add the core rule only: a restrained light sweep across a label. Use one source of truth for the active value and keep pointer, scroll, or timer listeners passive where possible.

06

Use one animation loop with a clear exit.

Start requestAnimationFrame only when a value changes. Keep the frame id in a ref, interpolate toward the target, and stop when the difference is below a small threshold.

07

Make the layout responsive.

Test narrow mobile widths, wide desktop containers, and text wrapping. Prefer CSS dimensions and ResizeObserver over hard-coded pixels tied to one screenshot.

08

Add reduced-motion and interaction fallbacks.

Respect prefers-reduced-motion and keep a non-motion state. Keyboard focus, readable labels, and a useful static result matter more than a decorative effect.

09

Integrate it into a real section.

Place Shiny Text behind a real message, card, or control. Keep content above decorative layers, preserve the MADBAK palette, and avoid letting motion compete with the hierarchy.

10

Tune, profile, and ship the smallest good version.

Check the effect on a mid-range device, remove unnecessary listeners, and keep the first release focused. Once the behavior is useful, expose only the controls that future projects really need.

02 / Debug checklist

If it feels wrong.

  • The preview is blank: confirm the container has a real height and the client component mounted.
  • It jumps on resize: keep one measured source of truth and cancel stale animation frames.
  • It feels heavy: reduce work per frame, remove duplicate listeners, and stop the loop when idle.
  • It is inaccessible: keep a readable static state, visible focus, and a reduced-motion path.

MADLAB / source

Ready to adapt it?

Open local source ↗