Reusable TailwindCSS Button
A reusable button with loading state, icon and hover states!
In modern web, buttons do more than just getting clicked. A lot of times they are required to show loading states, or disabled while form submission.
Not only that, a design system would also mean buttons would have variants and sizes. Fortunately, with TailwindCSS, it is really easy to make button a highly modular component.
This is the button component which I use across my projects. With TS, we get awesome type-safety across our project. We will require clsx which is a tiny (228B) utility for constructing className strings conditionally.
/ui/Button.tsx
tsximport clsx from 'clsx'
interface Props {
variant?: 'primary' | 'secondary' | 'white' | 'dark' | 'danger'
loading?: boolean
size?: 'xs' | 'sm' | 'base' | 'lg' | 'xl'
className?: string
}
const ButtonSize : Record<Props['size'], string> = {
xs: 'px-2 py-2 leading-5 text-sm',
sm: ' px-3 py-2 leading-5 text-sm',
base: 'px-3 py-1 leading-6 text-sm',
lg: 'px-4 py-2 text-base',
xl: 'px-6 py-3 text-base',
}
const ButtonVariants = {
primary:
'text-white border-indigo-700 bg-indigo-700 hover:bg-indigo-800 hover:border-indigo-800 focus:ring focus:ring-indigo-500 focus:ring-opacity-50 active:bg-indigo-700 active:border-indigo-700',
secondary:
'border-pink-200 bg-pink-200 text-pink-700 hover:text-pink-700 hover:bg-pink-300 hover:border-pink-300 focus:ring focus:ring-pink-500 focus:ring-opacity-50 active:bg-pink-200 active:border-pink-200',
white:
'border border-gray-300 text-gray-700 bg-white hover:bg-gray-300 focus:outline-none focus:ring-offset-2 focus:ring-brand-500',
dark: 'border border-gray-300 dark:border-gray-800 dark:text-gray-100 bg-white shadow-sm dark:bg-gray-700 hover:bg-gray-50 hover:dark:bg-gray-800 focus:outline-none focus:ring focus:ring-gray-500 active:bg-gray-200 active:dark:bg-gray-800',
danger:
'text-white bg-red-700 hover:bg-red-800 border border-red-800 focus:outline-none',
}
export function Button({
children,
variant = 'primary',
loading = false,
size = 'base',
rounded,
fullWidth,
className,
...props
}: Props) {
const sizeStyles = ButtonSize[size] || ButtonSize.sm
const variantStyles = ButtonVariants[variant] || 'primary'
return (
<button
className={clsx(
'inline-flex justify-center items-center font-medium shadow-sm focus:outline-none',
rounded !== 'full' ? sizeStyles : '',
variantStyles,
rounded === 'full' ? 'rounded-full p-2' : `rounded-${rounded}`,
!rounded && 'rounded-md',
fullWidth && 'w-full',
className
)}
{...props}
>
{children}
{loading && (
<svg
className="w-5 h-5 ml-2 fill-current animate-spin"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<path fill="none" d="M0 0h24v24H0z" />
<path d="M12 3a9 9 0 0 1 9 9h-2a7 7 0 0 0-7-7V3z" />
</svg>
)}
</button>
)
}
A lot of going on there! Let's see how to use it.
Usage
Our button supports 5 sizes and 5 variants along with loading state.
tsximport { Button } from './Button';
// ..rest of the code
<Button size="lg" variant="primary">
Click Me
</Button>;
<Button size="md" variant="white" loading={isLoading}>
I'm loading and will have a spinner!
</Button>;