DEV Community

Cover image for Create design primitives with React and TailwindCSS
Zetrik
Zetrik

Posted on • Updated on

Create design primitives with React and TailwindCSS

TailwindCSS is a powerful utility-first CSS framework for rapidly building your UI. It also removes all unused utility classes from the resulting bundle automatically. But this only works if a full class name provided by Tailwind like bg-gray-900 is inside your code.

A utility function to the rescue

To overcome this limitation, I played a bit around before I ended up with this:

function cx(...cns: (boolean | string | undefined)[]): string {
  return cns.filter(Boolean).join(" ");
}
Enter fullscreen mode Exit fullscreen mode

This small function is quite handy in this case. It allows us to concatenate utility classes in various ways. For example:

const styles = {
  base: "bg-gray-700",
  abc: {
    a: "hover:bg-gray-600",
    b: "hover:bg-gray-500",
    c: "hover:bg-gray-400",
  },
};

const classes = cx(styles.base, styles.abc["b"]);
Enter fullscreen mode Exit fullscreen mode

It's also possible to use bool expressions.

const classes = cx(true && "bg-gray-700", false && "hover:bg-gray-600");
Enter fullscreen mode Exit fullscreen mode

The class hover:bg-gray-600 will be filtered out by cx as the expression resulted in false.

Full Example

import { PropsWithChildren, ReactElement } from "react";

const styles = {
  base: "rounded",
  variants: {
    default: "bg-gray-600",
    primary: "bg-blue-600",
    secondary: "bg-green-600",
  },
  sizes: {
    sm: "px-1 py-1",
    md: "px-2 py-1",
    lg: "px-3 py-1",
  },
};

type Variant = keyof typeof styles.variants;

type Size = keyof typeof styles.sizes;

type Props = {
  type?: "button" | "submit";
  variant?: Variant;
  size?: Size;
  disabled?: boolean;
};

function Button({
  type = "button",
  variant = "default",
  size = "md",
  disabled = false,
  children,
}: PropsWithChildren<Props>): ReactElement {
  const classes = cx(
    styles.base,
    !disabled && styles.variants[variant],
    styles.sizes[size],
    disabled && "bg-gray-800"
  );

  return (
    <button type={type} className={classes} disabled={disabled}>
      {children}
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Somewhere in your codebase, this can be used as follows:

<Button>Click</Button>
<Button variant="primary">Click</Button>
<Button variant="secondary" sizes="lg">Click</Button>
<Button disabled>Click</Button>
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
maxiim3 profile image
maxiim3

The pattern is interesting and applicable to other styling paradigm. Nice article!