Skip to content

Styling

Jeremy Asuncion edited this page Jul 16, 2024 · 2 revisions

We use Tailwind and CSS Modules for styling the frontend. Tailwind is our preferred styling solution, however, CSS modules is also available for use cases where it's not possible to style with Tailwind.

Tailwind

Tailwind is a CSS framework for generating utility CSS class that style the markup. For example, a centered div with a blue background would look like:

<div className="flex items-center justify-center bg-blue-500">
  <p>Hello, World!</p>
</div>

The primary advantages of using Tailwind are for:

  • Developer Efficiency: Using utility classes allow us to keep styling and markup close together, reducing the need to switch between editing TSX and CSS files.
  • Performance: The Tailwind compiler only generates classnames for whatever is used in the codebase.

Tailwind can be configured in the tailwind.config.ts file.

CSS Modules

In some cases when Tailwind doesn't work, we also support CSS Modules for styling components. There may be a variety of reasons why you may want to use CSS modules, but the most I've seen it applicable is when styling deeply nested elements, or using CSS syntax that is not easily replicated in Tailwind like @keyframes.

To use a CSS module, you'll need to:

1. Create a CSS module file:

/* Example.module.css */

.component {
  background: red;

  /* You can still use Tailwind classes too if you need to */
  @apply p-3;

  /* And you can reference the theme too */
  color: theme(colors.blue.500);

  /* And you can use breakpoints defined in Tailwind too */
  @media screen(md) {
    background: theme(colors.blue.500);
    color: red;
  }
}

This will also generate the following type definitions using the typed-css-modules package:

// Example.module.css.d.ts

declare const styles: {
  readonly example: string
}

export = styles

With the above type definition, it'll be possible to apply strict type CSS module classes and ensure we only use classes that exist:

image image

2. Import it in your component

// Example.tsx

import styles from './Example.module.css'

export function Example() {
  return (
    <div className={styles.component}>
      <p>Hello, World!</p>
    </div>
  )
}

Class name management with cns

Sometimes we'll need to combine class names from various sources or conditionally show a class name. To do this, we can use the cns() utility function:

import { cns } from 'app/utils/cns'

// ...

return (
  <div
    className={cns(
      'flex items-center justify-center',

      // conditionally apply hidden class if `isHidden` prop is true
      isHidden && 'hidden',

      // Style based on multiple conditions
      textColor && [
        textColor === 'blue' && 'text-blue-500',
        textColor === 'red' && 'text-red-500',
      ],

      // Pass `className` prop last to allow components to override styles
      className,
    )}
  >
    <p>Hello, World!</p>
  </div>
)

This is based on clsx with the added functionality of merging class names using tailwind-merge.

Class name merging

We use tailwind-merge to handle conflicts when merging class names. This ensures that any class names passed by consuming components override the styles.

While not recommended, in some cases, you may not want to merge class names. To do that, you can use the cnsNoMerge() function to merge class names without tailwind-merge.

Clone this wiki locally