Components
Button
Renders a clickable button or an element styled as a button
Installation
Install ReUI
Refer to the Installation Guide for detailed instructions on setting up ReUI dependencies in your project.
Add component
Copy and paste the following code into your project’s components/ui/button.tsx
file.
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import { ChevronDown, LucideIcon } from 'lucide-react';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
'cursor-pointer group whitespace-nowrap focus-visible:outline-hidden inline-flex items-center justify-center has-data-[arrow=true]:justify-between whitespace-nowrap text-sm font-medium ring-offset-background transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 [&_svg]:shrink-0',
{
variants: {
variant: {
primary:
'bg-primary text-primary-foreground hover:bg-primary/90 data-[state=open]:bg-primary/90',
mono: 'bg-mono text-mono-foreground hover:bg-mono/90 data-[state=open]:bg-mono/90',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/90 data-[state=open]:bg-destructive/90',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/90 data-[state=open]:bg-secondary/90',
outline:
'bg-background text-accent-foreground border border-input hover:bg-accent data-[state=open]:bg-accent',
dashed:
'text-accent-foreground border border-input border-dashed bg-background hover:bg-accent hover:text-accent-foreground data-[state=open]:text-accent-foreground',
ghost:
'text-accent-foreground hover:bg-accent hover:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
dim: 'text-muted-foreground hover:text-foreground data-[state=open]:text-foreground',
foreground: '',
inverse: '',
},
appearance: {
default: '',
ghost: '',
},
underline: {
solid: '',
dashed: '',
},
underlined: {
solid: '',
dashed: '',
},
size: {
lg: 'h-10 rounded-md px-4 text-sm gap-1.5 [&_svg:not([class*=size-])]:size-4',
md: 'h-8.5 rounded-md px-3 gap-1.5 text-[0.8125rem] leading-(--text-sm--line-height) [&_svg:not([class*=size-])]:size-4',
sm: 'h-7 rounded-md px-2.5 gap-1.25 text-xs [&_svg:not([class*=size-])]:size-3.5',
},
autoHeight: {
true: '',
false: '',
},
shape: {
default: '',
circle: 'rounded-full',
},
mode: {
default:
'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
icon: 'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
link: 'text-primary h-auto p-0 bg-transparent rounded-none hover:bg-transparent data-[state=open]:bg-transparent',
input: `
justify-start font-normal hover:bg-background [&_svg]:transition-colors [&_svg]:hover:text-foreground data-[state=open]:bg-background
focus-visible:border-ring focus-visible:outline-hidden focus-visible:ring-[3px] focus-visible:ring-ring/30
[[data-state=open]>&]:border-ring [[data-state=open]>&]:outline-hidden [[data-state=open]>&]:ring-[3px]
[[data-state=open]>&]:ring-ring/30
aria-invalid:border-destructive/60 aria-invalid:ring-destructive/10 dark:aria-invalid:border-destructive dark:aria-invalid:ring-destructive/20
in-data-[invalid=true]:border-destructive/60 in-data-[invalid=true]:ring-destructive/10 dark:in-data-[invalid=true]:border-destructive dark:in-data-[invalid=true]:ring-destructive/20
`,
},
placeholder: {
true: 'text-muted-foreground',
false: '',
},
},
compoundVariants: [
// Icons opacity for default mode
{
variant: 'ghost',
mode: 'default',
className: '[&_svg:not([role=img]):not([class*=text-])]:opacity-60',
},
{
variant: 'outline',
mode: 'default',
className: '[&_svg:not([role=img]):not([class*=text-])]:opacity-60',
},
{
variant: 'dashed',
mode: 'default',
className: '[&_svg:not([role=img]):not([class*=text-])]:opacity-60',
},
{
variant: 'secondary',
mode: 'default',
className: '[&_svg:not([role=img]):not([class*=text-])]:opacity-60',
},
// Icons opacity for default mode
{
variant: 'outline',
mode: 'input',
className: '[&_svg:not([role=img]):not([class*=text-])]:opacity-60',
},
{
variant: 'outline',
mode: 'icon',
className: '[&_svg:not([role=img]):not([class*=text-])]:opacity-60',
},
// Auto height
{
size: 'md',
autoHeight: true,
className: 'h-auto min-h-8.5',
},
{
size: 'sm',
autoHeight: true,
className: 'h-auto min-h-7',
},
{
size: 'lg',
autoHeight: true,
className: 'h-auto min-h-10',
},
// Shadow support
{
variant: 'primary',
mode: 'default',
appearance: 'default',
className: 'shadow-xs shadow-black/5',
},
{
variant: 'mono',
mode: 'default',
appearance: 'default',
className: 'shadow-xs shadow-black/5',
},
{
variant: 'secondary',
mode: 'default',
appearance: 'default',
className: 'shadow-xs shadow-black/5',
},
{
variant: 'outline',
mode: 'default',
appearance: 'default',
className: 'shadow-xs shadow-black/5',
},
{
variant: 'dashed',
mode: 'default',
appearance: 'default',
className: 'shadow-xs shadow-black/5',
},
{
variant: 'destructive',
mode: 'default',
appearance: 'default',
className: 'shadow-xs shadow-black/5',
},
// Shadow support
{
variant: 'primary',
mode: 'icon',
appearance: 'default',
className: 'shadow-xs shadow-black/5',
},
{
variant: 'mono',
mode: 'icon',
appearance: 'default',
className: 'shadow-xs shadow-black/5',
},
{
variant: 'secondary',
mode: 'icon',
appearance: 'default',
className: 'shadow-xs shadow-black/5',
},
{
variant: 'outline',
mode: 'icon',
appearance: 'default',
className: 'shadow-xs shadow-black/5',
},
{
variant: 'dashed',
mode: 'icon',
appearance: 'default',
className: 'shadow-xs shadow-black/5',
},
{
variant: 'destructive',
mode: 'icon',
appearance: 'default',
className: 'shadow-xs shadow-black/5',
},
// Link
{
variant: 'primary',
mode: 'link',
underline: 'solid',
className:
'font-medium text-primary hover:text-primary/90 [&_svg:not([role=img]):not([class*=text-])]:opacity-60 hover:underline hover:underline-offset-4 hover:decoration-solid',
},
{
variant: 'primary',
mode: 'link',
underline: 'dashed',
className:
'font-medium text-primary hover:text-primary/90 [&_svg:not([role=img]):not([class*=text-])]:opacity-60 hover:underline hover:underline-offset-4 hover:decoration-dashed decoration-1',
},
{
variant: 'primary',
mode: 'link',
underlined: 'solid',
className:
'font-medium text-primary hover:text-primary/90 [&_svg:not([role=img]):not([class*=text-])]:opacity-60 underline underline-offset-4 decoration-solid',
},
{
variant: 'primary',
mode: 'link',
underlined: 'dashed',
className:
'font-medium text-primary hover:text-primary/90 [&_svg]:opacity-60 underline underline-offset-4 decoration-dashed decoration-1',
},
{
variant: 'inverse',
mode: 'link',
underline: 'solid',
className:
'font-medium text-inherit [&_svg:not([role=img]):not([class*=text-])]:opacity-60 hover:underline hover:underline-offset-4 hover:decoration-solid',
},
{
variant: 'inverse',
mode: 'link',
underline: 'dashed',
className:
'font-medium text-inherit [&_svg:not([role=img]):not([class*=text-])]:opacity-60 hover:underline hover:underline-offset-4 hover:decoration-dashed decoration-1',
},
{
variant: 'inverse',
mode: 'link',
underlined: 'solid',
className:
'font-medium text-inherit [&_svg:not([role=img]):not([class*=text-])]:opacity-60 underline underline-offset-4 decoration-solid',
},
{
variant: 'inverse',
mode: 'link',
underlined: 'dashed',
className:
'font-medium text-inherit [&_svg:not([role=img]):not([class*=text-])]:opacity-60 underline underline-offset-4 decoration-dashed decoration-1',
},
{
variant: 'foreground',
mode: 'link',
underline: 'solid',
className:
'font-medium text-foreground [&_svg:not([role=img]):not([class*=text-])]:opacity-60 hover:underline hover:underline-offset-4 hover:decoration-solid',
},
{
variant: 'foreground',
mode: 'link',
underline: 'dashed',
className:
'font-medium text-foreground [&_svg:not([role=img]):not([class*=text-])]:opacity-60 hover:underline hover:underline-offset-4 hover:decoration-dashed decoration-1',
},
{
variant: 'foreground',
mode: 'link',
underlined: 'solid',
className:
'font-medium text-foreground [&_svg:not([role=img]):not([class*=text-])]:opacity-60 underline underline-offset-4 decoration-solid',
},
{
variant: 'foreground',
mode: 'link',
underlined: 'dashed',
className:
'font-medium text-foreground [&_svg:not([role=img]):not([class*=text-])]:opacity-60 underline underline-offset-4 decoration-dashed decoration-1',
},
// Ghost
{
variant: 'primary',
appearance: 'ghost',
className:
'bg-transparent text-primary/90 hover:bg-primary/5 data-[state=open]:bg-primary/5',
},
{
variant: 'destructive',
appearance: 'ghost',
className:
'bg-transparent text-destructive/90 hover:bg-destructive/5 data-[state=open]:bg-destructive/5',
},
{
variant: 'ghost',
mode: 'icon',
className: 'text-muted-foreground',
},
// Size
{
size: 'sm',
mode: 'icon',
className: 'w-7 h-7 p-0 [[&_svg:not([class*=size-])]:size-3.5',
},
{
size: 'md',
mode: 'icon',
className: 'w-8.5 h-8.5 p-0 [&_svg:not([class*=size-])]:size-4',
},
{
size: 'lg',
mode: 'icon',
className: 'w-10 h-10 p-0 [&_svg:not([class*=size-])]:size-4',
},
// Input mode
{
mode: 'input',
placeholder: true,
variant: 'outline',
className: 'font-normal text-muted-foreground',
},
{
mode: 'input',
variant: 'outline',
size: 'sm',
className: 'gap-1.25',
},
{
mode: 'input',
variant: 'outline',
size: 'md',
className: 'gap-1.5',
},
{
mode: 'input',
variant: 'outline',
size: 'lg',
className: 'gap-1.5',
},
],
defaultVariants: {
variant: 'primary',
mode: 'default',
size: 'md',
shape: 'default',
appearance: 'default',
},
},
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
selected?: boolean;
}
function Button({
className,
selected,
variant,
shape,
appearance,
mode,
size,
autoHeight,
underlined,
underline,
asChild = false,
placeholder = false,
...props
}: ButtonProps) {
const Comp = asChild ? Slot : 'button';
return (
<Comp
data-slot="button"
className={cn(
buttonVariants({
variant,
size,
shape,
appearance,
mode,
autoHeight,
placeholder,
underlined,
underline,
className,
}),
asChild && props.disabled && 'pointer-events-none opacity-50',
)}
{...(selected && { 'data-state': 'open' })}
{...props}
/>
);
}
interface ButtonArrowProps extends React.SVGProps<SVGSVGElement> {
icon?: LucideIcon; // Allows passing any Lucide icon
}
function ButtonArrow({
icon: Icon = ChevronDown,
className,
...props
}: ButtonArrowProps) {
return (
<Icon
data-slot="button-arrow"
className={cn('ms-auto -me-1', className)}
{...props}
/>
);
}
export { Button, ButtonArrow, buttonVariants };
Examples
Primary
Loading
Secondary
Loading
Mono
Loading
Destructive
Loading
Outline
Loading
Dashed
Loading
Ghost
Loading
With Icon
Loading
As Input
Loading
Icon Only
Loading
Loading
Loading
Badge
Loading
Size
Loading
Circle
Loading
Link
Loading
Full Width
Loading
Disabled
Loading
API Reference
This component provides a versatile and customizable button system with various styles, appearances, and sizes.
Button
This is the root component for creating buttons with multiple variants, appearances, sizes, and shapes.
Prop | Type | Default |
---|---|---|
variant | ||
appearance | ||
size | ||
shape | ||
mode | ||
underline | ||
underlined | ||
asChild | ||
className |