Data Nexus

Interface · We run it ourselves

Tailwind CSS v4

A CSS framework whose fourth version has no JavaScript configuration at all — the theme is declared as custom properties inside the stylesheet, and every utility class is generated from those declarations.

The argument for utilities is usually made about typing speed, which is the least interesting thing about them. The interesting thing is what happens to a stylesheet over three years: rules accumulate, and each one becomes harder to delete than the last, because nobody can prove which elements it still reaches. Tailwind v4 moves the two decisions worth centralising — the palette and the scale — into a token file that is read at runtime, and leaves the arrangement of each element next to that element, where it can die with it.

tailwindcss.com

01/Why this one

  1. 01

    The theme is a declaration with five entries in it. This site's `@theme` block names five colour roles — paper, ink, muted, rule, accent — plus an inverse pair, and nothing else. That is not a convention held by a style guide; it is the generated surface. A colour that is not one of those roles has no utility class to reach it, so adding a sixth colour is a commit against a token file that a reviewer reads in ten seconds, rather than a hex literal buried in a component that nobody diffs.


  2. 02

    `@theme inline` keeps the generated utilities pointing at var() instead of substituting the hex value at build time, which makes the palette re-pointable by subtree, not just by document. The inverted full-bleed section on every page here is one class that redeclares the same five variables; everything inside recolours itself with no knowledge that it is inside anything. The measurable consequence: 69 files in this codebase carry className, 63 places use that inverted section, and the codebase contains zero `dark:` variants. A dark theme, an inverted band and a dark footer, and the markup does not mention any of them.


  3. 03

    The stylesheet becomes a countable object. Every route on datanexus.ae is served by one CSS file of 74 KB raw, 13.7 KB over the wire, containing 32 distinct hex literals — and most of those are the token declarations themselves, in their light, dark and inverted variants. A stylesheet where each component brings its own rules has no equivalent number, because there is no single place where the count can be taken; the honest audit is a crawl. Here it is a shell pipeline that anybody can run against the live site without access to the repository.


  4. 04

    Deletion is the asymmetry that decides this. A utility string is removed when the element is removed, atomically, by the same person, in the same commit. A named rule in a shared stylesheet outlives its markup and its author, and proving it unused requires exercising every route in every state — which is why old stylesheets grow monotonically and everyone learns to add rather than edit. Tailwind does not answer the question of whether a rule is still used; it removes the question.


  5. 05

    On readability, the objection is correct and worth stating plainly rather than deflecting: a div carrying fifteen utilities is harder to read than one carrying `class="card"`. What the objection gets wrong is where the cost lands. Reading a long class string is a local cost, paid once, with the complete answer in front of the reader; reading `class="card"` is cheap until the moment the rule needs changing, and then the cost is finding the rule and — the part that never gets cheaper — working out who else it affects. The second cost is unbounded and recurs on every edit.


  6. 06

    Where a name genuinely carries information, v4 gives first-class ways to make one without a plugin written in JavaScript. The type scale on this site is a set of named presets in `@layer components`, used 231 times, because a font-size, line-height and letter-spacing triple that changes at three breakpoints is a thing with a name, not an arrangement. Two `@utility` declarations and one `@custom-variant` cover the press response, the marginal label, and gating hover behind a fine pointer so touch devices do not fire it on tap. The position is not utilities everywhere: utilities for arrangement, names for the type scale, tokens for the palette.


  7. 07

    Everything is emitted into named cascade layers — properties, theme, base, components, utilities, in that order, visible in the shipped file. This turns precedence from a specificity contest into a rule that can be stated: anything unlayered beats every utility regardless of how it is written, which is why element defaults here live inside `@layer base`. A bare `a { color: inherit }` written outside a layer would silently override every text colour utility on every link on the site, and win.


  8. 08

    Because the theme is real CSS custom properties at runtime rather than a JavaScript object consumed at build time, hand-written CSS reads exactly the same values the utilities do. The view transitions, the pseudo-element that draws the column rule, and the achromatic ramps built with `color-mix()` all resolve against the same `--ink` that a `text-ink` class resolves against, and all of them flip together when the theme changes. In v3 that shared vocabulary had to be duplicated by hand, and duplicated values drift.

02/What you can check

An entry that cannot point at something you can open, run or read does not compile. That is a property of the type, not a promise in a paragraph.

Run it yourself
curl -sL https://datanexus.ae/ | grep -o '/_next/static/[^"?]*\.css' | head -1 | sed 's|^|https://datanexus.ae|' | xargs curl -sL | grep -oE '#[0-9a-f]{3,8}\b' | sort -u | wc -l → 32, measured on 4 September 2026 — every distinct hex literal in the single stylesheet that serves all 174 URLs in the sitemap. The number moves only when a colour is added to the token file. Replace the last three stages with grep -o '@layer[^{;]*' to see the five cascade layers Tailwind v4 emits, in order.

In our code
app/theme.css — The token file: 184 lines declaring five colour roles and an inverse pair in @theme inline, the three font roles, and all five breakpoints. The breakpoints are declared in full rather than partially overridden, and the comment says why — see the constraint below.

Property of the thing
Tailwind v4 generates a utility from every name in the --font-* namespace of @theme, including names that collide with built-in utilities. Declaring --font-light on this site replaced the framework's own font-light utility, and weight 300 was absent from the build entirely until the declaration was removed. — Add --font-light to any @theme block, build, and grep the generated CSS for .font-light — it now sets font-family, not font-weight.

Property of the thing
Breakpoint media blocks are emitted in the order @theme registers them, and precedence between equal-specificity blocks is source order. Overriding only md and lg therefore moves those two ahead of the untouched sm, xl and 2xl, and every sm: utility lands after its lg: counterpart and wins on wide screens. On this site that made a footer marked sm:grid-cols-2 lg:grid-cols-4 render two columns on desktop. — Register only --breakpoint-md and --breakpoint-lg, build, and read the order of the min-width blocks in the output: 810px, 1200px, 640px, 1280px, 1536px.

On this site
The Markdown twin of a page, served by content negotiation. It is the same content with no classes on anything, which is precisely the markup no utility framework can address — the reason this site still hand-writes 381 lines of typography rules for generated content.Open it

In our code
mozg/ — Turborepo monorepo running the same v4 setup with prettier-plugin-tailwindcss, husky and lint-staged, so class order is normalised on commit and a diff shows changes rather than reordering. Without it, class strings are exactly as reviewable as their authors happen to be that day.

In our code
seahero/ — Next, Tailwind and tailwind-merge alongside MDX rendering — the second half of the same problem: merging conflicting utility strings when a component accepts a className from its caller.
03/Where it stops

Invariant

Tailwind governs elements that carry classes, and a real site is full of elements that do not: this one still hand-writes 1,924 lines of CSS across four files for view transitions, pseudo-element geometry, keyframes, `color-mix()` ramps and the rich text that arrives from Markdown with no attributes at all. The heavier cost is that a token file distributes a mistake exactly as fast as a fix, and Tailwind verifies nothing about the values it distributes. Deriving the inverted section from the inverse token measured 15.85:1 against paper in the light theme and 1.07:1 in the dark one — the step change made no step, and the footer dissolved into the page on every address at once. No component contained a wrong value, so no code review could have found it; it was found by measuring contrast afterwards. And on a single-page site, where nothing is repeated and nothing outlives its markup, the whole discipline costs more than it returns.