Button

Displays a button or a component that looks like a button.

Installation

Install the following dependencies:

npm install @react-aria/button @react-aria/focus

Copy and paste the following code into your project.

"use client";
 
import { mergeProps } from "~/utils/merge-props";
import { mergeRefs } from "~/utils/merge-refs";
import { useButton } from "@react-aria/button";
import { useFocusRing } from "@react-aria/focus";
import type { FocusEvents, PressEvents } from "@react-aria/interactions";
import { useRef } from "react";
import { type VariantProps, tv } from "tailwind-variants";
 
export const ButtonStyles = tv({
  base: [
    "inline-flex select-none items-center justify-center whitespace-nowrap rounded-md font-medium text-sm outline-none ring-offset-2 ring-offset-background transition",
    "data-[focused=true]:ring-1",
    "disabled:cursor-not-allowed disabled:opacity-50",
    "data-[pressed=true]:scale-95",
  ],
 
  variants: {
    variant: {
      default: [
        "bg-primary text-primary-foreground shadow",
        "hover:bg-primary/90",
        "data-[focused=true]:ring-primary",
      ],
      destructive: [
        "bg-destructive text-destructive-foreground shadow-sm",
        "hover:bg-destructive/90",
        "data-[focused=true]:ring-destructive",
      ],
      outline: [
        "border border-input bg-background shadow-sm",
        "hover:bg-accent hover:text-accent-foreground",
        "data-[focused=true]:ring-accent",
      ],
      secondary: [
        "bg-secondary text-secondary-foreground shadow-sm",
        "hover:bg-secondary/80",
        "data-[focused=true]:ring-secondary",
      ],
      ghost: [
        "hover:bg-accent hover:text-accent-foreground",
        "data-[focused=true]:ring-accent",
      ],
      link: [
        "text-primary underline-offset-4",
        "hover:underline",
        "data-[focused=true]:ring-ring",
      ],
    },
    size: {
      default: ["h-9 px-4 py-2"],
      sm: ["h-8 rounded-md px-3 text-xs"],
      lg: ["h-10 rounded-md px-8"],
      icon: ["size-9"],
    },
  },
  defaultVariants: {
    variant: "default",
    size: "default",
  },
});
 
export type ButtonProps = React.ComponentProps<"button"> &
  VariantProps<typeof ButtonStyles> &
  PressEvents &
  FocusEvents;
 
export function Button({
  className,
  variant,
  size,
  type = "button",
  onPress,
  onPressStart,
  onPressEnd,
  onPressChange,
  onPressUp,
  ref: forwardedRef,
  ...props
}: ButtonProps) {
  const ref = useRef<HTMLButtonElement>(null);
 
  const { buttonProps, isPressed } = useButton(
    {
      ...props,
      type,
      onPress,
      onPressStart,
      onPressEnd,
      onPressChange,
      onPressUp,
      isDisabled: props.disabled,
    },
    ref
  );
 
  const { focusProps, isFocusVisible } = useFocusRing({
    isTextInput: false,
    autoFocus: props.autoFocus,
  });
 
  return (
    <button
      {...mergeProps(buttonProps, focusProps, props)}
      ref={mergeRefs([ref, forwardedRef])}
      data-pressed={isPressed}
      data-focused={isFocusVisible}
      className={ButtonStyles({ className, variant, size })}
    />
  );
}

Update the import paths to match your project setup.

Usage

import { Button } from "~/components/ui/button";
<Button variant="outline">Button</Button>

Examples

Default

Secondary

Destructive

Outline

Ghost

Icon

With Icon

API Reference

PropTypeDefaultDescription
variant"default" | "destructive" | "outline" | "secondary" | "ghost" | "link""default"The visual style of the button.
size"default" | "sm" | "lg" | "icon""default"The size of the button.
disabledboolean-Whether the button is disabled.
EventTypeDescription
onPress(e: PressEvent) => voidHandler that is called when the press is released over the target.
onPressStart(e: PressEvent) => voidHandler that is called when a press interaction starts.
onPressEnd(e: PressEvent) => voidHandler that is called when a press interaction ends, either over the target or when the pointer leaves the target.
onPressChange(isPressed: boolean) => voidHandler that is called when the press state changes.
onPressUp(e: PressEvent) => voidHandler that is called when a press is released over the target, regardless of whether it started on the target or not.
onFocus(e: FocusEvent<Target>) => voidHandler that is called when the element receives focus.
onBlur(e: FocusEvent<Target>) => voidHandler that is called when the element loses focus.
onFocusChange(isFocused: boolean) => voidHandler that is called when the element's focus status changes.
onKeyDown(e: KeyboardEvent) => voidHandler that is called when a key is pressed.
onKeyUp(e: KeyboardEvent) => voidHandler that is called when a key is released.
Data attributeDescription
[data-pressed]Whether the button is currently in a pressed state.
[data-focused]Whether the button is focused, either via a mouse or keyboard.