DEV Community

Gabriel Linassi
Gabriel Linassi

Posted on

TailwindCSS + React best practices: The clean way

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
|
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Usage (with NextJS Link)

<NextLink href="/signin" passHref>
  <Button fullWidth {...{ disabled, loading }}>
    Register
  </Button>
</NextLink>
Enter fullscreen mode Exit fullscreen mode

Source code (Stackblitz)

Top comments (1)

Collapse
 
oddward profile image
Mugtaba G

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?