Tawny
All components

Motion

Motion Primitives

WordRotate and ShimmerButton for headlines and CTAs.

components/ui/motion

Build with motion

Source

Copy the files below into your project.

packages/ui/src/components/word-rotate.tsx
'use client'

import { AnimatePresence, motion } from 'motion/react'
import { useEffect, useState } from 'react'
import { cn } from '../lib/utils'

interface WordRotateProps {
  words: string[]
  className?: string
  duration?: number
  motionProps?: React.ComponentProps<typeof motion.h1>
}

export function WordRotate({
  words,
  className,
  duration = 2800,
  motionProps,
}: WordRotateProps) {
  const [index, setIndex] = useState(0)

  useEffect(() => {
    const interval = setInterval(() => {
      setIndex((i) => (i + 1) % words.length)
    }, duration)
    return () => clearInterval(interval)
  }, [words.length, duration])

  return (
    <AnimatePresence mode="wait">
      <motion.span
        key={words[index]}
        className={cn('inline-block', className)}
        initial={{ opacity: 0, y: -20 }}
        animate={{ opacity: 1, y: 0 }}
        exit={{ opacity: 0, y: 20 }}
        transition={{ duration: 0.3, ease: 'easeOut' }}
        {...motionProps}
      >
        {words[index]}
      </motion.span>
    </AnimatePresence>
  )
}
packages/ui/src/components/shimmer-button.tsx
import { cn } from '../lib/utils'
import type { ButtonHTMLAttributes, ReactNode } from 'react'

interface ShimmerButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  children: ReactNode
  className?: string
  shimmerColor?: string
  shimmerSize?: string
  borderRadius?: string
  background?: string
}

export function ShimmerButton({
  children,
  className,
  shimmerColor = 'rgba(255,255,255,0.2)',
  shimmerSize = '0.05em',
  borderRadius = '100px',
  background = 'rgba(0,0,0,1)',
  ...props
}: ShimmerButtonProps) {
  return (
    <button
      style={
        {
          '--shimmer-color': shimmerColor,
          '--shimmer-size': shimmerSize,
          '--border-radius': borderRadius,
          '--background': background,
          borderRadius,
        } as React.CSSProperties
      }
      className={cn(
        'group relative inline-flex items-center justify-center gap-2',
        'cursor-pointer overflow-hidden whitespace-nowrap',
        'px-6 py-3 text-sm font-medium text-white',
        'bg-[var(--background)]',
        'transition-all duration-300',
        // shimmer sweep
        'before:absolute before:inset-0 before:-translate-x-full',
        'before:bg-gradient-to-r before:from-transparent before:via-[var(--shimmer-color)] before:to-transparent',
        'before:transition-transform before:duration-700',
        'hover:before:translate-x-full before:skew-x-[-20deg]',
        // border ring
        'ring-1 ring-white/15 hover:ring-white/25',
        className
      )}
      {...props}
    >
      {children}
    </button>
  )
}