Tailwind CSS v4 introduces a transformative shift by enabling a true CSS-first workflow through native design tokens and scoped class support. With the new @theme rule, you can define semantic design tokens for colours, spacing, shadows, and more, directly in CSS, while still benefiting from Tailwind’s utility engine.
This unlocks a clean separation of concerns. Utility classes remain useful for atomic overrides, but foundational layout, structure and styling logic can now be expressed through traditional CSS class declarations.
The result is scalable, readable, and maintainable styling. No more utility string overload, no more “className entanglement”, just clear, versionable styles that reflect real design intent.
Tailwind CSS v4 introduces a transformative shift by enabling a true CSS-first workflow through native design tokens and scoped class support. With the new @theme rule, you can define semantic design tokens for colours, spacing, shadows, and more, directly in CSS, while still benefiting from Tailwind’s utility engine.
This unlocks a clean separation of concerns. Utility classes remain useful for atomic overrides, but foundational layout, structure and styling logic can now be expressed through traditional CSS class declarations.
The result is scalable, readable, and maintainable styling. No more utility string overload, no more “className entanglement”, just clear, versionable styles that reflect real design intent.
Our approach is to apply a three-tier styling approach.
1. ClassName First
This is our default entry point. We use standard Tailwind tokens, ordered by prettier-plugin-tailwindcss and applied directly to markup. The standing order is simple;
If a component or view begins to reuse or duplicate a complex block of utilities, especially if the className string becomes entangled or unreadable, it must be promoted to the pure CSS layer.
2. Tailwind Native Tokens and Scoped Class Support
This is our application-level or concern-level design layer. Here we define named tokens (colours, shadows, spacing, etc.) using Tailwind’s @theme block inside CSS files. These tokens are consumed by class-based CSS rules like .auth-wrapper, .card-container or .navbar-shadow and represent real design intent.
Example location
Tokens like –color-googleblue or –shadow-navbar are scoped, traceable and standardized across shared layout or feature concerns.
3. Raw CSS (Entanglement Relief)
This tier exists purely to break className entanglement and enforce DRY principles at the component or concern level. Often used in areas like src/app/(pageRoutes)/(auth), it allows us to cleanly extract structural logic into a named class (e.g., .auth-wrapper) and style it with direct CSS and/or token references.
For example, our auth suite has a very verbose wrapper class to control the display of the right hand panel. Previously this was controlled by a hideously entangled className string (1);
There is no way that “future you” could even start to get your head inside what that is doing in a year’s time (I know because I just had to do it). Further, this “wrapper” is duplicated on each page so it is wet, entangled and prone to “error through editing”.
A Named Style Object (NSO) is a Sentinel concept I have created that refers to any clearly named, reusable block of styling logic, typically defined as a JavaScript or TypeScript object used in CSS-in-JS libraries or style systems.
An NSO is not just a collection of styles, but a versionable, domain-aware unit of visual intent. It encapsulates layout, spacing, visual decoration and even interaction semantics under a single, named reference, enabling composability, DRY enforcement and structural clarity across a codebase.
In Tailwind v4, the Raw CSS layer introduces a new kind of NSO, one expressed as a class-based CSS rule rather than a JavaScript object. These modern NSOs, such as .auth-wrapper, behave identically in practice;
Their purpose is the same;
In this new era, Raw CSS is the NSO, only now it’s;
A Named Style Object (NSO) operates as a self-contained styling unit, responsible for expressing layout, decoration, spacing and interaction in a way that is readable, reusable and versionable.
Whether authored as a JavaScript object (const authWrapper = {}) or as a raw CSS class (.auth-wrapper), an NSO is always bound to a single domain of responsibility;
It exists to consolidate complexity and eliminate duplication. NSOs follow the Sentinel Canonical Groupings, ensuring;
These formatting rules are not stylistic; they are mechanical standards, enabling linting, audits, and long-term maintainability. As we can see in the following example, the adoption of NSO standards produces a set of CSS tags that are able to be read and understood immediately without any confusion. Future you will thank you from the bottom of their heart.
Each NSO must be named meaningfully and placed in a file that reflects its domain. Global or shared NSOs (e.g. layout wrappers, containers, cards) may live in layout.css or globals.css. Concern- or feature-specific NSOs (e.g. .auth-wrapper, .form-step) are scoped to the component or route directory in which they apply. This avoids polluting the global namespace and keeps styling composable and portable.