Fiber UI LogoFiberUI

Button

The button component

Basic Usage

import { Button } from "@repo/ui/components/button";
import { Info } from "lucide-react";

/* BASIC USAGE EXAMPLES */
export const Example1 = () => {
    return (
        <div className="flex flex-wrap items-center justify-center gap-4">
            <Button>Default</Button>
            <Button variant={"outline"}>Outline</Button>
            <Button size={"icon"} variant={"outline"}>
                <Info />
            </Button>
        </div>
    );
};

Sizes

import { Button } from "@repo/ui/components/button";
import { BusIcon, Info, MenuSquareIcon } from "lucide-react";

export const Example2 = () => {
    return (
        <div>
            <div className="flex flex-wrap items-center justify-center gap-4">
                <Button size={"sm"} variant={"outline"}>
                    Small
                </Button>
                <Button size={"default"} variant={"outline"}>
                    Default
                </Button>
                <Button size={"lg"} variant={"outline"}>
                    Large
                </Button>
            </div>
            <div className="mt-5 flex flex-wrap items-center justify-center gap-4">
                <Button size={"icon-sm"} variant={"outline"}>
                    <Info />
                </Button>
                <Button size={"icon"} variant={"outline"}>
                    <MenuSquareIcon />
                </Button>
                <Button size={"icon-lg"} variant={"outline"}>
                    <BusIcon />
                </Button>
            </div>
        </div>
    );
};

Variants

import { Button } from "@repo/ui/components/button";

export const Example3 = () => {
    return (
        <div>
            <div className="flex flex-wrap items-center justify-center gap-5 md:gap-4">
                <Button> Default </Button>
                <Button variant={"gradient"}> Gradient </Button>
                <Button variant={"outline"}> Outline </Button>
                <Button variant={"secondary"}> Secondary </Button>
                <Button variant={"ghost"}> Ghost </Button>
                <Button variant={"destructive"}> Destructive </Button>
                <Button variant={"link"}> Link </Button>
            </div>
            <div className="mt-5 flex flex-wrap items-center justify-center gap-5 md:gap-4">
                <Button variant={"adobe"}> Adobe </Button>
                <Button variant={"instagram"}> Instagram </Button>
            </div>
        </div>
    );
};

Component Code

"use client";

import { forwardRef, useRef } from "react";
import { AriaButtonProps, useButton } from "react-aria";
import { cn } from "@repo/ui/lib/utils";

import { cva, type VariantProps } from "class-variance-authority";

export const buttonVariants = cva(
    "inline-flex items-center transition-colors font-medium cursor-pointer justify-center gap-2 whitespace-nowrap rounded-lg text-sm  transition-all disabled:pointer-events-none disabled:opacity-50 " +
        " [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]" +
        " aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
    {
        variants: {
            variant: {
                default:
                    "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90 focus-visible:ring-primary/20 dark:focus-visible:ring-primary/40",
                gradient:
                    "text-white dark:text-white from-indigo-600 to-purple-600 bg-gradient-to-br hover:from-purple-600 hover:to-indigo-600 text-primary-foreground shadow-xs hover:bg-primary/90",
                destructive:
                    "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
                outline:
                    "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
                secondary:
                    "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
                ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
                link: "text-primary underline-offset-4 hover:underline",

                adobe:
                    "bg-[#3B63FB] text-white font-semibold rounded-full shadow-sm hover:bg-[#274dea] " +
                    " active:bg-[#274dea] focus-visible:ring-1 focus-visible:ring-[#274dea]/40 " +
                    " focus-visible:ring-offset-0",

                instagram:
                    " text-white font-semibold rounded-full shadow-sm " +
                    " bg-gradient-to-tr from-[#F58529] via-[#DD2A7B] to-[#515BD4] " +
                    " hover:opacity-90 active:opacity-80 " +
                    " focus-visible:ring-2 focus-visible:ring-[#DD2A7B]/40 focus-visible:ring-offset-2",
            },
            size: {
                default: "h-9 px-4 py-2 has-[>svg]:px-3",
                sm: "h-8 rounded-lg gap-1.5 px-3 has-[>svg]:px-2.5",
                lg: "h-11 rounded-lg px-6 has-[>svg]:px-4",
                icon: "h-9 w-9 rounded-lg",
                "icon-sm": "h-8 w-8 rounded-lg ",
                "icon-lg": "h-10 w-10 rounded-lg ",
            },
        },
        defaultVariants: {
            variant: "default",
            size: "default",
        },
    },
);

interface ButtonProps
    extends AriaButtonProps<"button">,
        VariantProps<typeof buttonVariants> {
    className?: string;
    disabled?: boolean | undefined;
}

export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
    ({ variant, size, className, children, ...restProps }, forwardedRef) => {
        const internalRef = useRef<HTMLButtonElement | null>(null);

        // Merge refs properly
        const mergedRef = (node: HTMLButtonElement | null) => {
            internalRef.current = node;
            if (typeof forwardedRef === "function") {
                forwardedRef(node);
            } else if (forwardedRef) {
                forwardedRef.current = node;
            }
        };

        const { buttonProps, isPressed } = useButton(restProps, internalRef);

        return (
            <button
                {...restProps}
                {...buttonProps}
                ref={mergedRef}
                className={cn(buttonVariants({ variant, size }), className)}
                // Ensure disabled state is properly handled
                disabled={restProps.isDisabled || restProps.disabled}
                // Add data attribute for pressed state if needed
                data-pressed={isPressed ? "true" : undefined}
            >
                {children}
            </button>
        );
    },
);

Button.displayName = "Button";