Buttons

Common buttons prompt most actions in a UI

Usage

Buttons communicate actions that users can take. They are typically placed throughout your UI, in places like: Dialogs, Modal windows, Forms, Cards, Toolbars.

Instalation

Run the following command if is not already installed:

npm i @radix-ui/react-slot

Copy and paste the following code into your project.

import * as React from 'react'
import { Slot, Slottable } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'

import { cn } from '@/lib/utils'

const buttonVariants = cva(
  'flex items-center z-0 justify-center relative no-underline select-none group/button w-fit overflow-hidden text-label-lg font-medium rounded-2xl transition outline-none disabled:pointer-events-none disabled:text-onSurface/38 disabled:shadow-none active:scale-[0.98]',
  {
    variants: {
      size: {
        default: 'h-10 px-6',
        small: 'h-8 px-4',
      },
      variant: {
        filled:
          'bg-primary text-onPrimary hover:shadow-sm focus-visible:shadow-none active:shadow-none disabled:bg-onSurface/12',
        tonal:
          'bg-secondaryContainer text-onSecondaryContainer disabled:bg-onSurface/12',
        elevated:
          'bg-surfaceContainerLow shadow-sm text-primary  hover:shadow-md focus-visible:shadow-sm active:shadow-sm disabled:bg-onSurface/12',
        outlined:
          'border border-outline text-primary focus-visible:border-primary active:border-outline disabled:border-onSurface/12',
        text: 'px-3 text-primary',
        destructive:
          'bg-error text-onError hover:shadow-sm focus-visible:shadow-none active:shadow-none disabled:bg-onSurface/12',
      },
    },
    defaultVariants: {
      size: 'default',
      variant: 'filled',
    },
  }
)

const stateLayerVariants = cva(
  'transition-opacity absolute inset-0 z-[-1] opacity-0 group-hover/button:opacity-8 group-focus/button:opacity-12 group-active/button:opacity-12',
  {
    variants: {
      variant: {
        filled: 'bg-onPrimary',
        tonal: 'bg-onSecondaryContainer',
        elevated: 'bg-primary',
        outlined: 'bg-primary',
        text: 'bg-primary',
        destructive: 'bg-onError',
      },
    },
    defaultVariants: {
      variant: 'filled',
    },
  }
)

interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean
  disableStateLayer?: boolean
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  (
    {
      className,
      variant,
      size,
      asChild,
      disableStateLayer,
      children,
      ...props
    },
    ref
  ) => {
    const Comp = asChild ? Slot : 'button'

    return (
      <Comp
        ref={ref}
        className={cn(buttonVariants({ variant, size, className }))}
        {...props}
      >
        <Slottable>{children}</Slottable>
        {!disableStateLayer && (
          <span className={stateLayerVariants({ variant })} />
        )}
      </Comp>
    )
  }
)
Button.displayName = 'Button'

export { Button, buttonVariants, stateLayerVariants }
export type { ButtonProps }

Update the import paths to match your project setup.

Examples

Filled buttons

Filled buttons have the most visual impact after the FAB, and should be used for important, final actions that complete a flow, like Save, Join now, or Confirm.

import { Button } from '@/components/ui/button'

export const FilledButton = () => {
  return <Button>Make payment</Button>
}

Tonal buttons

A filled tonal button is an alternative middle ground between filled and outlined buttons. They’re useful in contexts where a lower-priority button requires slightly more emphasis than an outline would give, such as “Next” in an onboarding flow. Tonal buttons use the secondary color mapping.

import { Button } from '@/components/ui/button'

export const TonalButton = () => {
  return <Button variant="tonal">Visit site</Button>
}

Elevated buttons

Elevated buttons are essentially filled tonal buttons with a shadow. To prevent shadow creep, only use them when absolutely necessary, such as when the button requires visual separation from a patterned background.

import { Button } from '@/components/ui/button'

export const ElevatedButton = () => {
  return <Button variant="elevated">Explore</Button>
}

Outlined buttons

Outlined buttons are medium-emphasis buttons. They contain actions that are important, but aren’t the primary action in an app.

Outlined buttons pair well with filled buttons to indicate an alternative, secondary action.

import { Button } from '@/components/ui/button'

export const OutlinedButton = () => {
  return <Button variant="outlined">Next</Button>
}

Text buttons

Text buttons are used for the lowest priority actions, especially when presenting multiple options.

Text buttons can be placed on a variety of backgrounds. Until the button is interacted with, its container isn’t visible.

Text buttons are often embedded in components like cards, dialogs, and snackbars. Because text buttons don’t have a visible container in their default state, they don’t distract from nearby content.

import { Button } from '@/components/ui/button'

export const TextButton = () => {
  return <Button variant="text">Retry</Button>
}

Destructive buttons

Destructive buttons are used to indicate destructive actions, such as deleting an item.

import { Button } from '@/components/ui/button'

export const DestructiveButton = () => {
  return <Button variant="destructive">Remove account</Button>
}

With icon

Icons visually communicate the button’s action and help draw attention. They should be placed on the leading side of the button, before the label text.

import { Button } from '@/components/ui/button'
import { Icon } from '@/components/ui/icon'

export const ButtonWithIcon = () => {
  return (
    <Button className="pl-4">
      <Icon
        symbol="add"
        className="group-hover:font-emphasis mr-2 text-[20px]"
      />
      New document
    </Button>
  )
}

Loading buttons

Loading buttons indicate an action is in progress. The button should be disabled while loading.

import { Button } from '@/components/ui/button'
import { CircularProgress } from '@/components/ui/progress-indicator'

export const LoadingButton = () => {
  return (
    <Button disabled className="pl-4">
      <CircularProgress className="text-onSurface/38 mr-2 h-5 w-5" />
      Loading
    </Button>
  )
}

As child

With asChild prop, you can make a link looks like a button.

import { Button } from '@/components/ui/button'

export const AsChildButton = () => {
  return (
    <Button asChild className="pl-4">
      <Link href="/components/buttons">Go to Buttons</Link>
    </Button>
  )
}