Docs
FamilyButton

FamilyButton

Animated expansion inspired by Family

Installation

Copy and paste the following code into your project.

"use client"
 
import { FC, ReactNode, useState } from "react"
import { PlusIcon, XIcon } from "lucide-react"
import { motion } from "motion/react"
 
import { cn } from "@/lib/utils"
 
const CONTAINER_SIZE = 200
 
interface FamilyButtonProps {
  children: React.ReactNode
}
 
const FamilyButton: React.FC<FamilyButtonProps> = ({ children }) => {
  const [isExpanded, setIsExpanded] = useState(false)
  const toggleExpand = () => setIsExpanded(!isExpanded)
 
  return (
    <div
      className={cn(
        "rounded-[24px] border border-black/10  shadow-sm dark:border-yellow-400/20",
        "bg-gradient-to-b  from-neutral-900 to-black",
        isExpanded
          ? "w-[204px] bg-gradient-to-b dark:from-stone-900 dark:to-neutral-900/80"
          : "dark:from-neutral-900 dark:to-stone-950 bg-gradient-to-b"
      )}
    >
      <div className="rounded-[23px] border   border-black/10 ">
        <div className="rounded-[22px] border  dark:border-stone-800 border-white/50 ">
          <div className="rounded-[21px] border    border-neutral-950/20   flex items-center justify-center ">
            <FamilyButtonContainer
              isExpanded={isExpanded}
              toggleExpand={toggleExpand}
            >
              {isExpanded ? (
                <motion.div
                  initial={{ opacity: 0 }}
                  animate={{
                    opacity: 1,
                    transition: {
                      delay: 0.3,
                      duration: 0.4,
                      ease: "easeOut",
                    },
                  }}
                >
                  {children}
                </motion.div>
              ) : null}
            </FamilyButtonContainer>
          </div>
        </div>
      </div>
    </div>
  )
}
 
// A container that wraps content and handles animations
interface FamilyButtonContainerProps {
  isExpanded: boolean
  toggleExpand: () => void
  children: ReactNode
}
 
const FamilyButtonContainer: FC<FamilyButtonContainerProps> = ({
  isExpanded,
  toggleExpand,
  children,
}) => {
  return (
    <motion.div
      className={cn(
        "relative   border-white/10 border shadow-lg flex flex-col space-y-1  items-center  text-white  cursor-pointer z-10",
        !isExpanded
          ? "bg-gradient-to-b from-neutral-900 to-stone-900 dark:from-stone-700 dark:to-neutral-800/80"
          : ""
      )}
      layoutRoot
      layout
      initial={{ borderRadius: 21, width: "4rem", height: "4rem" }}
      animate={
        isExpanded
          ? {
              borderRadius: 20,
              width: CONTAINER_SIZE,
              height: CONTAINER_SIZE + 50,
 
              transition: {
                type: "spring",
                damping: 25,
                stiffness: 400,
                when: "beforeChildren",
              },
            }
          : {
              borderRadius: 21,
              width: "4rem",
              height: "4rem",
            }
      }
    >
      {children}
 
      <motion.div
        className="absolute  "
        initial={{ x: "-50%" }}
        animate={{
          x: isExpanded ? "0%" : "-50%",
          transition: {
            type: "tween",
            ease: "easeOut",
            duration: 0.3,
          },
        }}
        style={{
          left: isExpanded ? "" : "50%",
          bottom: 6,
        }}
      >
        {isExpanded ? (
          <motion.div
            className="p-[10px] group bg-neutral-800/50 dark:bg-black/50 border border-cyan-100/30 hover:border-neutral-200 text-orange-50 rounded-full shadow-2xl transition-colors duration-300 "
            onClick={toggleExpand}
            layoutId="expand-toggle"
            initial={false}
            animate={{
              rotate: -360,
              transition: {
                duration: 0.4,
              },
            }}
          >
            <XIcon
              className={cn(
                "h-7 w-7 text-cyan-100/30 dark:text-neutral-400/80 group-hover:text-neutral-500 transition-colors duration-200 "
              )}
            />
          </motion.div>
        ) : (
          <motion.div
            className={cn(
              "p-[10px] group bg-neutral-200 dark:bg-cyan-500/90 text-cyan-50 border border-cyan-100/10  shadow-2xl transition-colors duration-200"
            )}
            style={{ borderRadius: 24 }}
            onClick={toggleExpand}
            layoutId="expand-toggle"
            initial={{ rotate: 180 }}
            animate={{
              rotate: -180,
              transition: {
                duration: 0.4,
              },
            }}
          >
            <PlusIcon className="h-7 w-7 text-black dark:text-neutral-900" />
          </motion.div>
        )}
      </motion.div>
    </motion.div>
  )
}
 
export { FamilyButton }
export FamilyButton

Update the import paths to match your project setup.

Usage

import { FamilyButton } from "@/components/ui/family-button"
<FamilyButton>
  <FamilyButtonHeader className="flex flex-col gap-4 justify-center items-center  ">
    <div className="p-3 bg-neutral-950 rounded-full">
      <Mail className="h-4 w-4 stroke-neutral-200" />
    </div>
  </FamilyButtonHeader>
 
  <FamilyButtonContent className=" w-48 ">
    <p>
      Lorem ipsum dolor sit, amet consectetur adipisicing elit. Dolorum eos quia
      incidunt perspiciatis, ut, deleniti fugit a aliquam sequi, voluptatum
      pariatur quaerat. Temporibus sed facere at, voluptas dolorem officiis
      incidunt!
    </p>
  </FamilyButtonContent>
  <TextureSeparator />
 
  <div>
    <div className="dark:bg-neutral-800 bg-stone-100 pt-px rounded-b-[20px] overflow-hidden ">
      <div className="flex flex-col items-center justify-center">
        <div className="py-2 px-2">
          <p className="font-light dark:text-white text-black">
            Texture <span className="font-medium tracking-wide">card</span>
          </p>
        </div>
      </div>
    </div>
  </div>
</FamilyButton>