Tailwind CSS has done wonders for development — it can get you up and running in a matter of minutes. It contains the right building blocks out of the box with options to customize just about anything throughout the system.
If you’ve never built a design system from scratch, it’s easy taking something like Tailwind CSS for granted, especially when it comes to setting up a type-scale, spacing grid, and colors (where Tailwind truly shines). Tailwind’s world-class designers were meticulous when choosing the right color hues and shades capable of making just about anything look great.
Sometimes, you could be given a design with brand colors you could extend in Tailwind or override default colors with anything you’d like when you know those values upfront.
But what happens when you’re not in control of the exact colors that end up in the user’s browser? What if the colors come from the backend or are dynamic and controlled via user input? Do you resort back to doing inline styles? Or perhaps generate separate CSS styles for those use cases outside of Tailwind?
I hope not! I’m here to show you there’s a better way, a solution that’s native to the way Tailwind works, and its extensible API allows us to push it further than the defaults we get out of the box.
I’m going to showcase how I solved a real-world use case with a tiny SaaS product I built called NodCards. The concept is simple — it’s your digital business card. With NodCards, your details can be featured on a personal landing page, shared in your email signature, a link-in-bio on social media, or any other place you’d like to share your information.
NodCards is a good example because the user can choose any color as the primary design color, which requires NodCards to adapt dynamically. I was set on using Tailwind CSS for the styling, so how did I do this?
With this context in mind, and knowing how Tailwind classes work, we want to target the text color text-{primary}
for the person’s name, as well as a background color bg-{primary}
for the buttons, and maybe a bit more for hover effect.
Your first thought might be, thank goodness for the just-in-time (JIT) compiler, which can dynamically compose styles like bg-[#6231af]
. But I would argue that it’s not a real contender for what we have to do here, as you’ll soon see why.
I had a few considerations and requirements to make this work for my use case:
Based on these criteria, I put together a short demo of the project. To help visualize, I made the demo interactive by adding a color picker to simulate the dynamic changing of color in the UI. As you can see, it changes instantly without affecting the stylesheet and/or mark-up.
Let’s see how we achieved this.
The best approach I found was by leveraging the power of CSS variables. The same approach could apply to any language or framework. In my example, I’m building this as a Next.js site using React, but the concepts are transferable. Here’s the full source code.
Most of the time, we’ll be provided with a hex color from the backend. In this case, I wrote a quick helper function to help with getting our hex colors from the backend into the right RGB format.
The second utility function we need is a method to get the accessible color that is of high contrast (according to the WCAG guidelines) to display on top of the dynamic primary color. I created a utilities file to centralize my helper functions that we can use later on:
// utils/index.js ///////////////////////////////////////////////////////////////////// // Change hex color into RGB ///////////////////////////////////////////////////////////////////// export const getRGBColor = (hex, type) => { let color = hex.replace(/#/g, "") // rgb values var r = parseInt(color.substr(0, 2), 16) var g = parseInt(color.substr(2, 2), 16) var b = parseInt(color.substr(4, 2), 16) return `--color-${type}: ${r}, ${g}, ${b};` } ///////////////////////////////////////////////////////////////////// // Determine the accessible color of text ///////////////////////////////////////////////////////////////////// export const getAccessibleColor = (hex) => { let color = hex.replace(/#/g, "") // rgb values var r = parseInt(color.substr(0, 2), 16) var g = parseInt(color.substr(2, 2), 16) var b = parseInt(color.substr(4, 2), 16) var yiq = (r * 299 + g * 587 + b * 114) / 1000 return yiq >= 128 ? "#000000" : "#FFFFFF" }
The getAccessibleColor
function works by converting the RGB color space into YIQ, as explained in calculating color contrast. For our use case, we needed a reliable method for the text color to go on top of our primary color.
With our helper functions in place, we convert our dynamic primary color from our backend to an RGB format that can be used in our CSS variable. We then get the a11y
(accessibility) color.
I structured my function to receive a second param for the type of color I’m declaring. This allows me to reuse the function for any combination of CSS variables I would want to declare. Adding a secondary, accent, or any other color into the mix would follow the exact same logic.
Changing the format into RGB is a crucial step. The main reason we need these colors in RGB format is that when we compose our new colors through Tailwind CSS, we can add an alpha layer for RGBA colors. This ensures all bg-opacity
and text-opacity
classes would work, and we could get various shades of our dynamic color by working with the opacity layer as well.
With our primaryColor
and a11yColor
in RGB format, we need to declare a CSS variable scoped to the root of our HTML document. Adding this on :root
means we’ll have access to it anywhere we use CSS classes within the DOM.
// pages/index.js const primaryColor = getRGBColor("#6231af", "primary") const a11yColor = getRGBColor(getAccessibleColor("#6231af"), "a11y") <!-- ... --> <!-- ... --> <Head> <style>:root {`{${primaryColor} ${a11yColor}}`}</style> </Head>
For now, we’ve just hard-coded this to #6231af
, but this is easy to replace as a single entry point for any dynamic color later on.
Any color in Tailwind CSS is declared by immediately declaring a --tw-bg-opacity: 1;
utility in that class. Any colors declared are then made into RGBA values, where it uses the --tw-bg-opacity
value, as declared for the alpha channel.
If you just declare a color, that color would be solid, but when we declare bg-opacity-{value}
, Tailwind CSS re-declares that alpha channel, enabling us to achieve various levels of opacity with our dynamic color.
This is actually very clever from the guys at Tailwind CSS, as there is no way to declare or use a text or background-only opacity in CSS. The only way to achieve this is with the alpha channel in RGBA, and, by following the pattern they’ve laid out, we’re leveraging the full potential of their color system.
At this point, we have a CSS variable declared in our HTML (which could be connected to our backend). The next step is to link that CSS variable to some Tailwind CSS classes to use.
To achieve this, we have to focus on the tailwind.config.js
file, which is where all the magic happens. Tailwind allows us to assign colors as a function instead of a string to get access to the internal Tailwind opacity utility. This is something that we’ll use a couple of times, so it’s best to extract this into a reusable function at the top of our tailwind.config.js
:
// tailwind.config.js function withOpacity(variableName) { return ({ opacityValue }) => { if (opacityValue !== undefined) { return `rgba(var(${variableName}), ${opacityValue})` } return `rgb(var(${variableName}))` } }
First off, we receive the opacity value on colors from Tailwind that we can use with our RGB-formatted color as an alpha channel. Because this might not be set, we perform a quick check: if it’s undefined
, we return it without any alpha.
In either case, we simply assign the value of our CSS variable to the rgb
in this format. This is now ready to use when composing our colors.
Next, we set up our new colors by extending our theme:
// tailwind.config.js theme: { extend: { textColor: { skin: { primary: withOpacity("--color-primary"), a11y: withOpacity("--color-a11y"), }, }, backgroundColor: { skin: { primary: withOpacity("--color-primary"), a11y: withOpacity("--color-a11y"), }, }, ringColor: { skin: { primary: withOpacity("--color-primary"), }, }, borderColor: { skin: { primary: withOpacity("--color-primary"), a11y: withOpacity("--color-a11y"), }, }, }, },
I like to nest these utilities with my own prefix keyword, skin
. This is optional, but it comes in handy when you are typing out classes, especially when you use something like the Tailwind VSCode extension with IntelliSense, which would pick up and show all your new classes.
We extend our theme with the textColor
and backgroundColor
as planned, as well as add the ringColor
and borderColor
variations so that we have our dynamic custom color available on those utilities as well.
With this in place, Tailwind can generate a bunch of new classes for us. We can just start using any of these new classes in our markup.
Now, let’s change the text color to our new dynamically set primary color.
<p className="... text-skin-primary"> Jane Cooper </p>
On the buttons, let’s set the background to the primary with a 30 percent opacity, which allows us to use the primary text color. We can then make the background solid on hover and switch to our accessibility safe color for the icon on hover.
We can also use the border color and focus ring colors all set in our primary dynamic color.
<a href={link.href} target="_blank" className="... bg-skin-primary bg-opacity-30 text-skin-primary hover:bg-opacity-100 hover:text-skin-a11y border-skin-primary focus:ring-skin-primary"> <link.icon className="h-5 w-5" /> </a>
Looking back at our requirements, we were able to dynamically set our primary color without changing markup, get different shades of our primary color, and maintain a good contrast ratio when displaying anything on top of our dynamic primary color.
Have you done something similar? Let me know in the comments below!
As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app, mobile app, or website. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.
Modernize how you debug web and mobile apps — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowSimplify component interaction and dynamic theming in Vue 3 with defineExpose and for better control and flexibility.
Explore how to integrate TypeScript into a Node.js and Express application, leveraging ts-node, nodemon, and TypeScript path aliases.
es-toolkit is a lightweight, efficient JavaScript utility library, ideal as a modern Lodash alternative for smaller bundles.
The use cases for the ResizeObserver API may not be immediately obvious, so let’s take a look at a few practical examples.
29 Replies to "Applying dynamic styles with Tailwind CSS"
Wow, thank you. This is a really neat idea.
Hurrah, that’s what I was looking for, what a data!
very awsome information about Tailwind CSS very helpful for me thanks man.
Great article and work, keep up with your experiments.
Thanks for sharing blog commenting list with us, I hope all links will work. Thanks again!
written and come with approximately all significant infos. Wonderful, what a web site it is! This webpage presents valuable facts to us, keep it up.
Thank you so much for the resources and tips on how to use them. I learned a lot from this post.
Thanks for sharing this article. It’s very useful to me.
Thanks for this article admin
you are really amazing i am so interested to follow this blog
Thanks for sharing nice article. It is very beneficial for the guest blogger and blog owner.
really helped me, I am looking for this to use it with wordpress inline style, now it’s working like a charm.
Thank you for sharing .Keep blogging new updates.
Thank you for sharing. Keep blogging on new updates.
I am very enjoyed for this blog. Its an informative topic.
Great article!
Looks like this is exactly what I need. Already spent 4 hours without success. Stumbled upon this article just before I complicate things. I will try this. Thank you do much.
Thank you for share the informative blog article for us.
thank you for share the applying dynamic style blog.
Looks like this is exactly what I need. Already spent 4 hours without success. Stumbled upon this article just before I complicate things. I will try this. Thank you do much.
Hello!
I am Ahmad Raja(Box Label Packaging), a 10years+ Offset Printing Experienced Graphic designer.
Fiverr Gig Name: boxespackaging
Dude, needed this! Thanks so much!
All good in above article, But I think this is completely for the advanced level. No, newbies will be able to perform this. And also there are lots of other plugins or codes are available. But according to my suggestion, awesome article is this.
Thank you! This helps a lot. I would like to suggest that instead of writing an if-then function matching the opacityValue, you can just define the default opacityValue as 1 by adding :root {–tw-bg-opacity: 1;}. That way, you can just skip the “with opacity” function and just define everything as RGBA. Take it with a grain of salt tho, coz this may have some cons (haven’t really researched) but I think overall it would be a neater alternative.
fantastic, how did you generate the index.html with all the static files?
Have anyone gotten this to work in next 13 with app routes?
I tried to update a website to use the app routes which is getting theme colors in this way from a CMS system. However, I am unable to pass the props to the global styles.
Considering the techniques for applying dynamic styles in Tailwind CSS discussed in the article, what strategies can be employed to ensure these styles remain performant and scalable across large-scale applications?
hi how did you generate the index.html with all the static files?
Wow, So much to learn today. Thanks for the blog
Wow, So much to learn today. Thanks for the blog