Button

Examples

Variant Examples

Showcase different button variants (solid, ghost, light, bordered):

Color Examples

Display buttons in different colors (blue, red, green, dark, light):

Size Examples

Illustrate buttons of various sizes (small, medium, large):

Code

import React, { forwardRef, ReactNode } from 'react'
import { cva, VariantProps, cx } from 'class-variance-authority'

const ANIMATION_CLASS = 'transition-all duration-300 ease-in-out'

const buttonVariants = cva(`inline-grid group grid-flow-col items-center justify-center cursor-pointer !outline-none !ring-0 ${ANIMATION_CLASS} select-none`, {
  variants: {
    size: {
      small: 'px-3 py-1.5 text-sm',
      medium: 'px-5 py-2 text-base',
      large: 'px-8 py-2 text-lg'
    },
    radius: {
      full: 'rounded-full',
      large: 'rounded-lg',
      medium: 'rounded-md',
      small: 'rounded-sm',
      none: 'rounded-none'
    },
    disabled: {
      true: 'opacity-50 cursor-not-allowed',
      false: ''
    },
    color: {
      blue: '',
      red: '',
      green: '',
      dark: '',
      light: ''
    },
    variant: {
      solid: '',
      ghost: 'bg-transparent border',
      light: 'bg-transparent',
      bordered: 'border'
    }
  },
  compoundVariants: [
    { variant: 'solid', color: 'blue', className: 'bg-blue-600 hover:bg-blue-600/90' },
    { variant: 'solid', color: 'red', className: 'bg-red-500 hover:bg-red-600/90' },
    { variant: 'solid', color: 'green', className: 'bg-green-500 hover:bg-green-600/90' },
    { variant: 'solid', color: 'dark', className: 'bg-zinc-800 hover:bg-zinc-700/50' },
    { variant: 'solid', color: 'light', className: 'text-black bg-zinc-100 hover:bg-zinc-200/90' },
    { variant: 'ghost', color: 'light', className: 'text-white hover:text-black hover:bg-zinc-200 border-zinc-100 dark:border-zinc-700' },
    { variant: 'ghost', color: 'blue', className: 'text-blue hover:text-white hover:bg-blue-600 border-zinc-100 dark:border-zinc-700' },
    { variant: 'ghost', color: 'green', className: 'text-green hover:text-white hover:bg-green-500 hover:border-green-500 border-zinc-100 dark:border-zinc-700' },
    { variant: 'light', color: 'blue', className: 'text-blue-600 hover:bg-blue-600/20' },
    { variant: 'light', color: 'red', className: 'text-red-500 hover:bg-red-500/20' },
    { variant: 'light', color: 'green', className: 'text-green-500 hover:bg-green-500/20' },
    { variant: 'bordered', color: 'blue', className: 'text-blue-600 border-blue-600/30 hover:border-blue-600' },
    { variant: 'bordered', color: 'red', className: 'text-red-500 border-red-500/30 hover:border-red-600' },
    { variant: 'bordered', color: 'green', className: 'text-green-500 border-green-500/30 hover:border-green-600' }
  ],
  defaultVariants: {
    size: 'medium',
    radius: 'medium',
    disabled: false,
    color: 'dark',
    variant: 'solid'
  }
})

const contentAnimation = cva('inline-grid', {
  variants: {
    animation: {
      true: `${ANIMATION_CLASS} grid-cols-[0fr] group-hover:grid-cols-[1fr]`,
      false: ''
    }
  },
  defaultVariants: {
    animation: false
  }
})

const innerContentAnimation = cva('min-w-0', {
  variants: {
    animation: {
      true: `invisible opacity-0 ${ANIMATION_CLASS} group-hover:visible group-hover:opacity-100`,
      false: ''
    },
    position: {
      left: '',
      right: ''
    }
  },
  compoundVariants: [
    { animation: true, position: 'left', class: 'origin-left translate-x-4 group-hover:translate-x-0 group-hover:mr-1.5' },
    { animation: true, position: 'right', class: 'origin-right -translate-x-4 group-hover:translate-x-0 group-hover:ml-1.5' },
    { animation: false, position: 'left', class: 'mr-1.5' },
    { animation: false, position: 'right', class: 'ml-1.5' }
  ],
  defaultVariants: {
    animation: false,
    position: 'left' // Default position
  }
})

type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
  asChild?: React.ElementType
  children: ReactNode
  endContent?: ReactNode
  startContent?: ReactNode
  animateEndContent?: boolean
  animateStartContent?: boolean
} & VariantProps<typeof buttonVariants>

const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  (
    { asChild: AsChild, children, className, color, radius, variant, disabled, size, animateStartContent = false, animateEndContent = false, endContent, startContent, ...props },
    ref
  ) => {
    const mergedClassName = cx(buttonVariants({ color, radius, variant, disabled, size }), className)
    const Component = AsChild ?? 'button'
    return (
      <Component ref={ref} className={mergedClassName} {...props}>
        {startContent && (
          <span className={contentAnimation({ animation: animateStartContent }) + ' origin-left'}>
            <span className={innerContentAnimation({ animation: animateStartContent, position: 'left' })}>{startContent}</span>
          </span>
        )}
        {children}
        {endContent && (
          <span className={contentAnimation({ animation: animateEndContent }) + ' origin-right'}>
            <span className={innerContentAnimation({ animation: animateEndContent, position: 'right' })}>{endContent}</span>
          </span>
        )}
      </Component>
    )
  }
)

Button.displayName = 'Button'

export default Button

Usage

<Button color="blue" radius="medium" variant="solid">
  Click Me
</Button>

Props

Detail the props available for the Button component:

  • color: Defines the button's color. Options: blue, red, green, dark, light.
  • radius: Sets the border-radius. Options: full, large, medium, small, none.
  • variant: Specifies the style. Options: solid, ghost, light, bordered.
  • disabled: Enables or disables the button. Options: true, false.
  • size: Determines the button's size. Options: small, medium, large.

This structure focuses on clear, concise information and practical examples, making it easy for developers to understand and utilize the Button component in their projects.