Tooling

Built for Chromium 103

The game’s UI runs in FiveM’s embedded Chromium 103 and stays on Tailwind 3.4, because Tailwind 4 needs Chrome 111. Everything here — tokens, utilities, components — has to work there.

Where it runs

Game NUI

FiveM CEF · Chromium 103

Vite 8 · React 19 · Tailwind 3.4.1

backdrop-filter off, ui-scale PostCSS pass

Hub

Evergreen browsers

Next 16 · React 19 · Tailwind 3.4.17

light and dark themes

Web site

Evergreen browsers

React Router · Tailwind

tokens only

Why a lint

A Tailwind 4 class in a Tailwind 3 app generates nothing. Nothing fails, nothing warns; the style just never shows. Both apps already ship a few dozen of them, copied from newer shadcn code.

Silent by design

outline-hidden in Tailwind 3 is not an error — it is an unknown class, and an unknown class is ignored. The only way to catch it is to look for it.

Tailwind 4 class names

PatternWhy, and what to write instead
^(?:[\w-]+:)*outline-hidden$Tailwind 4 only; use outline-none
^(?:[\w-]+:)*shadow-xs$Tailwind 4 only; use shadow-sm
^(?:[\w-]+:)*rounded-xs$Tailwind 4 only; use rounded-sm
^(?:[\w-]+:)*bg-linear-to-[trbl]{1,2}$Tailwind 4 only; use bg-gradient-to-*
^(?:[\w-]+:)*field-sizing-Tailwind 4 only (field-sizing)
^(?:[\w-]+:)*[\w-]+-\(--[\w-]+\)$Tailwind 4 variable shorthand; use [var(--x)]
^(?:[\w-]+:)*data-(?:disabled|open|closed|active|selected|highlighted|placeholder):Tailwind 4 bare data variant; use data-[disabled]:
^(?:[\w-]+:)*not-[\w[\]-]+:Tailwind 4 not-* variant
^(?:[\w-]+:)*starting:Tailwind 4 starting: variant
^(?:[\w-]+:)*inset-shadow-Tailwind 4 only (inset-shadow)
^(?:[\w-]+:)*text-shadow-Tailwind 4 only (text-shadow)

Tailwind 3 classes Chromium 103 cannot run

PatternWhy, and what to write instead
(?:^|:)(?:group-|peer-)?has-\[has-* compiles to :has(), which needs Chromium 105
^(?:[\w-]+:)*text-(?:balance|pretty)$text-wrap: balance/pretty needs Chromium 114
^(?:[\w-]+:)*@containercontainer queries need Chromium 105

Dynamic viewport units (h-dvh, min-h-svh) are allowed only behind a supports-[height:100dvh]: guard, with a vh fallback beside them.

CSS Chromium 103 cannot parse

PatternWhy, and what to write instead
color-mix\(color-mix() needs Chromium 111
\bokl(?:ch|ab)\(oklch()/oklab() need Chromium 111
:has\(:has() needs Chromium 105
@container\bcontainer queries need Chromium 105
\b\d*\.?\d+(?:s|d|l)v[hwib]\bsvh/dvh/lvh need Chromium 108
text-wrap:\s*(?:balance|pretty)text-wrap: balance needs Chromium 114
@starting-style\b@starting-style needs Chromium 117

Inside an @supports block these are fine. A mask-image also needs its -webkit-mask-image twin.

Run it

The tokens package ships the lint as fast-tw-compat. It scans .ts/.tsx/.js/.jsx/.css and exits 1 on any finding.
bunx fast-tw-compat src
# src/components/ui/select.tsx:42  outline-hidden  Tailwind 4 only; use outline-none