1. docs
  2. components
  3. button

Button

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


Installation

Copy and paste the following code into your project.

"use client";

import { focusRing } from "@/components/ui/lib/aria-utils";
import { cn } from "@/lib/utils";
import { LoaderIcon } from "lucide-react";
import React from "react";
import {
  Button as RACButton,
  ButtonProps as RACButtonProps,
  type LinkProps as RACLinkProps,
  Link as RACLink,
} from "react-aria-components";
import { tv, type VariantProps } from "tailwind-variants";

let button = tv({
  extend: focusRing,
  base: `relative isolate pressed:ring-none inline-flex items-center justify-center 
  gap-x-2 rounded-sm border text-base/6 font-semibold px-5 py-2 text-base/6 text-center 
  transition no-underline  border 
  disabled:bg-gray-100 disabled:dark:bg-zinc-800 
  disabled:text-gray-300 disabled:dark:text-zinc-600 forced-colors:text-[GrayText] 
  disabled:border-black/5 disabled:dark:border-white/5 disabled:hover:bg-gray-100 
  disabled:dark:hover:bg-zinc-800 disabled:cursor-not-allowed`,
  variants: {
    variant: {
      primary:
        "bg-primary hover:bg-primary/90 pressed:bg-primary/90 text-primary-foreground  border-black/10 dark:border-white/10 ",
      secondary:
        "bg-secondary text-secondary-foreground hover:bg-secondary/80 pressed:bg-secondary/90 border-secondary/10",
      destructive:
        "bg-destructive text-destructive-foreground border-destructive/10",
      accent: "hover:bg-accent text-accent-foreground border-none",
      outline: "bg-transparent border hover:bg-foreground/5",
      ghost: "bg-transparent border-none hover:bg-foreground/5 !shadow-none",
    },
    size: {
      sm: "text-sm/6 px-3 py-1 [&_svg]:size-4",
      md: "text-base/6 px-5 py-2 [&_svg]:size-5",
      lg: "text-lg/6 px-7 py-3 [&_svg]:size-6",
      icon: "p-2 rounded-sm size-9 border-none",
    },
    shadow: {
      true: "shadow-[inset_0_1px_0_0_rgba(255,255,255,0.1)] dark:shadow-none",
    },
    border: {
      false: "border-none",
    },
    fullWidth: {
      true: "w-full",
    },
  },
  defaultVariants: {
    variant: "primary",
    border: true,
    shadow: true,
    size: "md",
  },
});

type ButtonProps = RACButtonProps &
  Omit<RACLinkProps, "className" | "style" | "children"> &
  VariantProps<typeof button> & {
    isLoading?: boolean;
    leftSection?: React.ReactNode;
    rightSection?: React.ReactNode;
  };

function Button({
  children,
  className,
  isDisabled,
  size,
  variant,
  border,
  shadow,
  href,
  fullWidth,
  isLoading,
  ...props
}: ButtonProps) {
  const ButtonElement: React.ElementType = href ? RACLink : RACButton;

  return (
    <ButtonElement
      {...props}
      href={href}
      isDisabled={isLoading || isDisabled}
      className={cn(
        button({ size, variant, border, shadow, fullWidth }),
        className
      )}
    >
      {isLoading && <LoaderIcon className="h-4 w-4 animate-spin" />}
      {props.leftSection}
      {children}
      {props.rightSection}
    </ButtonElement>
  );
}

export { Button, type ButtonProps };

Update the import paths to match your project setup.

Usage

import { Button } from "@/components/Button";
<Button>Primary</Button>

There is a small difference between this button and your good old button element. To get same functionality of onClick you have to use onPress prop.

<Button onPress={() => console.log("Hello World")}>Primary</Button>

The visual style of the button.

Props

Button Variants

Use the variant prop to change the appearance of the button.

Default variant is primary.

Button Sizes

Use the size prop to change the size of the button.

Default size is md.

Now you dont have to use a or Link tag to make a button look like a link.

Button Disabled

Use the isDisabled prop to disable the button.

Button Loading

Well unlike shadcn you dont have to adjust button styles and keep looking for the right spinner. Just use the isLoading prop to show a loading spinner.

Button with Icon

Use the rightSection or leftSection prop to add an icon to the button.

type of rightSection or leftSection is ReactNode.

API Reference

PropTypeDefaultDescription
variant"primary" | "secondary" | "accent" | "outline" | "destructive" | "ghost" "primary"Basic Style of Button
size"sm" | "md" | "lg""md"The size of the button.
leftSectionReact.ReactNode-React Element before button text
rightSectionReact.ReactNode-React Element before button text
isLoadingboolean-loading state of the button
isDisabledboolean-
hrefstring-acts as <Link> element of react-aria
autoFocusboolean—Whether the element should receive focus on render.
type"button" | "submit" | 'reset''button'The behavior of the button when used in an HTML form.
childrenReactNode | (values: ButtonRenderProps & {defaultChildren: ReactNode | undefined}) => ReactNode-The children of the component. A function may be provided to alter the children based on component state.
classNamestring | (values: ButtonRenderProps & { defaultClassName: string | undefined }) => string—The CSS className for the element. A function may be provided to compute the class based on component state.
styleCSSProperties | (values: ButtonRenderProps & { defaultStyle: CSSProperties }) => CSSProperties—The inline style for the element. A function may be provided to compute the style based on component state.

Events

NameTypeDescription
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.
onHoverStart(e: HoverEvent) => voidHandler that is called when a hover interaction starts.
onHoverEnd(e: HoverEvent) => voidHandler that is called when a hover interaction ends.
onHoverChange(isHovering: boolean) => voidHandler that is called when the hover state changes.

Accessibility

NameTypeDescription
idstringThe element's unique identifier. See MDN.
excludeFromTabOrderbooleanWhether to exclude the element from the sequential tab order. If true, the element will not be focusable via the keyboard by tabbing. This should be avoided except in rare scenarios where an alternative means of accessing the element or its functionality via the keyboard is available.
aria-expandedboolean | 'true' | 'false'Indicates whether the element, or another grouping element it controls, is currently expanded or collapsed.
aria-haspopupboolean | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog' | 'true' | 'false'Indicates the availability and type of interactive popup element, such as menu or dialog, that can be triggered by an element.
aria-controlsstringIdentifies the element (or elements) whose contents or presence are controlled by the current element.
aria-pressedboolean | 'true' | 'false' | 'mixed'Indicates the current "pressed" state of toggle buttons.
aria-labelstringDefines a string value that labels the current element.
aria-labelledbystringIdentifies the element (or elements) that labels the current element.
aria-describedbystringIdentifies the element (or elements) that describes the object.
aria-detailsstringIdentifies the element (or elements) that provide a detailed, extended description for the object.