Componentform1.12 KB gzipZero dependencies

Fab

Floating action button fixed in corner with hover growth, ripple press, and tooltip label on hover.

Preview

Live Preview

Installation

Tokens guide →

Add to your project via CLI (zero dependencies, code you own):

npx bigbullui add fab

Or install the full npm package:

npm install bigbullui
import { Fab } from "@/components/ui/fab"

<Fab label="Add" position="bottom-right" onClick={() => {}} />

Usage guidance

Use Fab when a ticket stub surface needs Floating action button fixed in corner with hover growth, ripple press, and tooltip label on hover. Keep props minimal and prefer composition with sibling stubs.

Avoid Fab for long form flows or dense data grids where a dedicated layout block fits better.

Source code

fab.tsx

Zero dependencies (only React + utils). Copy and paste directly into your project:

"use client";

import * as React from "react";
import { cn } from "./lib/utils";

export type FabPosition = "bottom-right" | "bottom-left" | "top-right" | "top-left";

export type FabProps = {
  label: string;
  icon?: string;
  position?: FabPosition;
  onClick: () => void;
};

const positionClasses: Record<FabPosition, string> = {
  "bottom-right": "bottom-4 right-4",
  "bottom-left": "bottom-4 left-4",
  "top-right": "top-4 right-4",
  "top-left": "top-4 left-4",
};

export function Fab({ label, icon: iconProp, position = "bottom-right", onClick }: FabProps) {
  const [open, setOpen] = React.useState(false);

  React.useEffect(() => {
    const handleClick = () => setOpen((prev) => !prev);
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Escape") setOpen(false);
    };
    document.addEventListener("keydown", handleKeyDown);
    return () => document.removeEventListener("keydown", handleKeyDown);
  }, []);

  return (
    <div>
      <style>{`
        @keyframes fabRipple {
          0% { opacity: 1; transform: scale(0); }
          100% { opacity: 0; transform: scale(1.5); }
        }
      `}</style>

      <div
        className={cn(
          "fixed ",
          positionClasses[position],
          " -translate-y-1/2 -translate-x-1/2",
          "z-50",
          "flex items-center justify-center w-14 h-14 rounded-full bg-accent text-accent-foreground shadow-lg hover:-translate-y-0.5 hover:shadow-xl transition-transform",
          "active:scale-[0.95] transition-transform",
          "motion-reduce:animate-none",
          "motion-reduce:transition-none",
        )}
        onClick={() => { onClick(); }}
        onMouseDown={() => setOpen(true)}
        aria-label={label}
        role="button"
        tabIndex={0}
      >
        {iconProp ? (
          <span className="w-6 h-6">{iconProp}</span>
        ) : (
          <>
            <svg
              className="w-5 h-5"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              strokeWidth={2}
              strokeLinecap="round"
              strokeLinejoin="round"
            >
              <polygon points="13 2 3 14 12 14 11 22 21 10 14 13"></polygon>
            </svg>
          </>
        )}
      </div>

      {open && (
        <div
          className={cn(
            "fixed inset-0 z-40 bg-black/40 backdrop-blur-sm",
            "motion-reduce:animate-none",
          )}
        >
          <div
            className={cn(
              "fixed inset-0 z-50 bg-black/80 rounded-full w-20 h-20 scale-100 opacity-0 transition-all duration-300 ease-out",
              "motion-reduce:animate-none",
            )}
          >
            <span className="w-8 h-8 rounded-full bg-accent text-accent-foreground flex items-center justify-center text-xs uppercase tracking-wider">
              {label.substring(0, 3).toUpperCase()}
            </span>
          </div>
        </div>
      )}
    </div>
  );
}

Props

PropTypeDescription
labelstringTooltip label on hover.
iconstringOptional custom SVG icon component or text.
position"bottom-right" | "bottom-left" | "top-right" | "top-left"Corner position.
onClick() => voidOn click handler.