Skip to content

Instantly share code, notes, and snippets.

Last active Oct 3, 2022
What would you like to do?
import React, { ReactNode } from "react";
import { classNames } from "../utils/class-names";
enum Variant {
type Props = {
variant: Variant;
children?: ReactNode;
const VARIANT_MAPS: Record<Variant, string> = {
[Variant.GRAY]: "bg-gray-100 text-gray-800",
[Variant.RED]: "bg-red-100 text-red-800",
[Variant.ORANGE]: "bg-orange-100 text-orange-800",
[Variant.YELLOW]: "bg-yellow-100 text-yellow-800",
[Variant.GREEN]: "bg-green-100 text-green-800",
[Variant.TEAL]: "bg-teal-100 text-teal-800",
[Variant.BLUE]: "bg-blue-100 text-blue-800",
[Variant.INDIGO]: "bg-indigo-100 text-indigo-800",
[Variant.PURPLE]: "bg-purple-100 text-purple-800",
[Variant.PINK]: "bg-pink-100 text-pink-800"
export function Badge(props: Props) {
const { children, variant } = props;
return (
"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium leading-4 whitespace-no-wrap",
Badge.defaultProps = {
variant: Variant.GRAY
Badge.variant = Variant;
<Badge />
<Badge variant={Badge.variant.GRAY} />

Why this is bad (you might think)

  1. The file is big
  2. The file might look unmaintainable:
  • You have to update the class map & the enum (spoiler: TypeScript will ensure this is in sync)
  • There is a lot of duplicate code (the classNames per variant are almost the same except for the color)

Why this is good

  1. You know the exact states your component can be in
  2. There is no cleverness / dynamic classes going on -> More maintable
  3. There is no className prop, this means that we know what states / variants our component can be in, we don't introduce new implicit states (a large badge if we add className="text-xl")
  4. If one of the values need an extra class, you can add it, no need for clever tricks. (E.g.: Let's say a yellow color needs a darker text color)
  5. PurgeCSS can easily remove non-used css classes

Here is a bad example:

Copy link

neldeles commented Nov 10, 2021

Thanks for sharing your method @crimelabs786 . I'm fairly new to TS so this part was unclear to me:

only the array variants need to be modified, and TypeScript would do the rest.

I still had to manually update 2 places with the method you shared (i.e. variants and variantMaps). Just recently though I came across this awesome article by Kent Dodds on constrained identity functions. Have started using the CIF pattern instead.

// the constrained identity function
const createMaps = <ObjectMapType extends Record<string, string>>(
  obj: ObjectMapType
) => obj;

const variantMaps = createMaps({
    red: "bg-red-100 text-red-800",

export type ButtonProps = {
  variant: keyof typeof variantMaps;

With the CIF pattern, I now only need to update one place instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment