@viscalyx/developer-mode-react publishes its overlay's Tailwind v4
class list as a first-class artifact, so consumers do not have to
hand-curate a safelist or @source node_modules directly.
This document explains how the artifact is produced and the three ways downstream applications can consume it. If you only want the short version, jump to Option A — recommended.
- How it works
- Option A — import
safelist.css(recommended) - Option B —
@sourcethe JS module (advanced) - Option C — first-party safelist (fallback)
- Verification checklist for downstreams
- Versioning expectations
The overlay code lives in
packages/developer-mode-react/src/index.tsx.
Every Tailwind utility string the overlay renders is exported from
packages/developer-mode-react/src/safelist.ts:
- one named constant per overlay region —
OVERLAY_ROOT_CLASS,OVERLAY_HOVER_OUTLINE_CLASS,OVERLAY_BADGE_CLASS,OVERLAY_CHIP_CLASS,TOAST_CONTAINER_CLASS,TOAST_SUCCESS_TONE_CLASS,TOAST_ERROR_TONE_CLASS - a
DEVELOPER_MODE_OVERLAY_CLASSESarray that aggregates all of them
The overlay JSX in src/index.tsx references those constants directly
via className={OVERLAY_BADGE_CLASS} etc., so the published JS bundle
and the safelist module always agree.
tsdown then emits three artifacts:
| Artifact | Source | Subpath export |
|---|---|---|
dist/safelist.js |
src/safelist.ts |
@viscalyx/developer-mode-react/safelist |
dist/safelist.d.ts |
src/safelist.ts |
(types for the above) |
dist/safelist.css |
generated post-build | @viscalyx/developer-mode-react/safelist.css |
dist/safelist.css is generated by the onSuccess hook in
packages/developer-mode-react/tsdown.config.ts.
It imports the just-built dist/safelist.js and writes one Tailwind v4
@source inline("…") declaration per entry. Because the CSS file is
derived from the same JS module the overlay imports from, the three
artifacts cannot drift.
Two safety nets back this up:
- A drift-guard test in
packages/developer-mode-react/tests/safelist.test.tsscanssrc/index.tsxfor any Tailwind class string literal and fails if a class is inlined instead of being imported from the safelist module. - The CI
safelist-checkjob in.github/workflows/ci.ymlbuilds the React package and re-runs the drift-guard test in isolation, so failures surface as their own signal on every PR.
The .github/instructions/overlay-safelist.instructions.md instruction
file makes the workflow explicit for AI agents working in the repo.
Add a single line to your Tailwind v4 entry CSS:
/* src/styles/globals.css */
@import "tailwindcss";
@import "@viscalyx/developer-mode-react/safelist.css";That is the entire integration. Tailwind v4 reads the
@source inline(...) declarations during CSS compilation and emits the
overlay's utility classes into your CSS bundle.
Properties:
- Build-time only.
safelist.cssis consumed by the Tailwind CSS compiler. There is no JavaScript runtime surface and nothing for the application bundler to ship to the client. - Compatible with the
./noopentry. You can alias@viscalyx/developer-mode-reactto./noop(or to a first-party stub) without touching this@import. The CSS file simply emits unused selectors that compress away. - Survives
npm prune --omit=devas long as the CSS build runs before the prune. Most CI pipelines do exactly that. - Versioned with the package. Upgrading
@viscalyx/developer-mode-reactautomatically updates the safelist.
If you maintain a Tailwind config or CSS-in-JS layer that prefers a JS source-of-truth, the same constants are exported from a TypeScript subpath:
import {
DEVELOPER_MODE_OVERLAY_CLASSES,
OVERLAY_BADGE_CLASS,
} from '@viscalyx/developer-mode-react/safelist'For a Tailwind v4 setup that prefers @source over @import, point at
the published JS file:
/* src/styles/globals.css */
@import "tailwindcss";
@source "../../node_modules/@viscalyx/developer-mode-react/dist/safelist.js";This option is identical in coverage to Option A. Choose it when you
need programmatic access to the individual class strings (e.g. to
re-export them, to feed a Tailwind config generator, or to compose them
with consumer-side classes). It does require
@viscalyx/developer-mode-react to remain in node_modules at CSS
build time.
For air-gapped builds, monorepos that cannot @import from
node_modules, or older versions of @viscalyx/developer-mode-react
that do not yet ship the artifact, copy the class strings into a local
file in your own source tree and @source it from your Tailwind entry
CSS.
The full snippet, the local file template, and the drift-guard test
that catches upstream changes live in
docs/production-noop-guide.md.
When using Option C, treat any package upgrade of
@viscalyx/developer-mode-react as a checkpoint to re-sync the local
file against the published dist/safelist.js.
After wiring up Option A or B:
-
Build the consumer application with developer mode enabled.
ENABLE_DEVELOPER_MODE=true npm run build
-
Optionally simulate a production-style install to confirm the CSS build is independent of
node_modulespost-prune:npm ci --omit=dev ENABLE_DEVELOPER_MODE=false NODE_ENV=production npm run build
(Option A's CSS compilation must happen before the prune; the resulting CSS file is self-contained.)
-
Render a page that mounts
DeveloperModeProvider, pressMod+Alt+Shift+H, and confirm:- the badge renders top-right with rounded border, white background, and uppercase letter spacing;
- hovering an element renders a chip with a coloured border and shadow;
- copying a target produces a styled toast in the bottom-right.
-
Inspect the generated CSS bundle for one of the overlay's arbitrary-value classes (e.g.
bg-white\/92ortracking-\[0\.18em\]). If it is absent, the safelist is not being read by Tailwind — re-check the@import/@sourcepath.
The safelist is part of the package's public API.
- Adding a new constant or extending an existing class string =
minorbump. - Removing or renaming a constant =
majorbump. - The shape of
dist/safelist.css(one@source inline("…");per entry, in the order published byDEVELOPER_MODE_OVERLAY_CLASSES) is stable; layout changes inside the file will be released asmajor.
Consumers who pin a major version of @viscalyx/developer-mode-react
can rely on every documented constant remaining available, even if new
ones are added.