Back to overview

Setting and using CSS variables in Tailwind with React

TailwindCSS logo
| 3 min read
View count:

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:

Astral picture
29 Mar 2024

Astro DB: Migrating my analytics data from Vercel Postgres

5 min reading time

  • astro
  • database
  • vercel
  • postgres
  • libsql
Screenshot of an analytics dashboard
16 Jan 2024

Basic analytics with Vercel Postgres - Drizzle - Astro

10 min reading time

  • vercel
  • databases
  • typescript
  • astro
Page of a dictionary
15 Nov 2023

A minimal dependency-free translation system for Next.js

6 min reading time

  • nextjs
  • react
  • javascript