For example, when using TailwindCSS, I noticed that one color class could override another, and in that case, the last added class took effect.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<div class="bg-orange-500 bg-sky-500">
sky is stronger
</div>
But if I add them in the opposite order, it doesn't work.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<div class="bg-sky-500 bg-orange-500">
why isn't orange stronger?
</div>
Why doesn't it work?
What you described in your question would seem logical at first, but native CSS behavior completely rules it out. The order of <div>
elements or how classes are added to a component doesn't matter.
First, because CSS specificity is calculated very differently for each class. Second, because it's not logical to declare the same property twice within a single component.
Because class specificity doesn't depend on their order. Each class has its own strength. The TailwindCSS utilities you're using all have the same specificity level, so whether you can override one with another is often just a matter of luck.
Many developers make this mistake when writing dynamic components - they pass an external class list into the class attribute but also define default values inside the component, like this (which is not a best practice and should be avoided) in example.vue
:
function Box({ className = '', children }) {
return (<div className={`bg-orange-500 ${className}`}>{children}</div>);
}
function App() {
return (
<div>
<Box>*orange* is default</Box>
<Box className="bg-sky-500">*sky* is stronger</Box>
<Box className="bg-green-500">why isn't *green* stronger?</Box>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root')).render(<App />);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<div id="root"></div>
This fundamentally shouldn't be considered standard practice. If you study how native CSS specificity works, you'll realize that if it ever worked before, it was only due to a lucky coincidence. The order of classes will never be a determining factor.
What primarily has an impact is CSS layers. From weakest to strongest, the default order in Tailwind is: theme, base, components, utilities
. Every utility - like bg-sky-500
and bg-orange-100
- ends up in the utilities
layer, which is the strongest layer. However, they all share the same specificity.
From this point on, any perceived "overwriting" is determined solely by the order of declaration:
.bg-sky-500 {
background-color: var(--color-sky-500);
}
.bg-orange-100 {
background-color: var(--color-orange-100);
}
In this case, bg-orange-100
is stronger than bg-sky-500
.
.bg-orange-100 {
background-color: var(--color-orange-100);
}
.bg-sky-500 {
background-color: var(--color-sky-500);
}
In this case, the opposite is true.
Conclusion: don't rely on order. Find a more robust solution.
For example, with predefined styles that consistently enforce the available styles and provide better version control:
function Box({ variant = 'orange', className = '', children }) {
const styles = {
orange: 'bg-orange-500 text-white',
sky: 'bg-sky-500 text-white',
green: 'bg-green-500 text-white',
};
// Only accept layout-specific classes in className
// Colors and styles are handled through enum variants
return (
<div className={`${styles[variant]} p-4 rounded-lg ${className}`}>
{children}
</div>
);
}
function App() {
return (
<div>
<Box>*orange* is default style</Box>
<Box variant="sky">*sky* variant is requested</Box>
<Box variant="green">*green* variant is requested</Box>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root')).render(<App />);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<div id="root"></div>
An alternative solution like this is Tailwind Merge. It doesn't solve the specificity issue directly but instead wraps your class declarations inside a JavaScript function.
Through this function, it can determine which class names to keep from the input and which to discard. This way, it can recognize declarations originating from the same utility - such as two background color classes - and retain only the last one, as if you had written just that one to begin with.
import { twMerge } from 'tailwind-merge'
twMerge('bg-orange-100 bg-sky-500')
// -> bg-sky-500
import { twMerge } from 'tailwind-merge'
const className = 'bg-sky-500'
twMerge('bg-orange-100', className)
// -> bg-sky-500
Which I personally don't prefer - is using !important
. If you only need to override styles occasionally, it's a quick fix: bg-orange-100 bg-sky-500!
Of course, for components, this approach is generally not ideal.
There's also another override method similar to !important
, which is interesting - but I wouldn't recommend it either:
[&]:
, [&&]:
, ... - an infinitely repeatable series. The more &
symbols you use, the stronger the CSS specificity becomes. As an inline solution, it might even be slightly better than !important
, but it generally feels unnecessary. If you're considering using this, there's almost certainly a better alternative available.