Components / Shiny Wrap

Shiny Wrap

A simple shimmering highlight wrapper that adds a moving light streak over any content. Great for featured cards, album covers, or hero images.

Preview

The shine effect animates across the cards in a loop. You can also enable hover-only mode.

Shiny wrapper
Shiny wrapper
Shiny wrapper

Usage

tsx
import ShinyWrapper from "@/components/shiny-wrap/shiny-wrapper"

export function Example() {
  return (
    <div className="flex gap-4">
      <ShinyWrapper className="rounded-xl">
        <figure className="h-[320px] w-[220px] overflow-hidden rounded-xl">
          <img
            src="/images/photo1.jpg"
            alt="Shiny wrapper"
            className="w-full h-full object-cover"
          />
        </figure>
      </ShinyWrapper>
    </div>
  )
}

Props

PropTypeDefaultDescription
childrenReactNoderequiredContent to wrap with the shine effect
animationSpeednumber1500Duration of the shine animation in milliseconds
loopbooleantrueWhether the shine animation should loop
loopDelaynumber200Delay between shine loops (ms) when looping
hoverOnlybooleanfalseOnly animate when hovered
shinyStyle'gradient' | 'solid''gradient'Style of the shine bar (gradient vs solid)
shinyWidthstring'30%'Width of the shine bar (CSS value)
classNamestring""Additional classes for the wrapper

Source Code

tsx
'use client'
import { useState, ReactNode } from 'react'

interface ShinyWrapperProps {
    children: ReactNode
    animationSpeed?: number
    loop?: boolean
    loopDelay?: number
    hoverOnly?: boolean
    shinyStyle?: 'gradient' | 'solid'
    className?: string
    shinyWidth?: string
}

const ShinyWrapper = ({
    children,
    animationSpeed = 1500,
    loop = true,
    loopDelay = 200,
    hoverOnly = false,
    shinyStyle = 'gradient',
    className = '',
    shinyWidth = '30%'
}: ShinyWrapperProps) => {
    const [isHovered, setIsHovered] = useState(false)

    const totalDuration = loop ? animationSpeed + loopDelay : animationSpeed
    const shouldAnimate = hoverOnly ? isHovered : true

    const animationStyle = {
        animation: shouldAnimate
            ? `shine ${totalDuration}ms linear ${loop ? 'infinite' : '1'}`
            : 'none'
    }

    const shinyClass = {
        gradient: 'bg-gradient-to-r from-transparent via-white/10 to-transparent',
        solid: 'bg-white/10'
    }

    return (
        <div
            className={`relative overflow-hidden ${className}`}
            onMouseEnter={hoverOnly ? () => setIsHovered(true) : undefined}
            onMouseLeave={hoverOnly ? () => setIsHovered(false) : undefined}
        >
            <div
                className={`absolute z-1 top-0 -left-1/2 h-full -skew-x-14 ${shinyClass[shinyStyle]}`}
                style={{ ...animationStyle, width: shinyWidth }}
            />
            <div className='relative'>{children}</div>
        </div>
    )
}

export default ShinyWrapper