0

I'm having the following noob issue trying to assign dynamically tailwind classes to a react component.

I have extended my theme colors in the tailwind.config.js as follow:

...
theme: {
    extend: {
      colors: {
        blueGray: {
           50: '#f6f9f9',
          100: '#e4f1f8',
          200: '#c2e0f0',
          300: '#91c0db',
          400: '#5b9bbf',
          500: '#4479a3',
          600: '#385f87',
          700: '#2d4768',
          800: '#203049',
          900: '#131d2f',
       },
       // OTHER COLORS
    },
  },
},
...

My react component looks like this:

import Draggable from 'react-draggable';

type SensorProps = {
    name: string
    color: string
}

export default function Sensor(props : SensorProps): JSX.Element {
    return (
        <Draggable
            axis="both"
            bounds="flow-canvas">
             <div className={`border-${props.color}-400  bg-${props.color}-50 text-${props.color}-700`}>
              <p> {props.name} </p>
            </div>
        </Draggable>
    )
}

This are some examples of how I instantiate my Sensor component

<Sensor name={"Water Level"} color={"blueGray"} />
<Sensor name={"Flow"} color={"mGreen"} />

The problem is that the classes are not applied, but when I inspect my page the div has the right classes.

If switch from:

<div className={`border-${props.color}-400  bg-${props.color}-50 text-${props.color}-700`}>

to:

<div className={`border-blueGray-400  bg-blueGray-50 text-blueGray-700`}>

