DocumentationSocial Flip Button
Social Flip Button
A social media button group that flips letters to reveal icons on hover, featuring animated border lines.
Install using CLI
npx shadcn@latest add "https://vengeance-ui.vercel.app/r/social-flip-button.json"Install Manually
1
Install dependencies
npm install framer-motion clsx tailwind-merge react-icons
2
Add util file
lib/utils.ts
import { ClassValue, clsx } from "clsx";import { twMerge } from "tailwind-merge";export function cn(...inputs: ClassValue[]) {return twMerge(clsx(inputs));}
3
Add styles to global CSS
Add the following styles to your app/globals.css file
/* Glass Component Borders */.glass-border {border: 1px solid rgba(0, 0, 0, 0.15);}.dark .glass-border {border: 1px solid rgba(255, 255, 255, 0.2);}
4
Copy the source code
Copy the code below and paste it into components/ui/social-flip-button.tsx
"use client";import { motion, AnimatePresence } from "framer-motion";import React, { useState } from "react";import { cn } from "@/lib/utils";import {FaGithub,FaTwitter,FaFacebook,FaInstagram,FaLinkedin,FaEnvelope,FaDiscord,} from "react-icons/fa";export interface SocialItem {letter: string;icon: React.ReactNode;label: string;href?: string;onClick?: () => void;}interface SocialFlipButtonProps {items?: SocialItem[];className?: string;itemClassName?: string;frontClassName?: string;backClassName?: string;}const defaultItems: SocialItem[] = [{ letter: "C", icon: <FaGithub />, label: "Github", href: "#" },{ letter: "O", icon: <FaTwitter />, label: "Twitter", href: "#" },{ letter: "N", icon: <FaLinkedin />, label: "LinkedIn", href: "#" },{ letter: "T", icon: <FaInstagram />, label: "Instagram", href: "#" },{ letter: "A", icon: <FaFacebook />, label: "Facebook", href: "#" },{ letter: "C", icon: <FaEnvelope />, label: "Email", href: "#" },{ letter: "T", icon: <FaDiscord />, label: "Discord", href: "#" },];const SocialFlipNode = ({item,index,isHovered,setTooltipIndex,tooltipIndex,itemClassName,frontClassName,backClassName,}: {item: SocialItem;index: number;isHovered: boolean;setTooltipIndex: (val: number | null) => void;tooltipIndex: number | null;itemClassName?: string;frontClassName?: string;backClassName?: string;}) => {const Wrapper = item.href ? "a" : "div";const wrapperProps = item.href? { href: item.href, target: "_blank", rel: "noopener noreferrer" }: { onClick: item.onClick };return (<Wrapper{...wrapperProps}className={cn("relative h-10 w-10 cursor-pointer", itemClassName)}style={{ perspective: "1000px" }}onMouseEnter={() => setTooltipIndex(index)}onMouseLeave={() => setTooltipIndex(null)}><AnimatePresence>{isHovered && tooltipIndex === index && (<motion.divinitial={{ opacity: 0, y: 10, scale: 0.8, x: "-50%" }}animate={{ opacity: 1, y: -50, scale: 1, x: "-50%" }}exit={{ opacity: 0, y: 10, scale: 0.8, x: "-50%" }}transition={{ duration: 0.2, ease: "easeOut" }}className="absolute left-1/2 z-50 whitespace-nowrap rounded-lg bg-neutral-900 px-3 py-1.5 text-xs font-semibold text-white shadow-xl dark:bg-white dark:text-neutral-900">{item.label}{/* Arrow */}<div className="absolute -bottom-1 left-1/2 -translate-x-1/2 h-2 w-2 rotate-45 bg-neutral-900 dark:bg-white" /></motion.div>)}</AnimatePresence><motion.divclassName="relative h-full w-full"initial={false}animate={{ rotateY: isHovered ? 180 : 0 }}transition={{duration: 0.8,type: "spring",stiffness: 120,damping: 15,delay: index * 0.08,}}style={{ transformStyle: "preserve-3d" }}>{/* Front - Letter */}<divclassName={cn("absolute inset-0 flex items-center justify-center rounded-lg bg-neutral-100 text-lg font-bold text-neutral-800 shadow-sm dark:bg-neutral-900 dark:text-neutral-200",frontClassName)}style={{ backfaceVisibility: "hidden" }}>{item.letter}</div>{/* Back - Icon */}<divclassName={cn("absolute inset-0 flex items-center justify-center rounded-lg bg-black text-lg text-white dark:bg-white dark:text-black",backClassName)}style={{backfaceVisibility: "hidden",transform: "rotateY(180deg)",}}>{item.icon}</div></motion.div></Wrapper>);};export default function SocialFlipButton({items = defaultItems,className,itemClassName,frontClassName,backClassName,}: SocialFlipButtonProps) {const [isHovered, setIsHovered] = useState(false);const [tooltipIndex, setTooltipIndex] = useState<number | null>(null);const [isDark, setIsDark] = useState(false);React.useEffect(() => {const checkTheme = () => {setIsDark(document.documentElement.classList.contains('dark'));};checkTheme();const observer = new MutationObserver(checkTheme);observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });return () => observer.disconnect();}, []);return (<div className={cn("flex items-center justify-center gap-4 p-4", className)}><divclassName="group relative flex items-center justify-center gap-2 rounded-2xl glass-border bg-white p-4 shadow-sm dark:bg-black"onMouseEnter={() => setIsHovered(true)}onMouseLeave={() => {setIsHovered(false);setTooltipIndex(null);}}>{/* Border Lines Container - Clipped */}<div className="absolute -inset-[1px] overflow-hidden rounded-2xl pointer-events-none">{/* Animated Top Border Line */}<motion.divclassName="absolute top-0 left-0 h-[1px] w-full bg-gradient-to-r from-transparent via-black/50 to-transparent dark:via-white/50"animate={{ x: ["-100%", "100%"] }}transition={{duration: 2.5,repeat: Infinity,ease: "linear",}}/>{/* Animated Bottom Border Line */}<motion.divclassName="absolute bottom-0 left-0 h-[1px] w-full bg-gradient-to-r from-transparent via-black/50 to-transparent dark:via-white/50"animate={{ x: ["100%", "-100%"] }}transition={{duration: 2.5,repeat: Infinity,ease: "linear",}}/></div>{items.map((item, index) => (<SocialFlipNodekey={index}item={item}index={index}isHovered={isHovered}setTooltipIndex={setTooltipIndex}tooltipIndex={tooltipIndex}itemClassName={itemClassName}frontClassName={frontClassName}backClassName={backClassName}/>})};</div></div>);}
Usage
1import SocialFlipButton from "@/components/ui/social-flip-button"23export function SocialFlipButtonDemo() {4return (5 <div className="flex items-center justify-center h-[300px] w-full">6 <SocialFlipButton />7 </div>8);9}
Props
| Prop Name | Type | Default | Description |
|---|---|---|---|
| items | SocialItem[] | defaultItems | Array of social items with letter, icon, label, and href/onClick. |
| className | string | - | Class for the outer wrapper. |
| itemClassName | string | - | Class for the individual button items. |
| frontClassName | string | - | Class for the front face (content side). |
| backClassName | string | - | Class for the back face (icon side). |