Making CSS layers work together
CSS in a frontend project rarely stays in one form for long. A project may have global CSS, a little Less or Sass from an older area, component styles, a library such as Ant Design, and Tailwind utilities sitting beside all of it.
I do not think the answer is to declare one approach the winner. What has helped me more is giving each one a clear job, then using the cascade deliberately when they need to meet.
Give each styling tool a job
Plain CSS is still the final language the browser understands. Less and Sass can make authoring easier when a project needs variables, nesting, mixins, or a clearer way to organise a larger set of styles. Component-scoped styles keep a pattern close to the component that owns it. Tailwind is useful when small layout and visual decisions are easier to see directly in the markup.
These approaches become difficult when they all try to own the same decision. A colour should not mean one thing in a Sass variable, another in a component stylesheet, and a third in a Tailwind class. When possible, I keep shared tokens and broad rules in one place, use a component library for its intended patterns, and reserve local styles for behaviour that genuinely belongs to that component.
| Decision | Usually owned by |
|---|---|
| Colours, spacing, type scale | Shared tokens, in one place |
| Resets and broad element rules | Global CSS |
| Library component behaviour | The library, through its theme and public API |
| Product-specific layout and tweaks | Utilities or local component styles |
The details vary by project. The useful question is simply: which layer should own this decision, and how should another layer override it when there is a good reason?
Use the cascade on purpose
CSS cascade layers make this easier to reason about. For normal declarations, rules in a later layer take precedence over rules in an earlier layer. Styles outside any layer also take precedence over normal styles inside a named layer. That means an override does not always need a more specific selector or !important.
One example is using Ant Design with Tailwind. Ant Design provides a lot of useful component behaviour and default styling, while Tailwind is convenient for product-specific layout and visual adjustments. Instead of fighting Ant Design’s selectors, its styles can be lowered into an antd cascade layer.
With Ant Design’s CSS-in-JS setup, that begins by enabling the layer option on StyleProvider, wrapped around ConfigProvider (documented here):
<StyleProvider layer>
<ConfigProvider>
<App />
</ConfigProvider>
</StyleProvider>For a Tailwind v4 setup, the global stylesheet can establish the order before importing Tailwind:
@layer theme, base, antd, components, utilities;
@import "tailwindcss";antd, so a utility wins a conflict without a heavier selector.In this order, normal Tailwind utilities sit after the antd layer, so a utility can override a conflicting Ant Design declaration without making the selector heavier. I treat this as a deliberate boundary, not permission to style every Ant Design internal element with utilities.
Prefer a small override over a growing exception list
When a library component needs to look closer to the product, I usually start with its documented theme tokens or public APIs. If that is not enough, a cascade layer or a small local wrapper can be a reasonable next step. Reaching straight for a global selector, a deeply nested override, or !important often makes the next change harder.
The same caution applies to reset styles and separately imported library CSS. If Ant Design’s reset or generated CSS is imported outside the intended layer, it can change the precedence again. It is worth checking the final CSS order rather than assuming the source files tell the whole story.
Keep the system understandable
Less, Sass, Tailwind, CSS modules, and component-library styles can coexist. What matters more than the number of tools is whether someone joining the project can tell where a decision belongs and why it wins in the cascade.
For me, a good styling setup is not the one with the fewest files or the most utilities. It is the one that lets the team make a local change with confidence, without accidentally changing another part of the product.
Working on something similar? Let's talk