Tailwind v4.0

Introduction

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.

The Tiers save Tears

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;

    • keep it short;
    • keep it dry; and,
    • keep it readable for future you.

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

    • src/app/(pageRoutes)/layout.css –  alongside globals.css and layout.tsx

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”.

Raw CSS and the Emergence of the New NSO

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;

  • they are uniquely named;

  • scoped to a component or concern; and,

  • built from stable design tokens using Tailwind’s @theme system and raw CSS tokens.

Their purpose is the same;

  • to break className entanglement;

  • reduce duplication; and,

  • allow styling to be audited, maintained and evolved independently of component markup.

In this new era, Raw CSS is the NSO, only now it’s;

  • readable;

  • inspectable; and,

  • deeply integrated with the Tailwind token engine.

NSO Mechanics

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;

  • a component;
  • layout region; or,
  • UI concern.

It exists to consolidate complexity and eliminate duplication. NSOs follow the Sentinel Canonical Groupings, ensuring;

  • all properties are ordered by purpose (e.g. Layout → Position → Text → Spacing) and alphabetically within each group;
  • group comment captions (e.g. /* 1. Layout */) are required and must follow exact formatting; and,
  • spacing around group blocks, including full blank lines between groups, is enforced to promote legibility at a glance.

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.