Velocity UI
Loading…
Menu

Magnetic Tilt Button

A 3D magnetic tilt button with specular highlight, etched light beam, and depth layers. Designed for actions where motion and clarity both matter.

Installation

Add this component to your project using the CLI:

terminal
npx vui-registry-cli-v1 add premium-magnetic-button

Source Code

premium-magnetic-button.tsx
'use client'

import { motion, useMotionValue, useSpring, useTransform } from 'framer-motion'
import { cn } from '@/lib/utils'
import React, { useRef } from 'react'

type PremiumMagneticButtonProps = Omit<
  React.ComponentPropsWithoutRef<typeof motion.button>,
  'children'
> & {
  children?: React.ReactNode
  className?: string
}

export function PremiumMagneticButton({ className, children, ...props }: PremiumMagneticButtonProps) {
  const ref = useRef<HTMLButtonElement>(null)

  const x = useMotionValue(0)
  const y = useMotionValue(0)

  const mouseXSpring = useSpring(x, { stiffness: 150, damping: 20 })
  const mouseYSpring = useSpring(y, { stiffness: 150, damping: 20 })

  const rotateX = useTransform(mouseYSpring, [-0.5, 0.5], ['10deg', '-10deg'])
  const rotateY = useTransform(mouseXSpring, [-0.5, 0.5], ['-10deg', '10deg'])

  const handleMouseMove = (e: React.MouseEvent<HTMLButtonElement>) => {
    if (!ref.current) return
    const rect = ref.current.getBoundingClientRect()
    const width = rect.width
    const height = rect.height
    const mouseX = e.clientX - rect.left
    const mouseY = e.clientY - rect.top
    const xPct = mouseX / width - 0.5
    const yPct = mouseY / height - 0.5
    x.set(xPct)
    y.set(yPct)
  }

  const handleMouseLeave = () => {
    x.set(0)
    y.set(0)
  }

  return (
    <div className="perspective-[1000px]">
      <motion.button
        ref={ref}
        onMouseMove={handleMouseMove}
        onMouseLeave={handleMouseLeave}
        style={{ rotateX, rotateY, transformStyle: 'preserve-3d' }}
        whileTap={{ scale: 0.95 }}
        className={cn(
          'group relative inline-flex items-center justify-center px-12 py-5 rounded-2xl',
          'bg-neutral-950 text-white transition-colors duration-500',
          'border border-white/10 hover:border-white/20 shadow-2xl',
          className,
        )}
        {...props}
      >
        <motion.div
          style={{
            translateX: useTransform(mouseXSpring, [-0.5, 0.5], ['-20px', '20px']),
            translateY: useTransform(mouseYSpring, [-0.5, 0.5], ['-10px', '10px']),
          }}
          className="absolute inset-0 z-10 pointer-events-none opacity-0 group-hover:opacity-100 transition-opacity duration-500 bg-[radial-gradient(circle_at_center,rgba(255,255,255,0.08)_0%,transparent_70%)] blur-xl"
        />

        <div className="absolute inset-0 z-0 overflow-hidden rounded-2xl">
          <motion.div
            animate={{ x: ['-100%', '200%'] }}
            transition={{ duration: 3, repeat: Infinity, ease: 'linear', repeatDelay: 1 }}
            className="absolute top-0 h-[1px] w-full bg-gradient-to-r from-transparent via-white/20 to-transparent"
          />
        </div>

        <span
          style={{ transform: 'translateZ(30px)' }}
          className="relative z-20 flex items-center gap-4 text-[11px] font-black uppercase tracking-[0.4em] text-neutral-400 group-hover:text-white transition-colors duration-500"
        >
          {children || 'Secure Access'}
          <div className="relative flex h-2 w-2">
            <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-white/20 opacity-75"></span>
            <span className="relative inline-flex rounded-full h-2 w-2 bg-white/40"></span>
          </div>
        </span>

        <div className="absolute inset-x-6 bottom-0 h-10 bg-gradient-to-t from-white/6 to-transparent blur-2xl pointer-events-none" />
      </motion.button>
    </div>
  )
}

export default PremiumMagneticButton

Dependencies

  • framer-motion: latest

Props

Component property reference.

NameTypeDefaultDescription
classNamestring-Custom className for the button.
childrenReactNode-Button label; defaults to 'Secure Access'.
motionPropsReact.ComponentPropsWithoutRef<typeof motion.button>-All Framer Motion button props are supported (whileTap, onClick, etc.).
Context Worth Keeping In Orbit

Most components here are inspired by outstanding libraries and creators in the ecosystem. I don’t claim to be the original author — this is my space for learning, rebuilding, and understanding great work at a deeper level.

I’m still a student of the craft, constantly studying the best and translating what I learn through my own perspective. Every piece reflects curiosity, respect for the community, and small creative touches that feel true to me.

I’ve done my best to credit inspirations properly. If anything is missing or inaccurate, I truly appreciate a message so it can be corrected with care.