Back to overview

Setting and using CSS variables in Tailwind with React

|3 min read

Why?

I often get the question to make a component themeable through a configuration in a CMS (or another datasource). Usually when I hear theming, the first thing that comes to mind is CSS variables. But I don’t want to be using a useEffect with setProperty.

Implementation

The CSS variables are not set yet, so I add them to my global CSS.

src/styles/global.css
:root {
  --primary: #c6ebbe;
  --secondary: #2c6e49;
  --cta-bg: var(--primary);
  --cta-text: black;
}

Now I can use these colors in my component using arbitrary values.

src/components/Demo/Cta.tsx
const Cta = () => {
  return (
    <div className="bg-[var(--cta-bg)] text-[var(--cta-text)] h-40 p-8 flex justify-center items-center">
      Click me
    </div>
  );
};

export default Cta;

This will have the following output:

test

Now let’s make the component themeable. I add a prop “variant” to the component, which will be used to set the CSS variables. If the variant prop has the value “highlighted”, the component will change the variables for the CTA’s background color and text color using the [--VARIABLENAME:VALUE] syntax. Setting the CSS variable this way will only affect the component and not the whole page due to the scoping.

src/components/Demo/Cta.tsx
import clsx from "clsx";

const Cta = ({
  variant = "standard",
}: {
  variant: "standard" | "highlighted";
}) => {
  return (
    <div
      className={clsx(
        {
          "[--cta-bg:var(--secondary)] [--cta-text:white]":
            variant === "highlighted",
        },
        "bg-[var(--cta-bg)] text-[var(--cta-text)] h-40 p-8 flex justify-center items-center",
      )}
    >
      Click me
    </div>
  );
};

export default Cta;

So when I now use the component with the variant “highlighted” <Cta variant="highlighted" />, the background color and text color will change.

Now what if I would have dynamic rich text content coming from the CMS, which could contain HTML elements, and I want to apply theming to those elements too? This approach can easily be extended to support that. In Tailwind you can use the [&_ELEMENTNAME] syntax to target an element inside a component.

src/components/Demo/Cta.tsx
import clsx from "clsx";

const Cta = ({
  variant = "standard",
  content,
}: {
  variant: "standard" | "highlighted";
  content: "string";
}) => {
  return (
    <div
      className={clsx(
        {
          "[--cta-bg:var(--secondary)] [--cta-text:white] [--cta-link-text:yellow] [--cta-bullet-color:yellow] [&_a]:!decoration-yellow-200 [&_a]:text-[var(--cta-link-text)] [&_li]:marker:text-[var(--cta-bullet-color)]":
            variant === "highlighted",
        },
        "bg-[var(--cta-bg)] text-[var(--cta-text)] h-40 p-8 flex justify-center items-center",
      )}
    >
      {content ? <div dangerouslySetInnerHTML={{ __html: content }} /> : "test"}
    </div>
  );
};

export default Cta;

Without variant

Code:

<Cta content="<div><ul><li>List item 1</li><li>List item 2</li></ul><a href='https://www.google.com'>Link</a></div>" />

Output:

  • List item 1
  • List item 2
Link

With prop variant="highlighted"

Code:

<Cta
  variant="highlighted"
  content="<div><ul><li>List item 1</li><li>List item 2</li></ul><a href='https://www.google.com'>Link</a></div>"
/>

Output:


Notice how the link and list item bullets coming from the external HTML are now yellow.

Conclusion

I know it’s possible to use inline styling to set CSS variables too, but I don’t like the syntax for inline styling and this would not work for the dynamic content coming from an external source, since we don’t have control over the HTML structure. With this approach we can do everything using the power of Tailwind, which is what I prefer in all cases. The code for this can be found on my GitHub.

Did you like this post? Check out my latest blog posts:

Chrome logo repeated in a grid
4 Sept 2023

Developing a Chrome extension to convert the current url to localhost in 1 click

  • javascript
  • chrome extension
  • html
The word update spelled out with scrabble blocks
10 Jul 2023

Keeping your dependencies up to date in a visual way using npm-check-updates

  • javascript
  • nodejs
Github discussion style comment section
18 Jun 2023

Using GitHub Discussions to host my Astro blog comments and reactions

  • javascript
  • astro
  • react