It works :(

I'm already using the tailwind JIT compiler

...
mode: 'jit',
purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
...

Any suggestions?

Humberto
  • 328
  • 3
  • 12
  • https://tailwindcss.com/docs/optimizing-for-production#writing-purgeable-html, https://tailwindcss.com/docs/optimizing-for-production#safelisting-specific-classes – brc-dd Nov 24 '21 at 09:16
  • 1
    Does this answer your question? [purgecss can't recognize conditional classes](https://stackoverflow.com/questions/61312762/purgecss-cant-recognize-conditional-classes) – juliomalves Nov 24 '21 at 20:29
  • For Nextjs 13.4 + Refer https://stackoverflow.com/a/76660733/13431819 – krishnaacharyaa Aug 06 '23 at 12:14

4 Answers4

3

The tailwind compiler parses your code on compilation and purges classes that it does not see used anywhere. You're not using border-blueGray-400 directly so it treats it as an unused class and removes it from its bundle to improve performance.

The best solution in my opinion is to not pass arbitrary props like color, size etc., but instead pass a className attribute.

Therefore, you would render your component like this:

<Sensor className="border-blueGray-400 bg-blueGray-50 text-blueGray-700" />

And in the child component:

<div className={props.className} />
szaman
  • 2,159
  • 1
  • 14
  • 30
2

Now you can use

<div className={`border-${props.color}-400  bg-${props.color}-50 text-${props.color}-700`}>

By using safelisting classes in tailwindcss


Explanation

Is it recommended to use dynamic class in tailwind ?

No

Using dynamic classes in tailwind-css is usually not recommended because tailwind uses tree-shaking i.e any class that wasn't declared in your source files, won't be generated in the output file.

Hence it is always recommended to use full class names

According to Docs

If you use string interpolation or concatenate partial class names together, Tailwind will not find them and therefore will not generate the corresponding CSS

Isn't there work around ?

Yes

As a last resort, Tailwind offers Safelisting classes.

Safelisting is a last-resort, and should only be used in situations where it’s impossible to scan certain content for class names. These situations are rare, and you should almost never need this feature.

In your example,you want to have 100 500 700 shades of colors. You can use regular expressions to include all the colors you want using pattern and specify the shades accordingly .

In tailwind.config.js

module.exports = {
  content: [
    './pages/**/*.{html,js}',
    './components/**/*.{html,js}',
  ],
  safelist: [
    {
      pattern: /bg-(red|green|blue|orange)-(100|500|700)/, // You can display all the colors that you need
    },
  ],
  // ...
}

EXTRA: How to automate to have all tailwind colors in the safelist
const tailwindColors = require("./node_modules/tailwindcss/colors")
const colorSafeList = []

// Skip these to avoid a load of deprecated warnings when tailwind starts up
const deprecated = ["lightBlue", "warmGray", "trueGray", "coolGray", "blueGray"]

for (const colorName in tailwindColors) {
  if (deprecated.includes(colorName)) {
    continue
  }

  // Define all of your desired shades
  const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900]

  const pallette = tailwindColors[colorName]

  if (typeof pallette === "object") {
    shades.forEach((shade) => {
      if (shade in pallette) {
        colorSafeList.push(`text-${colorName}-${shade}`)
        colorSafeList.push(`bg-${colorName}-${shade}`)
        colorSafeList.push(`border-${colorName}-${shade}`)
      }
    })
  }
}

// tailwind.config.js
module.exports = {
  safelist: colorSafeList,                      // <-- add the safelist here
  content: ["{pages,app}/**/*.{js,ts,jsx,tsx}"],
  theme: {
    extend: {
      colors: tailwindColors,
    },
  },
  plugins: [],
}
krishnaacharyaa
  • 14,953
  • 4
  • 49
  • 88
1

Using Inline-CSS

I stumbled upon this problem when I was dynamically generating jsx having the same design but different text and button colors. It can be a bit tricky since when developing it seemed fine and all the classes rendered correctly, but a quick restart of dev server seemed to completely remove those classes from the final bundle. After cursing git for few minutes, I had an intuition and found out this thread.

For any future readers, the most elegant solution without using outside libraries and risking bloat from safelisting tailwind classes, is to use inline styling, i.e style = {{ color: INSERT_VARIABLE_HERE }} and you're good to go.

Working code:

// Generate modals for different types
// All use the same design
// IMPORTANT: Tailwind cannot deduce partial class names sent as arguments, and
// removes them from final bundle, safe to use inline styling
const _generateModal = (
  initialTitle: string,
  image: string,
  buttonColor: string,
  bgColor: string = "white",
  textColor: string = "rgb(55 65 81)",
  buttonText: string = "Continue"
) => {
  return ({ title = initialTitle, text, isOpen, onClose }: Props) => {
    if (!isOpen) return null;
    return ReactDom.createPortal(
      <div className="fixed inset-0 bg-black bg-opacity-80">
        <div className="flex h-full flex-col items-center justify-center">
          <div
            className={`relative flex h-1/2 w-1/2 flex-col items-center justify-evenly rounded-xl lg:w-1/4`}
            style={{ color: textColor, backgroundColor: bgColor }}
          >
            <RxCross2
              className="absolute top-0 right-0 mr-5 mt-5 cursor-pointer text-2xl"
              onClick={() => onClose()}
            />
            <h1 className="text-center text-3xl font-thin">{title}</h1>
            <h3 className="text-center text-xl font-light tracking-wider opacity-80">
              {text}
            </h3>
            <img
              src={image}
              alt="info image"
              className="hidden w-1/6 lg:block lg:w-1/4"
            />
            <button
              onClick={() => onClose()}
              className={`rounded-full px-16 py-2 text-xl text-white`}
              style={{ backgroundColor: buttonColor }}
            >
              {buttonText}
            </button>
          </div>
        </div>
      </div>,
      document.getElementById("modal-root") as HTMLElement
    );
  };
};

export const SuccessModal = _generateModal(
  "Success!",
  checkimg,
  "rgb(21 128 61)" // green-700
);
export const InfoModal = _generateModal(
  "Hey there!",
  infoimg,
  "rgb(59 130 246)" // blue-500
);
export const ErrorModal = _generateModal(
  "Face-plant!",
  errorimg,
  "rgb(190 18 60)", // rose-700
  "rgb(225 29 72)", // rose-600
  "rgb(229 231 235)", // gray-200
  "Try Again"
);

I'm using reactjs and typescript here with tailwind and this code right here can be used to generate 3 types of popup modals, InfoModal, ErrorModal and SuccessModal. Instead of writing the same boilerplate code everywhere, where updating one would need updating in 3 places, a jsx generator can help mitigate those problems by writing the code for us.

0

You can render classes conditionally with a library like clsx. Then your child component would render:

<div className={clsx(
  "border-blueGray-400 bg-blueGray-50 text-blueGray-700": props.color === "blueGray",
  "border-mGray-400 bg-mGray-50 text-mGray-700": props.color === "mGray",
)} />

This is not a good solution if you just want to modify one or two attributes. I would then advise to pass tailwind classes as props directly instead, like mentioned in the other answer.

But if you have some more complex logic, like multiple css attributes depending on the class, this solution might be good.

szaman
  • 2,159
  • 1
  • 14
  • 30