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?