bundui svg logo
Bundui

Floating Button

A floating button is a common UI component used in user interfaces. It typically appears as a button "floating" over other content, often positioned in a corner of the screen. This button allows users to quickly perform a key action.

import { cn } from "@/lib/utils";
import {
  DribbbleIcon,
  FacebookIcon,
  LinkedinIcon,
  PlusIcon,
} from "lucide-react";

import {
  FloatingButton,
  FloatingButtonItem,
} from "@/components/core/floating-button";
import {Button} from "@/components/ui/button";

export default function FloatingButtonExample() {
  const items = [
    {
      id: "facebook",
      icon: <FacebookIcon />,
      bgColor: "bg-[#1877f2]",
    },
    {
      id: "dribbble",
      icon: <DribbbleIcon />,
      bgColor: "bg-[#ea4c89]",
    },
    {
      id: "linkedin",
      icon: <LinkedinIcon />,
      bgColor: "bg-[#0a66c2]",
    },
  ];

  return (
    <FloatingButton
      triggerContent={
        <Button size="icon" className="size-12 rounded-full">
          <PlusIcon className="size-5" />
        </Button>
      }
    >
      {items.map((item) => (
        <FloatingButtonItem key={item.id}>
          <Button size="icon" className={cn("size-12 rounded-full", item.bgColor)}>
            {item.icon}
          </Button>
        </FloatingButtonItem>
      ))}
    </FloatingButton>
  );
}

Installation

Install the following dependencies:
npm install motion usehooks-ts
Copy and paste the following code into your project:
"use client";

import React, { ReactNode } from "react";
import { AnimatePresence, motion } from "motion/react";
import { useOnClickOutside } from "usehooks-ts";

type FloatingButtonProps = {
  className?: string;
  children: ReactNode;
  triggerContent: ReactNode;
};

type FloatingButtonItemProps = {
  children: ReactNode;
};

const list = {
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.1,
      staggerDirection: -1,
    },
  },
  hidden: {
    opacity: 0,
    transition: {
      when: "afterChildren",
      staggerChildren: 0.1,
    },
  },
};

const item = {
  visible: { opacity: 1, y: 0 },
  hidden: { opacity: 0, y: 5 },
};

const btn = {
  visible: { rotate: "45deg" },
  hidden: { rotate: 0 },
};

function FloatingButton({ children, triggerContent }: FloatingButtonProps) {
  const ref = React.useRef<HTMLDivElement | null>(null);
  const [isOpen, setIsOpen] = React.useState(false);

  useOnClickOutside(ref as React.RefObject<HTMLDivElement>, () =>
    setIsOpen(false),
  );

  return (
    <div className="flex flex-col items-center relative">
      <AnimatePresence>
        <motion.ul
          key="list"
          className="flex flex-col items-center absolute bottom-14 gap-2"
          initial="hidden"
          animate={isOpen ? "visible" : "hidden"}
          variants={list}
        >
          {children}
        </motion.ul>
        <motion.div
          key="button"
          variants={btn}
          animate={isOpen ? "visible" : "hidden"}
          ref={ref}
          onClick={() => setIsOpen(!isOpen)}
          className="cursor-pointer"
        >
          {triggerContent}
        </motion.div>
      </AnimatePresence>
    </div>
  );
}

function FloatingButtonItem({ children }: FloatingButtonItemProps) {
  return <motion.li variants={item}>{children}</motion.li>;
}

export { FloatingButton, FloatingButtonItem };
Update the import paths to match your project setup.

Usage

import { cn } from "@/lib/utils";
import {
  DribbbleIcon,
  FacebookIcon,
  LinkedinIcon,
  PlusIcon,
} from "lucide-react";

import {
  FloatingButton,
  FloatingButtonItem,
} from "@/components/core/floating-button";
import {Button} from "@/components/ui/button";

export default function FloatingButtonExample() {
  const items = [
    {
      id: "facebook",
      icon: <FacebookIcon />,
      bgColor: "bg-[#1877f2]",
    },
    {
      id: "dribbble",
      icon: <DribbbleIcon />,
      bgColor: "bg-[#ea4c89]",
    },
    {
      id: "linkedin",
      icon: <LinkedinIcon />,
      bgColor: "bg-[#0a66c2]",
    },
  ];

  return (
    <FloatingButton
      triggerContent={
        <Button size="icon" className="size-12 rounded-full">
          <PlusIcon className="size-5" />
        </Button>
      }
    >
      {items.map((item) => (
        <FloatingButtonItem key={item.id}>
          <Button size="icon" className={cn("size-12 rounded-full", item.bgColor)}>
            {item.icon}
          </Button>
        </FloatingButtonItem>
      ))}
    </FloatingButton>
  );
}

Props

FloatingButton

PropTypeDefault
className?
string
-
triggerContent?
ReactNode
-
children
ReactNode
-

FloatingButtonItem

PropTypeDefault
children
ReactNode
-