Button
Installation
Install the following dependencies
npm install clsx lucide-react
Copy and paste the following code into your project
primitives/button/index.tsx
import { clsx } from 'clsx';import { Loader2 } from 'lucide-react';import { ComponentPropsWithoutRef } from 'react';export interface ButtonProps extends ComponentPropsWithoutRef<'button'> { variant?: 'primary' | 'secondary' | 'tertiary' | 'ghost' | 'danger'; size?: 'large' | 'medium' | 'small' | 'x-small'; shape?: 'pill' | 'rounded' | 'square' | 'circle'; loading?: boolean;}/** * This component supports various CSS variables for theming. Here's a comprehensive list, along * with their default values: * * ```css * :root { * --button-focus: var(--primary); * --button-font-family: var(--font-family-body); * --button-primary-background: var(--primary); * --button-primary-background-hover: var(--primary-highlight); * --button-primary-text: var(--foreground); * --button-primary-border: var(--primary); * --button-secondary-background: var(--foreground); * --button-secondary-background-hover: var(--background); * --button-secondary-text: var(--background); * --button-secondary-border: var(--foreground); * --button-tertiary-background: var(--background); * --button-tertiary-background-hover: var(--contrast-100); * --button-tertiary-text: var(--foreground); * --button-tertiary-border: var(--contrast-200); * --button-ghost-background: transparent; * --button-ghost-background-hover: color-mix(in oklab, var(--foreground) 5%, transparent); * --button-ghost-text: var(--foreground); * --button-ghost-border: transparent; * --button-loader-icon: var(--foreground); * --button-danger-background: color-mix(in oklab, var(--error), white 30%); * --button-danger-background-hover: var(--error-highlight); * --button-danger-text: var(--foreground); * --button-danger-border: color-mix(in oklab, var(--error), white 30%); * } * ``` */export function Button({ variant = 'primary', size = 'large', shape = 'pill', loading = false, type = 'button', disabled = false, className, children, ...props}: ButtonProps) { return ( <button {...props} aria-busy={loading} className={clsx( 'relative z-0 inline-flex h-fit items-center justify-center overflow-hidden border text-center font-(family-name:--button-font-family,var(--font-family-body)) leading-normal font-semibold select-none after:absolute after:inset-0 after:-z-10 after:-translate-x-[105%] after:transition-[opacity,translate] after:duration-300 after:[animation-timing-function:cubic-bezier(0,0.25,0,1)] focus-visible:ring-2 focus-visible:ring-(--button-focus,var(--primary)) focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-30', { primary: 'border-(--button-primary-border,var(--primary)) bg-(--button-primary-background,var(--primary)) text-(--button-primary-text,var(--foreground)) after:bg-(--button-primary-background-hover,var(--primary-highlight))', secondary: 'border-(--button-secondary-border,var(--foreground)) bg-(--button-secondary-background,var(--foreground)) text-(--button-secondary-text,var(--background)) after:bg-(--button-secondary-background-hover,var(--background))', tertiary: 'border-(--button-tertiary-border,var(--contrast-200)) bg-(--button-tertiary-background,var(--background)) text-(--button-tertiary-text,var(--foreground)) after:bg-(--button-tertiary-background-hover,var(--contrast-100))', ghost: 'border-(--button-ghost-border,transparent) bg-(--button-ghost-background,transparent) text-(--button-ghost-text,var(--foreground)) after:bg-(--button-ghost-background-hover,color-mix(in_oklab,var(--foreground)_5%,transparent))', danger: 'border-(--button-danger-border,color-mix(in_oklab,var(--error),white_30%)) bg-(--button-danger-background,color-mix(in_oklab,var(--error),white_30%)) text-(--button-danger-text,var(--foreground)) after:bg-(--button-danger-background-hover,var(--error-highlight))', }[variant], { pill: 'rounded-full after:rounded-full', rounded: 'rounded-lg after:rounded-lg', square: 'rounded-none after:rounded-none', circle: 'rounded-full after:rounded-full', }[shape], !loading && !disabled && 'hover:after:translate-x-0', loading && 'pointer-events-none', className, )} disabled={disabled} type={type} > <span className={clsx( 'inline-flex items-center justify-center transition-all duration-300 ease-in-out', loading ? '-translate-y-10 opacity-0' : 'translate-y-0 opacity-100', shape === 'circle' && 'aspect-square', { 'x-small': 'min-h-8 text-xs', small: 'min-h-10 text-sm', medium: 'min-h-12 text-base', large: 'min-h-14 text-base', }[size], shape !== 'circle' && { 'x-small': 'gap-x-2 px-3 py-1.5', small: 'gap-x-2 px-4 py-2.5', medium: 'gap-x-2.5 px-5 py-3', large: 'gap-x-3 px-6 py-4', }[size], variant === 'secondary' && 'mix-blend-difference', )} > {children} </span> <span className={clsx( 'absolute inset-0 grid place-content-center transition-all duration-300 ease-in-out', loading ? 'translate-y-0 opacity-100' : 'translate-y-10 opacity-0', )} > <Loader2 className={clsx( 'animate-spin', variant === 'tertiary' && 'text-(--button-loader-icon,var(--foreground))', )} /> </span> </button> );}
Usage
import { Button } from '@/vibes/soul/primitives/button';function Usage() { return ( <Button shape={'pill'} size={'large'} variant={'primary'}> Show now </Button> );}
API Reference
ButtonProps
Prop | Type | Default |
---|---|---|
children* | ReactNode | |
className | string | |
variant | 'primary' | 'secondary' | 'tertiary' | 'ghost' | 'danger' | 'primary' |
size | 'large' | 'medium' | 'small' | 'x-small' | 'large' |
shape | 'pill' | 'rounded' | 'square' | 'circle' | 'pill' |
loading | boolean | false |
srOnlyText | string | '' |
CSS Variables
This component supports various CSS variables for theming. Here's a comprehensive list.
:root { --button-focus: var(--primary); --button-font-family: var(--font-family-body); --button-primary-background: var(--primary); --button-primary-background-hover: var(--primary-highlight); --button-primary-text: var(--foreground); --button-primary-border: var(--primary); --button-secondary-background: var(--foreground); --button-secondary-background-hover: var(--background); --button-secondary-text: var(--background); --button-secondary-border: var(--foreground); --button-tertiary-background: var(--background); --button-tertiary-background-hover: var(--contrast-100); --button-tertiary-text: var(--foreground); --button-tertiary-border: var(--contrast-200); --button-ghost-background: transparent; --button-ghost-background-hover: color-mix(in oklab, var(--foreground) 5%, transparent); --button-ghost-text: var(--foreground); --button-ghost-border: transparent; --button-loader-icon: var(--foreground); --button-danger-background: color-mix(in oklab, var(--error), white 30%); --button-danger-background-hover: var(--error-highlight); --button-danger-text: var(--foreground); --button-danger-border: color-mix(in oklab, var(--error), white 30%);}
Changelog
2025-05-20
- Removed
loading
prop from disabled state - Added correct
disabled
Tailwind classes