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?