Install
Two commands, one alias, one stylesheet.
Setting up Oxygen UI in a Next.js or Vite project with Tailwind v4.
npx @oxygenui-design/cli initinit writes oxygen.json, which records where your @/ alias points. Every later add copies source relative to it.
What it needs.
19
React
18 is partly covered.
5.7+
TypeScript
Sources ship as .tsx and .ts.
4
Tailwind CSS
CSS-first config. No tailwind.config.js.
2
npm dependencies
clsx and tailwind-merge. Nothing else.
No component in the registry imports a Node built-in, so nothing here requires a polyfill or a server runtime to bundle.
Three things to configure.
01
The path alias
Components import each other and their shared library by the paths the CLI writes. Both files below are what a working build uses.
// tsconfig.json
{ "compilerOptions": { "paths": { "@/*": ["./src/*"] } } }
// vite.config.ts — Vite needs the alias too; Next reads tsconfig directly
resolve: { alias: { "@": path.resolve(__dirname, "src") } }02
Tailwind sources and tokens
Tailwind v4 finds classes by scanning files it knows about. Components installed outside your existing content roots are invisible to it until you say where they went — which shows up as a component that renders with no styling at all.
/* your global stylesheet */
@import "tailwindcss";
@source "./components/oxygen"; /* or wherever oxygen.json put them */
@import "./styles/oxygen-tokens.css"; /* required — the design tokens */
@import "./styles/oxygen-result-value.css"; /* one per component that ships CSS */03
The client boundary
About half the registry declares its own “use client” boundary, so components work unchanged in a React Server Component tree. The ones that do not are pure presentation and render on the server.
// app/page.tsx — a Server Component
import { ResultValue } from "@/components/oxygen/result-value";
// Works. ResultValue declares its own boundary where it needs one.
// You do not need to mark your page "use client" to use these.What we have actually run.
“Untested” below means nobody has run it, not that it is known to fail; “partial” means some of the surface is covered and the rest is not. We would rather name the gap than let you find it.
Next.js 16, App Router
verifiedThis documentation site is built on it, including every live demo on every component page.
Vite 6 + @tailwindcss/vite
verifiedFour components installed from the registry, then vite build and tsc --noEmit, both clean. 40 modules, no polyfills, no shims.
Remix / React Router 7
untestedNothing about the components is Next-specific — no framework imports, no Node built-ins anywhere in the registry — so it is expected to work. We have not run it, so it is listed as untested rather than supported.
React 18
partialThe loaders and tokens build and typecheck against React 18 in a dedicated smoke package. The registry components are authored against React 19 and some use patterns that may not have 18 equivalents; nobody has checked those.
If something is wrong, it is probably one of these.
- The component renders with no styling.
- Tailwind has not been told where the files are. Add @source pointing at your components directory. This is the most common setup failure and it looks like a broken component rather than a missing config line.
- Severity colours are missing, but layout is fine.
- The component's own stylesheet is not imported. Clinical severity is carried by CSS custom properties in styles/oxygen-*.css, not by Tailwind utilities, so it fails independently of the rest.
- Cannot find module '@/lib/oxygen-…'
- The @ alias is missing or points somewhere else. Vite needs it in vite.config.ts as well as tsconfig.json — TypeScript resolving it is not enough for the bundler.
- A hook error inside a Server Component.
- You are importing a non-component export across the boundary. A value exported from a "use client" module becomes an opaque reference in a Server Component; pass it as a prop from a client component instead.
- Styles disappear when a value is dynamic.
- Never build a Tailwind class from a variable. Tailwind resolves classes by scanning source text, so a template literal produces no CSS and severity styling silently vanishes. Map the value to whole class strings.