I've using Tailwind for a long time and I've had so much pain and that uncomfortable feeling of seeing your code a total mess. I've sharped my strategies to write Tailwind throughout the time and I think I've come up with a good pattern to build more complex components.
The pattern uses CSS Modules, Talwind @apply and tailwind-merge. Let's say I want to build a Button, this would be the folder structure:
|
|-button
|--Button.tsx
|--Button.module.css
|--index.ts
|
And the code like this:
Button /button/Button.tsx
import s from './Button.module.css'
import React from 'react'
import cn from 'classnames'
import Spinner from 'components/spinner'
type ButtonProps = {
children: React.ReactNode
fullWidth?: boolean
loading?: boolean
variant?: 'filled' | 'outlined'
color?: 'primary' | 'secondary'
size?: 'base' | 'lg'
} & Omit<React.ComponentProps<'button'>, 'className'>
const Button = ({
children,
variant = 'filled',
color = 'primary',
size = 'base',
fullWidth,
loading,
disabled,
...props
}: ButtonProps) => {
const classes = cn(s.root, s[variant], s[color], s[size], {
[s.fullWidth]: fullWidth,
})
return (
<button className={classes} disabled={disabled || loading} {...props}>
{children}
{loading && (
<span className="ml-1.5">
<Spinner className={s.spinner} />
</span>
)}
</button>
)
}
export default Button
Styles /button/Button.module.css
.root {
@apply inline-flex items-center justify-center rounded-full font-semibold duration-150 disabled:pointer-events-none disabled:opacity-75;
}
.fullWidth {
@apply w-full;
}
/*
* SIZES
*/
.base {
@apply px-8 py-3;
}
.lg {
@apply px-12 py-5;
}
/*
* VARIANTS & COLORS
*/
.filled.primary {
@apply bg-[#FAA806] text-[#FFFFFF] hover:bg-[#EE9F04];
}
.filled.secondary {
@apply bg-[#373E4B] text-[#97A3B7] hover:bg-[#343A47];
}
.outlined.primary {
@apply border-[#FAA806] text-[#FAA806];
}
.outlined.secondary {
@apply border-[#373E4B] text-[#373E4B];
}
/*
* LOADING INDICATOR
*/
.primary .spinner {
@apply fill-[#bc7e03] text-white;
}
.secondary .spinner {
@apply fill-[#292e38] text-white;
}
Usage (with NextJS Link)
<NextLink href="/signin" passHref>
<Button fullWidth {...{ disabled, loading }}>
Register
</Button>
</NextLink>
Source code (Stackblitz)
Top comments (1)
Thank you for this, it's a pretty nice structure 👌
Would you strictly keep all css classes to the module or occasionally use some on the template? And is the index file for previewing the component in isolation?