From dd21283d89f6cb57d8e4ef9001fa9254fadc5555 Mon Sep 17 00:00:00 2001 From: ankit-thesys Date: Wed, 10 Jun 2026 18:39:11 +0530 Subject: [PATCH 01/13] feat(react-ui): add css layer wrap/mirror helpers with BOM strip Co-Authored-By: Claude Sonnet 4.6 --- packages/react-ui/css-layer-utils.mjs | 40 +++++++++++++++++ packages/react-ui/css-layer-utils.test.mjs | 52 ++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 packages/react-ui/css-layer-utils.mjs create mode 100644 packages/react-ui/css-layer-utils.test.mjs diff --git a/packages/react-ui/css-layer-utils.mjs b/packages/react-ui/css-layer-utils.mjs new file mode 100644 index 000000000..a4799c68d --- /dev/null +++ b/packages/react-ui/css-layer-utils.mjs @@ -0,0 +1,40 @@ +import fs from "fs"; +import path from "path"; + +// Wrap a CSS file's contents in @layer openui { ... } if not already wrapped. +// Idempotency check protects watch-mode and back-to-back builds. +export function wrapInLayer(content) { + // Sass emits a UTF-8 BOM for files with non-ASCII output. At byte 0 the + // decoder strips it, but wrapping would push it inside the layer block, + // where U+FEFF parses as an identifier and kills the first rule + // (e.g. the :root theme tokens). Strip it before wrapping. + content = content.replace(/^\uFEFF/, ""); + if (content.trim() === "") return content; + if (/^\s*@layer\s+openui\b/.test(content)) return content; + return `@layer openui{${content}}`; +} + +// Write a layered copy of srcFile at destFile, creating parent directories. +export function writeLayeredCopy(srcFile, destFile) { + const content = fs.readFileSync(srcFile, "utf8"); + fs.mkdirSync(path.dirname(destFile), { recursive: true }); + fs.writeFileSync(destFile, wrapInLayer(content), "utf8"); +} + +// Mirror every top-level *.css file in srcDir into destDir wrapped in +// @layer openui. Files named in `unwrapped` are copied verbatim — they must +// stay in the unlayered cascade (openui-defaults.css backs the runtime +// theming override contract). Non-CSS files (e.g. cssUtils.scss) are skipped. +export function mirrorStylesWithLayer(srcDir, destDir, unwrapped = ["openui-defaults.css"]) { + fs.mkdirSync(destDir, { recursive: true }); + for (const name of fs.readdirSync(srcDir)) { + if (!name.endsWith(".css")) continue; + const src = path.join(srcDir, name); + if (!fs.statSync(src).isFile()) continue; + if (unwrapped.includes(name)) { + fs.copyFileSync(src, path.join(destDir, name)); + } else { + writeLayeredCopy(src, path.join(destDir, name)); + } + } +} diff --git a/packages/react-ui/css-layer-utils.test.mjs b/packages/react-ui/css-layer-utils.test.mjs new file mode 100644 index 000000000..fa4e4be63 --- /dev/null +++ b/packages/react-ui/css-layer-utils.test.mjs @@ -0,0 +1,52 @@ +import { describe, expect, it } from "vitest"; +import fs from "fs"; +import os from "os"; +import path from "path"; +import { mirrorStylesWithLayer, wrapInLayer, writeLayeredCopy } from "./css-layer-utils.mjs"; + +describe("wrapInLayer", () => { + it("wraps plain css in @layer openui", () => { + expect(wrapInLayer(".a{color:red}")).toBe("@layer openui{.a{color:red}}"); + }); + + it("strips a leading BOM before wrapping so the first rule stays valid", () => { + // U+FEFF inside a layer block parses as an identifier and kills the + // first rule (e.g. the :root theme tokens) — the 2026-06 BOM incident. + expect(wrapInLayer("\uFEFF:root{--x:1}")).toBe("@layer openui{:root{--x:1}}"); + }); + + it("is idempotent", () => { + const once = wrapInLayer(".a{color:red}"); + expect(wrapInLayer(once)).toBe(once); + }); + + it("leaves empty/whitespace-only content untouched", () => { + expect(wrapInLayer("")).toBe(""); + expect(wrapInLayer(" \n")).toBe(" \n"); + }); +}); + +describe("writeLayeredCopy", () => { + it("writes a wrapped copy, creating parent directories", () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "css-layer-")); + const src = path.join(dir, "in.css"); + const dest = path.join(dir, "nested", "out.css"); + fs.writeFileSync(src, ".a{color:red}"); + writeLayeredCopy(src, dest); + expect(fs.readFileSync(dest, "utf8")).toBe("@layer openui{.a{color:red}}"); + }); +}); + +describe("mirrorStylesWithLayer", () => { + it("wraps css files, copies unwrapped names verbatim, skips non-css", () => { + const src = fs.mkdtempSync(path.join(os.tmpdir(), "css-layer-src-")); + const dest = path.join(fs.mkdtempSync(path.join(os.tmpdir(), "css-layer-dest-")), "layered"); + fs.writeFileSync(path.join(src, "button.css"), ".b{color:red}"); + fs.writeFileSync(path.join(src, "openui-defaults.css"), ":root{--x:1}"); + fs.writeFileSync(path.join(src, "cssUtils.scss"), "$x: 1;"); + mirrorStylesWithLayer(src, dest); + expect(fs.readFileSync(path.join(dest, "button.css"), "utf8")).toBe("@layer openui{.b{color:red}}"); + expect(fs.readFileSync(path.join(dest, "openui-defaults.css"), "utf8")).toBe(":root{--x:1}"); + expect(fs.existsSync(path.join(dest, "cssUtils.scss"))).toBe(false); + }); +}); From bc3f6b48d710f9568b5c34e51e74e41f988e07dc Mon Sep 17 00:00:00 2001 From: ankit-thesys Date: Wed, 10 Jun 2026 18:46:44 +0530 Subject: [PATCH 02/13] feat(react-ui): emit layered CSS mirror under dist/layered, keep defaults unlayered Co-Authored-By: Claude Sonnet 4.6 --- packages/react-ui/cp-css.js | 39 ++++++++++--------------------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/packages/react-ui/cp-css.js b/packages/react-ui/cp-css.js index dc8f9b78a..2c645f860 100644 --- a/packages/react-ui/cp-css.js +++ b/packages/react-ui/cp-css.js @@ -2,6 +2,7 @@ import fs from "fs"; import { camelCase } from "lodash-es"; import path from "path"; import { fileURLToPath } from "url"; +import { mirrorStylesWithLayer, writeLayeredCopy } from "./css-layer-utils.mjs"; const dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -12,29 +13,6 @@ function ensureDirectoryExists(dirPath) { } } -// Wrap a CSS file's contents in @layer openui { ... } if not already wrapped. -// Idempotency check protects watch-mode and back-to-back builds. -function wrapInLayer(content) { - if (content.trim() === "") return content; - if (/^\s*@layer\s+openui\b/.test(content)) return content; - return `@layer openui{${content}}`; -} - -// Walk dist/components and wrap every emitted .css file in @layer openui. -// *.module.css are Storybook CSS Modules — locally scoped, not shipped, not wrapped. -function wrapComponentCssInPlace(dir) { - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { - const full = path.join(dir, entry.name); - if (entry.isDirectory()) { - wrapComponentCssInPlace(full); - } else if (entry.name.endsWith(".css") && !entry.name.endsWith(".module.css")) { - const content = fs.readFileSync(full, "utf8"); - const wrapped = wrapInLayer(content); - if (wrapped !== content) fs.writeFileSync(full, wrapped, "utf8"); - } - } -} - // Replace .scss imports with .css imports in compiled JS files function fixScssImportsInJs(dir) { const entries = fs.readdirSync(dir); @@ -60,12 +38,6 @@ function copyCssFiles() { const srcDir = path.join(dirname, "dist", "components"); const distDir = path.join(dirname, "dist", "styles"); - // Wrap every emitted component CSS in @layer openui before copying. - // dist/openui-defaults.css lives outside dist/components and stays unwrapped - // so the defaults.css export remains in the unlayered cascade — matching the - // ThemeProvider runtime injection contract. - wrapComponentCssInPlace(srcDir); - // Ensure the dist/styles directory exists ensureDirectoryExists(distDir); @@ -101,6 +73,15 @@ function copyCssFiles() { fs.copyFileSync(defaultsCssPath, path.join(distDir, "openui-defaults.css")); } + // Emit the opt-in layered mirror (./layered-components.css and + // ./layered/styles/*). The default exports above stay unlayered — see + // README "Styling integration". + writeLayeredCopy( + path.join(srcDir, "index.css"), + path.join(dirname, "dist", "layered", "components", "index.css"), + ); + mirrorStylesWithLayer(distDir, path.join(dirname, "dist", "layered", "styles")); + // Fix .scss imports in compiled JS to point to .css files instead fixScssImportsInJs(path.join(dirname, "dist")); } From fe2689349e008854c7860397f171245126961b96 Mon Sep 17 00:00:00 2001 From: ankit-thesys Date: Wed, 10 Jun 2026 18:55:07 +0530 Subject: [PATCH 03/13] feat(react-ui): export layered-components.css and layered/styles/*, drop browserslist Co-Authored-By: Claude Sonnet 4.6 --- packages/react-ui/package.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/react-ui/package.json b/packages/react-ui/package.json index 9c3889a47..b3c8205ce 100644 --- a/packages/react-ui/package.json +++ b/packages/react-ui/package.json @@ -21,12 +21,18 @@ "./components.css": { "default": "./dist/components/index.css" }, + "./layered-components.css": { + "default": "./dist/layered/components/index.css" + }, "./defaults.css": { "default": "./dist/styles/openui-defaults.css" }, "./styles/*": { "default": "./dist/styles/*" }, + "./layered/styles/*": { + "default": "./dist/layered/styles/*" + }, "./genui-lib": { "import": { "types": "./dist/genui-lib/index.d.mts", @@ -60,7 +66,7 @@ "README.md" ], "scripts": { - "test": "vitest run --passWithNoTests", + "test": "vitest run", "copy-css": "node cp-css.js", "generate-scss-index": "node src/scripts/scss-import.js", "generate:css-utils": "tsx src/scripts/generate-css-utils.ts", @@ -76,10 +82,11 @@ "lint:fix": "eslint ./src --fix", "format:fix": "prettier --write ./src", "format:check": "prettier --check ./src", + "check:css": "node check-css-artifacts.js", "check:publint": "publint", "check:attw": "attw --pack . --ignore-rules no-resolution", "prepare": "pnpm run build", - "prepublishOnly": "pnpm run check:publint && pnpm run check:attw", + "prepublishOnly": "pnpm run check:css && pnpm run check:publint && pnpm run check:attw", "ci": "pnpm run lint:check && pnpm run format:check" }, "peerDependencies": { @@ -196,7 +203,6 @@ "bugs": { "url": "https://github.com/thesysdev/openui/issues" }, - "browserslist": "defaults and supports css-cascade-layers", "eslintConfig": { "extends": [ "plugin:storybook/recommended" From 938e732e7239912535173dac6008a8ca77f461a3 Mon Sep 17 00:00:00 2001 From: ankit-thesys Date: Wed, 10 Jun 2026 19:00:40 +0530 Subject: [PATCH 04/13] feat(react-ui): add prepublish CSS artifact guard (layer wrap, BOM, defaults contract) Co-Authored-By: Claude Sonnet 4.6 --- packages/react-ui/check-css-artifacts.js | 63 ++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 packages/react-ui/check-css-artifacts.js diff --git a/packages/react-ui/check-css-artifacts.js b/packages/react-ui/check-css-artifacts.js new file mode 100644 index 000000000..1f02eeb33 --- /dev/null +++ b/packages/react-ui/check-css-artifacts.js @@ -0,0 +1,63 @@ +// Pre-publish guard for the CSS artifact contract: +// - default exports stay UNLAYERED (./components.css, ./styles/*) +// - the layered mirror is wrapped in @layer openui and BOM-free +// - openui-defaults.css is unlayered in both trees (runtime theming contract) +// Born out of the 2026-06 BOM incident: a U+FEFF pushed inside the layer +// block silently killed the :root theme tokens in the packed tarball. +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +const dirname = path.dirname(fileURLToPath(import.meta.url)); +const dist = path.join(dirname, "dist"); +const failures = []; + +const read = (rel) => fs.readFileSync(path.join(dist, rel), "utf8"); +const assert = (cond, msg) => { + if (!cond) failures.push(msg); +}; + +assert( + !/^\s*@layer/.test(read("components/index.css")), + "components/index.css must stay unlayered", +); +assert(!/^\s*@layer/.test(read("styles/index.css")), "styles/index.css must stay unlayered"); +assert( + read("layered/components/index.css").startsWith("@layer openui{"), + "layered/components/index.css must start with @layer openui{", +); +assert( + !/^\s*@layer/.test(read("styles/openui-defaults.css")), + "styles/openui-defaults.css must stay unlayered", +); +assert( + !/^\s*@layer/.test(read("layered/styles/openui-defaults.css")), + "layered/styles/openui-defaults.css must stay unlayered", +); + +const unlayered = fs.readdirSync(path.join(dist, "styles")).filter((f) => f.endsWith(".css")); +const layered = fs + .readdirSync(path.join(dist, "layered", "styles")) + .filter((f) => f.endsWith(".css")); +assert( + unlayered.length === layered.length, + `layered mirror has ${layered.length} css files, unlayered has ${unlayered.length}`, +); + +for (const f of [ + ...layered.map((n) => path.join("layered", "styles", n)), + "layered/components/index.css", +]) { + const content = read(f); + assert(!content.includes("\uFEFF"), `${f} contains a BOM`); + const base = path.basename(f); + if (base !== "openui-defaults.css" && content.trim() !== "") { + assert(content.startsWith("@layer openui{"), `${f} is not wrapped in @layer openui`); + } +} + +if (failures.length > 0) { + console.error("CSS artifact check FAILED:\n - " + failures.join("\n - ")); + process.exit(1); +} +console.log(`CSS artifact check passed (${layered.length} layered files verified).`); From 3c265440fbe2b24d333a3abf6f8e2683fceec576 Mon Sep 17 00:00:00 2001 From: ankit-thesys Date: Wed, 10 Jun 2026 19:07:55 +0530 Subject: [PATCH 05/13] docs(react-ui): document dual unlayered/layered styling modes in README Co-Authored-By: Claude Sonnet 4.6 --- packages/react-ui/README.md | 25 +++++++++++++------ .../ThemeProvider/ThemeProvider.tsx | 5 ++-- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/react-ui/README.md b/packages/react-ui/README.md index 2f3044661..16a69ec63 100644 --- a/packages/react-ui/README.md +++ b/packages/react-ui/README.md @@ -141,34 +141,45 @@ function App() { ## Styling integration -OpenUI's component styles live inside a CSS cascade layer named `openui`. Any unlayered consumer CSS overrides OpenUI without `!important` or specificity matching: +OpenUI ships its component styles in two variants: + +| Import | Cascade behavior | +| --- | --- | +| `@openuidev/react-ui/components.css` (default) | Unlayered — override via normal CSS specificity, as in 0.11.x and earlier | +| `@openuidev/react-ui/layered-components.css` (opt-in) | Wrapped in `@layer openui` — any unlayered consumer CSS wins | + +Per-component granular imports follow the same split: `./styles/*` (unlayered) and `./layered/styles/*` (layered). + +With the layered variant, plain CSS overrides OpenUI without `!important` or specificity matching: ```css -@import "@openuidev/react-ui/components.css"; +@import "@openuidev/react-ui/layered-components.css"; /* Wins, no specificity tricks needed */ .openui-button-base-primary { background: hotpink; } ``` -### With Tailwind v4 +### With Tailwind v4 (layered variant) Declare layer order at the top of your entry stylesheet so `openui` sits above Tailwind's reset but below `components` and `utilities`: ```css @layer theme, base, openui, components, utilities; -@import "@openuidev/react-ui/components.css"; +@import "@openuidev/react-ui/layered-components.css"; @import "tailwindcss"; ``` This places Tailwind's Preflight (in `base`) below OpenUI components so its element resets don't override them, while keeping utilities (`bg-red-500`, etc.) winning over OpenUI styles. -### With Tailwind v3, CSS Modules, or CSS-in-JS +### Rules for the layered variant -No configuration needed — these all emit unlayered CSS, which automatically beats anything in `@layer openui`. +- Import OpenUI CSS from **exactly one place** — multiple import sites under chunk-splitting bundlers (e.g. Turbopack) can register `openui` before your layer-order statement and lock the wrong order. +- Wrap app-wide resets in a layer below `openui` (e.g. `@layer base { * { margin: 0; } }`) — unlayered resets beat all layered styles regardless of specificity. +- `./defaults.css` and the `ThemeProvider` runtime style injection stay unlayered in both modes so runtime theming always overrides component defaults. ### Browser support -CSS cascade layers require Chrome 99+, Firefox 97+, Safari 15.4+, or Edge 99+ (all baseline from March 2022). On older browsers, the `@layer { ... }` block is dropped entirely and components render unstyled. The package declares this floor via the `browserslist` field in its `package.json`. +The layered variant requires CSS cascade layers: Chrome 99+, Firefox 97+, Safari 15.4+, Edge 99+ (all baseline from March 2022). On older browsers the `@layer { ... }` block is dropped entirely and components render unstyled. The default unlayered styles have no such floor. ## Components diff --git a/packages/react-ui/src/components/ThemeProvider/ThemeProvider.tsx b/packages/react-ui/src/components/ThemeProvider/ThemeProvider.tsx index 424fab01a..d9e54675d 100644 --- a/packages/react-ui/src/components/ThemeProvider/ThemeProvider.tsx +++ b/packages/react-ui/src/components/ThemeProvider/ThemeProvider.tsx @@ -236,8 +236,9 @@ export const ThemeProvider = ({ const useAutoScope = isNested && !hasExplicitSelector; const styleSelector = useAutoScope ? `.${scopedClassName}` : effectiveCssSelector; - // Intentionally unlayered — must override @layer openui so runtime theme - // switching takes effect. See README "Styling integration" before changing. + // Intentionally unlayered — must override component styles in both modes, + // including when consumers opt into layered-components.css (@layer openui), + // so runtime theming always wins. See README "Styling integration" before changing. useInsertionEffect(() => { const style = document.createElement("style"); style.setAttribute("data-openui-theme", id); From 982ce127e326161ddebf3db1a9561937417273d8 Mon Sep 17 00:00:00 2001 From: ankit-thesys Date: Wed, 10 Jun 2026 19:12:10 +0530 Subject: [PATCH 06/13] docs(react-ui): add layered and defaults entries to Subpath Exports table Co-Authored-By: Claude Sonnet 4.6 --- packages/react-ui/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/react-ui/README.md b/packages/react-ui/README.md index 16a69ec63..22e8692b9 100644 --- a/packages/react-ui/README.md +++ b/packages/react-ui/README.md @@ -211,9 +211,12 @@ import { Charts } from "@openuidev/react-ui/Charts"; | :--- | :--- | | `@openuidev/react-ui` | All components and libraries | | `@openuidev/react-ui/components.css` | Compiled component styles | +| `@openuidev/react-ui/layered-components.css` | Opt-in aggregate stylesheet wrapped in `@layer openui` | +| `@openuidev/react-ui/defaults.css` | Theme tokens, always unlayered | | `@openuidev/react-ui/genui-lib` | OpenUI Lang libraries and prompt options | | `@openuidev/react-ui/tailwind` | Tailwind CSS plugin | | `@openuidev/react-ui/styles/*` | SCSS utilities | +| `@openuidev/react-ui/layered/styles/*` | Per-component styles wrapped in `@layer openui` | | `@openuidev/react-ui/scssUtils` | SCSS utility functions | | `@openuidev/react-ui/` | Per-component entry points | From b1a5dafc712acf57414e2042693e58a78f997d81 Mon Sep 17 00:00:00 2001 From: ankit-thesys Date: Wed, 10 Jun 2026 19:14:31 +0530 Subject: [PATCH 07/13] docs: describe default unlayered + opt-in layered styling modes Co-Authored-By: Claude Sonnet 4.6 --- docs/content/docs/api-reference/react-ui.mdx | 6 ++++-- docs/content/docs/chat/installation.mdx | 18 +++++++++++++++--- docs/content/docs/chat/theming.mdx | 8 +++++--- .../docs/openui-lang/standard-library.mdx | 2 +- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/docs/content/docs/api-reference/react-ui.mdx b/docs/content/docs/api-reference/react-ui.mdx index 629c89f5d..c5c2d8c02 100644 --- a/docs/content/docs/api-reference/react-ui.mdx +++ b/docs/content/docs/api-reference/react-ui.mdx @@ -13,9 +13,11 @@ import { Copilot, FullScreen, BottomTray } from "@openuidev/react-ui"; ### Cascade-layer contract -`@openuidev/react-ui/components.css` wraps every component rule in `@layer openui`. Unlayered consumer CSS overrides OpenUI components without specificity matching or `!important`. See [Chat → Theming](/docs/chat/theming#override-component-styles-with-css) for override patterns, and [Chat → Installation](/docs/chat/installation#3-for-tailwind-v4-set-the-cascade-layer-order) for the Tailwind v4 layer-order setup. +The default stylesheet exports (`./components.css`, `./styles/*`) are **unlayered**. Two parallel exports — `@openuidev/react-ui/layered-components.css` and `@openuidev/react-ui/layered/styles/*` — ship the same rules wrapped in `@layer openui`; with the layered variant, unlayered consumer CSS overrides OpenUI components without specificity matching or `!important`. Import OpenUI CSS (either variant) from exactly one place in your app — multiple import sites under chunk-splitting bundlers can register cascade layers in the wrong order. See [Chat → Theming](/docs/chat/theming#override-component-styles-with-css) for override patterns, and [Chat → Installation](/docs/chat/installation#3-optional-opt-into-cascade-layered-styles) for the Tailwind v4 layer-order setup. -Browser support: Chrome 99+, Firefox 97+, Safari 15.4+, Edge 99+ (all baseline from March 2022). +`./defaults.css` and the `ThemeProvider` runtime style injection are always unlayered, so theme tokens override component defaults in both modes. + +Browser support for the layered variant: Chrome 99+, Firefox 97+, Safari 15.4+, Edge 99+ (all baseline from March 2022); older browsers drop the `@layer` block entirely. The default unlayered styles have no such floor. ## Layout components diff --git a/docs/content/docs/chat/installation.mdx b/docs/content/docs/chat/installation.mdx index 1f52d2ee2..0328aaadb 100644 --- a/docs/content/docs/chat/installation.mdx +++ b/docs/content/docs/chat/installation.mdx @@ -57,9 +57,16 @@ export default function RootLayout({ children }: { children: React.ReactNode }) These imports give you the default chat layout styling and theme tokens. -## 3. (For Tailwind v4) Set the cascade-layer order +## 3. (Optional) Opt into cascade-layered styles -OpenUI's component styles live in `@layer openui`. If your app uses Tailwind v4, declare the layer order in your global stylesheet so `openui` sits above Tailwind's reset (`base`) but below `components` and `utilities`: +The default import above ships **unlayered** CSS — overrides work via normal specificity, exactly as in earlier versions. If you prefer your plain app CSS to win over OpenUI styles without specificity matching (recommended for Tailwind v4 apps), import the **layered** variant instead: + +```tsx +import "@openuidev/react-ui/layered-components.css"; +import "./globals.css"; +``` + +With Tailwind v4, also declare the layer order in your global stylesheet so `openui` sits above Tailwind's reset (`base`) but below `components` and `utilities`: ```css /* app/globals.css */ @@ -69,7 +76,12 @@ OpenUI's component styles live in `@layer openui`. If your app uses Tailwind v4, This places OpenUI above Tailwind's Preflight (so its element resets don't override component styles) while keeping Tailwind utilities like `bg-red-500` winning over OpenUI. Without this declaration, the cascade order is bundler-dependent and `openui` may end up declared *after* `utilities`, which prevents utility overrides from taking effect. -Tailwind v3, plain CSS, CSS Modules, and CSS-in-JS need no configuration — their styles are unlayered and beat anything in `@layer openui` automatically. +Two rules when using the layered variant: + +- Import OpenUI CSS from **exactly one place** (your root layout or global stylesheet, not both) — multiple import sites under chunk-splitting bundlers like Turbopack can lock the wrong layer order. +- Wrap your own global resets in a layer below `openui` (e.g. `@layer base { ... }`) — unlayered resets beat all layered styles regardless of specificity. + +The layered variant requires cascade-layer support: Chrome 99+, Firefox 97+, Safari 15.4+, Edge 99+ (March 2022 baseline). Tailwind v3, plain CSS, CSS Modules, and CSS-in-JS need no configuration with either variant. See [`@openuidev/react-ui`](/docs/api-reference/react-ui#cascade-layer-contract) for the full styling integration contract. diff --git a/docs/content/docs/chat/theming.mdx b/docs/content/docs/chat/theming.mdx index 81218c053..fa51cb762 100644 --- a/docs/content/docs/chat/theming.mdx +++ b/docs/content/docs/chat/theming.mdx @@ -56,7 +56,9 @@ import { FullScreen } from "@openuidev/react-ui"; ## Override component styles with CSS -OpenUI's component styles live in `@layer openui`. Any unlayered consumer CSS overrides them without `!important` or matching specificity: +By default OpenUI ships **unlayered** styles: override them like any other stylesheet — with a selector of equal or higher specificity loaded after the component CSS. + +If you import the **layered** variant (`@openuidev/react-ui/layered-components.css`), all component styles live in `@layer openui`, and any unlayered consumer CSS overrides them without `!important` or specificity matching: ```css .openui-button-base-primary { @@ -64,9 +66,9 @@ OpenUI's component styles live in `@layer openui`. Any unlayered consumer CSS ov } ``` -For Tailwind v4 apps, declare `@layer theme, base, openui, components, utilities;` ahead of `@import "tailwindcss";` in your global stylesheet (see [Installation](/docs/chat/installation#3-for-tailwind-v4-set-the-cascade-layer-order)) so utility classes also override OpenUI styles. CSS Modules, CSS-in-JS, and Tailwind v3 utilities emit unlayered CSS and override OpenUI automatically with no configuration. +For Tailwind v4 apps using the layered variant, declare `@layer theme, base, openui, components, utilities;` ahead of `@import "tailwindcss";` in your global stylesheet (see [Installation](/docs/chat/installation#3-optional-opt-into-cascade-layered-styles)) so utility classes also override OpenUI styles. CSS Modules, CSS-in-JS, and Tailwind v3 utilities emit unlayered CSS and override the layered variant automatically with no configuration. -Browser support: Chrome 99+, Firefox 97+, Safari 15.4+, Edge 99+ (all baseline from March 2022). +Browser support for the layered variant: Chrome 99+, Firefox 97+, Safari 15.4+, Edge 99+ (all baseline from March 2022). The default unlayered styles have no browser floor.
diff --git a/docs/content/docs/openui-lang/standard-library.mdx b/docs/content/docs/openui-lang/standard-library.mdx index 3bebcde91..26bc6408c 100644 --- a/docs/content/docs/openui-lang/standard-library.mdx +++ b/docs/content/docs/openui-lang/standard-library.mdx @@ -21,7 +21,7 @@ import { openuiLibrary } from "@openuidev/react-ui"; ; ``` -The compiled stylesheet wraps component rules in `@layer openui` so your own CSS overrides them without specificity matching. See [Chat → Theming](/docs/chat/theming#override-component-styles-with-css) for the override contract, or [Chat → Installation](/docs/chat/installation#3-for-tailwind-v4-set-the-cascade-layer-order) for the Tailwind v4 setup. +The compiled stylesheet wraps component rules in `@layer openui` so your own CSS overrides them without specificity matching. See [Chat → Theming](/docs/chat/theming#override-component-styles-with-css) for the override contract, or [Chat → Installation](/docs/chat/installation#3-optional-opt-into-cascade-layered-styles) for the Tailwind v4 setup. ## Generate prompt From 6d5a83f5ee16416d3ce1504b8f9baf5b2f556b35 Mon Sep 17 00:00:00 2001 From: ankit-thesys Date: Wed, 10 Jun 2026 19:15:36 +0530 Subject: [PATCH 08/13] docs: fix stale layered-css claim in standard-library page --- docs/content/docs/openui-lang/standard-library.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/docs/openui-lang/standard-library.mdx b/docs/content/docs/openui-lang/standard-library.mdx index 26bc6408c..b9ab590fe 100644 --- a/docs/content/docs/openui-lang/standard-library.mdx +++ b/docs/content/docs/openui-lang/standard-library.mdx @@ -21,7 +21,7 @@ import { openuiLibrary } from "@openuidev/react-ui"; ; ``` -The compiled stylesheet wraps component rules in `@layer openui` so your own CSS overrides them without specificity matching. See [Chat → Theming](/docs/chat/theming#override-component-styles-with-css) for the override contract, or [Chat → Installation](/docs/chat/installation#3-optional-opt-into-cascade-layered-styles) for the Tailwind v4 setup. +The `components.css` import ships unlayered styles, so you can override them via normal CSS specificity. If you prefer cascade-layered styles, import `@openuidev/react-ui/layered-components.css` instead — see [Chat → Installation](/docs/chat/installation#3-optional-opt-into-cascade-layered-styles) for details. ## Generate prompt From 7d89eee4afa952676d6ba9a9e5138c098eeb5e23 Mon Sep 17 00:00:00 2001 From: ankit-thesys Date: Wed, 10 Jun 2026 23:02:19 +0530 Subject: [PATCH 09/13] docs(examples): switch layer-recipe apps to layered-components.css, single import site Co-Authored-By: Claude Sonnet 4.6 --- docs/app/global.css | 2 +- examples/hands-on-table-chat/src/app/page.tsx | 27 ++++++----------- examples/mastra-chat/src/app/page.tsx | 2 +- examples/multi-agent-chat/src/app/globals.css | 2 +- examples/multi-agent-chat/src/app/page.tsx | 29 ++++++------------- .../openui-artifact-demo/src/app/page.tsx | 16 ++++------ examples/openui-chat/src/app/page.tsx | 2 +- .../src/components/OpenUIDashboard/index.tsx | 2 +- examples/shadcn-chat/src/app/page.tsx | 2 +- examples/supabase-chat/src/app/page.tsx | 2 +- examples/vercel-ai-chat/src/app/globals.css | 2 +- examples/vercel-ai-chat/src/app/page.tsx | 21 +++++--------- 12 files changed, 40 insertions(+), 69 deletions(-) diff --git a/docs/app/global.css b/docs/app/global.css index 14b7b4f9b..acaf5db5f 100644 --- a/docs/app/global.css +++ b/docs/app/global.css @@ -10,7 +10,7 @@ @import "fumadocs-ui/css/neutral.css"; @import "fumadocs-ui/css/preset.css"; @import "../shared/design-system/styles/variables/index.css"; -@import "@openuidev/react-ui/styles/index.css"; +@import "@openuidev/react-ui/layered/styles/index.css"; body { --color-fd-background: var(--openui-background); diff --git a/examples/hands-on-table-chat/src/app/page.tsx b/examples/hands-on-table-chat/src/app/page.tsx index c07c8cdb1..18883a032 100644 --- a/examples/hands-on-table-chat/src/app/page.tsx +++ b/examples/hands-on-table-chat/src/app/page.tsx @@ -1,18 +1,15 @@ "use client"; -import "@openuidev/react-ui/components.css"; -import { openAIMessageFormat, openAIAdapter } from "@openuidev/react-headless"; -import { Copilot } from "@openuidev/react-ui"; import { spreadsheetLibrary } from "@/lib/spreadsheet-library"; -import { TableProvider, useTableContext } from "./TableContext"; -import { useState, useEffect, useCallback } from "react"; +import { openAIAdapter, openAIMessageFormat } from "@openuidev/react-headless"; +import { Copilot } from "@openuidev/react-ui"; +import "@openuidev/react-ui/layered-components.css"; import { MessageSquare, PanelRightClose } from "lucide-react"; import dynamic from "next/dynamic"; +import { useCallback, useEffect, useState } from "react"; +import { TableProvider, useTableContext } from "./TableContext"; -const PersistentSpreadsheet = dynamic( - () => import("./PersistentSpreadsheet"), - { ssr: false } -); +const PersistentSpreadsheet = dynamic(() => import("./PersistentSpreadsheet"), { ssr: false }); function ChatPanel({ onClose }: { onClose: () => void }) { const { threadId } = useTableContext(); @@ -42,16 +39,14 @@ function ChatPanel({ onClose }: { onClose: () => void }) { agentName="Spreadsheet AI" welcomeMessage={{ title: "Spreadsheet AI", - description: - "I can help you analyze, visualize, and modify your product revenue data.", + description: "I can help you analyze, visualize, and modify your product revenue data.", }} conversationStarters={{ variant: "long", options: [ { displayText: "Chart revenue by quarter", - prompt: - "Show me a bar chart comparing Q1 through Q4 revenue for all products.", + prompt: "Show me a bar chart comparing Q1 through Q4 revenue for all products.", }, { displayText: "Add Vision Pro to the lineup", @@ -102,11 +97,7 @@ export default function Home() { {chatOpen && } {!chatOpen && ( - diff --git a/examples/mastra-chat/src/app/page.tsx b/examples/mastra-chat/src/app/page.tsx index 14e9ffc63..6aaf5a093 100644 --- a/examples/mastra-chat/src/app/page.tsx +++ b/examples/mastra-chat/src/app/page.tsx @@ -1,5 +1,5 @@ "use client"; -import "@openuidev/react-ui/components.css"; +import "@openuidev/react-ui/layered-components.css"; import { useTheme } from "@/hooks/use-system-theme"; import { agUIAdapter } from "@openuidev/react-headless"; diff --git a/examples/multi-agent-chat/src/app/globals.css b/examples/multi-agent-chat/src/app/globals.css index c45470e58..7e1cd2a6f 100644 --- a/examples/multi-agent-chat/src/app/globals.css +++ b/examples/multi-agent-chat/src/app/globals.css @@ -1,3 +1,3 @@ @layer theme, base, openui, components, utilities; @import "tailwindcss"; -@import "@openuidev/react-ui/components.css"; +@import "@openuidev/react-ui/layered-components.css"; diff --git a/examples/multi-agent-chat/src/app/page.tsx b/examples/multi-agent-chat/src/app/page.tsx index d60879ff6..e0b76e74a 100644 --- a/examples/multi-agent-chat/src/app/page.tsx +++ b/examples/multi-agent-chat/src/app/page.tsx @@ -1,18 +1,16 @@ "use client"; -import "@openuidev/react-ui/components.css"; - -import { useChat } from "@ai-sdk/react"; -import { useRef, useEffect, useState } from "react"; -import { useTheme } from "@/hooks/use-system-theme"; -import { useThreads } from "@/hooks/use-threads"; +import { AssistantMessage } from "@/components/assistant-message"; import { ChatHeader } from "@/components/chat-header"; import { ChatInput } from "@/components/chat-input"; import { ConversationStarters } from "@/components/conversation-starters"; -import { AssistantMessage } from "@/components/assistant-message"; -import { UserMessage } from "@/components/user-message"; -import { ThinkingIndicator } from "@/components/thinking-indicator"; import { Sidebar } from "@/components/sidebar"; +import { ThinkingIndicator } from "@/components/thinking-indicator"; +import { UserMessage } from "@/components/user-message"; +import { useTheme } from "@/hooks/use-system-theme"; +import { useThreads } from "@/hooks/use-threads"; +import { useChat } from "@ai-sdk/react"; +import { useEffect, useRef, useState } from "react"; export default function Page() { useTheme(); @@ -88,10 +86,7 @@ export default function Page() { sidebarOpen ? "md:ml-[280px]" : "ml-0" }`} > - setSidebarOpen((o) => !o)} - /> + setSidebarOpen((o) => !o)} />
{isEmpty ? ( @@ -100,13 +95,7 @@ export default function Page() {
{messages.map((m) => { if (m.role === "assistant") { - return ( - - ); + return ; } if (m.role === "user") { return ; diff --git a/examples/openui-artifact-demo/src/app/page.tsx b/examples/openui-artifact-demo/src/app/page.tsx index a68e6c6f9..b4a173e23 100644 --- a/examples/openui-artifact-demo/src/app/page.tsx +++ b/examples/openui-artifact-demo/src/app/page.tsx @@ -1,10 +1,10 @@ "use client"; -import "@openuidev/react-ui/components.css"; +import "@openuidev/react-ui/layered-components.css"; import { useTheme } from "@/hooks/use-system-theme"; +import { artifactDemoLibrary } from "@/library"; import { openAIAdapter, openAIMessageFormat } from "@openuidev/react-headless"; import { FullScreen } from "@openuidev/react-ui"; -import { artifactDemoLibrary } from "@/library"; export default function Page() { const mode = useTheme(); @@ -31,23 +31,19 @@ export default function Page() { options: [ { displayText: "React login form", - prompt: - "Build me a React login form with email and password validation", + prompt: "Build me a React login form with email and password validation", }, { displayText: "Python REST API", - prompt: - "Create a FastAPI REST API with CRUD endpoints for a todo app", + prompt: "Create a FastAPI REST API with CRUD endpoints for a todo app", }, { displayText: "CSS animation", - prompt: - "Write a CSS animation for a bouncing loading indicator", + prompt: "Write a CSS animation for a bouncing loading indicator", }, { displayText: "SQL schema", - prompt: - "Design a SQL schema for a blog with users, posts, and comments", + prompt: "Design a SQL schema for a blog with users, posts, and comments", }, ], }} diff --git a/examples/openui-chat/src/app/page.tsx b/examples/openui-chat/src/app/page.tsx index 29cc3daf4..87253bb38 100644 --- a/examples/openui-chat/src/app/page.tsx +++ b/examples/openui-chat/src/app/page.tsx @@ -1,5 +1,5 @@ "use client"; -import "@openuidev/react-ui/components.css"; +import "@openuidev/react-ui/layered-components.css"; import { useTheme } from "@/hooks/use-system-theme"; import { openAIAdapter, openAIMessageFormat } from "@openuidev/react-headless"; diff --git a/examples/openui-dashboard/src/components/OpenUIDashboard/index.tsx b/examples/openui-dashboard/src/components/OpenUIDashboard/index.tsx index 1fc4de287..2a0588d92 100644 --- a/examples/openui-dashboard/src/components/OpenUIDashboard/index.tsx +++ b/examples/openui-dashboard/src/components/OpenUIDashboard/index.tsx @@ -2,7 +2,7 @@ import type { Starter } from "@/starters"; import type { Library } from "@openuidev/react-lang"; -import "@openuidev/react-ui/components.css"; +import "@openuidev/react-ui/layered-components.css"; import { useRef, useState } from "react"; import { DashboardProvider, useDashboard } from "./context"; import { ConversationPanel } from "./ConversationPanel"; diff --git a/examples/shadcn-chat/src/app/page.tsx b/examples/shadcn-chat/src/app/page.tsx index a59a3d9ee..ccc91e26c 100644 --- a/examples/shadcn-chat/src/app/page.tsx +++ b/examples/shadcn-chat/src/app/page.tsx @@ -1,5 +1,5 @@ "use client"; -import "@openuidev/react-ui/components.css"; +import "@openuidev/react-ui/layered-components.css"; import { useTheme } from "@/hooks/use-system-theme"; import { shadcnChatLibrary } from "@/lib/shadcn-genui"; diff --git a/examples/supabase-chat/src/app/page.tsx b/examples/supabase-chat/src/app/page.tsx index 7f503c18c..ad49df06a 100644 --- a/examples/supabase-chat/src/app/page.tsx +++ b/examples/supabase-chat/src/app/page.tsx @@ -1,6 +1,6 @@ "use client"; -import "@openuidev/react-ui/components.css"; +import "@openuidev/react-ui/layered-components.css"; import { openAIAdapter, openAIMessageFormat } from "@openuidev/react-headless"; import { FullScreen } from "@openuidev/react-ui"; diff --git a/examples/vercel-ai-chat/src/app/globals.css b/examples/vercel-ai-chat/src/app/globals.css index c45470e58..7e1cd2a6f 100644 --- a/examples/vercel-ai-chat/src/app/globals.css +++ b/examples/vercel-ai-chat/src/app/globals.css @@ -1,3 +1,3 @@ @layer theme, base, openui, components, utilities; @import "tailwindcss"; -@import "@openuidev/react-ui/components.css"; +@import "@openuidev/react-ui/layered-components.css"; diff --git a/examples/vercel-ai-chat/src/app/page.tsx b/examples/vercel-ai-chat/src/app/page.tsx index b08b0a679..b64c52090 100644 --- a/examples/vercel-ai-chat/src/app/page.tsx +++ b/examples/vercel-ai-chat/src/app/page.tsx @@ -1,18 +1,16 @@ "use client"; -import "@openuidev/react-ui/components.css"; - -import { useChat } from "@ai-sdk/react"; -import { useRef, useEffect, useState } from "react"; -import { useTheme } from "@/hooks/use-system-theme"; -import { useThreads } from "@/hooks/use-threads"; +import { AssistantMessage } from "@/components/assistant-message"; import { ChatHeader } from "@/components/chat-header"; import { ChatInput } from "@/components/chat-input"; import { ConversationStarters } from "@/components/conversation-starters"; -import { AssistantMessage } from "@/components/assistant-message"; -import { UserMessage } from "@/components/user-message"; -import { ThinkingIndicator } from "@/components/thinking-indicator"; import { Sidebar } from "@/components/sidebar"; +import { ThinkingIndicator } from "@/components/thinking-indicator"; +import { UserMessage } from "@/components/user-message"; +import { useTheme } from "@/hooks/use-system-theme"; +import { useThreads } from "@/hooks/use-threads"; +import { useChat } from "@ai-sdk/react"; +import { useEffect, useRef, useState } from "react"; export default function Page() { useTheme(); @@ -78,10 +76,7 @@ export default function Page() { sidebarOpen ? "md:ml-[280px]" : "ml-0" }`} > - setSidebarOpen((o) => !o)} - /> + setSidebarOpen((o) => !o)} />
{isEmpty ? ( From f56ec7ef8745628dfcc1cdc6145ad2744fcd2e7e Mon Sep 17 00:00:00 2001 From: ankit-thesys Date: Wed, 10 Jun 2026 23:15:29 +0530 Subject: [PATCH 10/13] docs(react-ui): fix mislabeled styles/* row in Subpath Exports table Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/react-ui/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-ui/README.md b/packages/react-ui/README.md index 22e8692b9..2e07c2a17 100644 --- a/packages/react-ui/README.md +++ b/packages/react-ui/README.md @@ -215,7 +215,7 @@ import { Charts } from "@openuidev/react-ui/Charts"; | `@openuidev/react-ui/defaults.css` | Theme tokens, always unlayered | | `@openuidev/react-ui/genui-lib` | OpenUI Lang libraries and prompt options | | `@openuidev/react-ui/tailwind` | Tailwind CSS plugin | -| `@openuidev/react-ui/styles/*` | SCSS utilities | +| `@openuidev/react-ui/styles/*` | Per-component compiled styles (unlayered) | | `@openuidev/react-ui/layered/styles/*` | Per-component styles wrapped in `@layer openui` | | `@openuidev/react-ui/scssUtils` | SCSS utility functions | | `@openuidev/react-ui/` | Per-component entry points | From 4fe9258662aac911f1d5cb906dd217af1a316b05 Mon Sep 17 00:00:00 2001 From: i-subham Date: Thu, 11 Jun 2026 20:04:45 +0530 Subject: [PATCH 11/13] docs: clarify usage of layered and unlayered CSS imports in documentation Updated the API reference and installation guide to emphasize the importance of importing the layered variant from a single location to ensure proper cascade order. Added details on maintaining unlayered styles for individual components and adjusted the build script to include a CSS check for compliance with these guidelines. --- docs/content/docs/api-reference/react-ui.mdx | 2 +- docs/content/docs/chat/installation.mdx | 16 ++++++++++++---- packages/react-ui/check-css-artifacts.js | 11 +++++++++++ packages/react-ui/package.json | 4 ++-- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/docs/content/docs/api-reference/react-ui.mdx b/docs/content/docs/api-reference/react-ui.mdx index c5c2d8c02..5bd9b9f7c 100644 --- a/docs/content/docs/api-reference/react-ui.mdx +++ b/docs/content/docs/api-reference/react-ui.mdx @@ -13,7 +13,7 @@ import { Copilot, FullScreen, BottomTray } from "@openuidev/react-ui"; ### Cascade-layer contract -The default stylesheet exports (`./components.css`, `./styles/*`) are **unlayered**. Two parallel exports — `@openuidev/react-ui/layered-components.css` and `@openuidev/react-ui/layered/styles/*` — ship the same rules wrapped in `@layer openui`; with the layered variant, unlayered consumer CSS overrides OpenUI components without specificity matching or `!important`. Import OpenUI CSS (either variant) from exactly one place in your app — multiple import sites under chunk-splitting bundlers can register cascade layers in the wrong order. See [Chat → Theming](/docs/chat/theming#override-component-styles-with-css) for override patterns, and [Chat → Installation](/docs/chat/installation#3-optional-opt-into-cascade-layered-styles) for the Tailwind v4 layer-order setup. +The default stylesheet exports (`./components.css`, `./styles/*`) are **unlayered**. Two parallel exports — `@openuidev/react-ui/layered-components.css` and `@openuidev/react-ui/layered/styles/*` — ship the same rules wrapped in `@layer openui`; with the layered variant, unlayered consumer CSS overrides OpenUI components without specificity matching or `!important`. When using the layered variant, import it from exactly one place in your app — multiple import sites under chunk-splitting bundlers can register the `openui` layer in the wrong order. See [Chat → Theming](/docs/chat/theming#override-component-styles-with-css) for override patterns, and [Chat → Installation](/docs/chat/installation#3-optional-opt-into-cascade-layered-styles) for the Tailwind v4 layer-order setup. `./defaults.css` and the `ThemeProvider` runtime style injection are always unlayered, so theme tokens override component defaults in both modes. diff --git a/docs/content/docs/chat/installation.mdx b/docs/content/docs/chat/installation.mdx index 0328aaadb..7ce202964 100644 --- a/docs/content/docs/chat/installation.mdx +++ b/docs/content/docs/chat/installation.mdx @@ -59,22 +59,30 @@ These imports give you the default chat layout styling and theme tokens. ## 3. (Optional) Opt into cascade-layered styles -The default import above ships **unlayered** CSS — overrides work via normal specificity, exactly as in earlier versions. If you prefer your plain app CSS to win over OpenUI styles without specificity matching (recommended for Tailwind v4 apps), import the **layered** variant instead: +The default import above ships **unlayered** CSS — overrides work via normal specificity, exactly as in earlier versions. If you prefer your plain app CSS to win over OpenUI styles without specificity matching (recommended for Tailwind v4 apps), import the **layered** variant instead. + +Import it from a **single place** — your global stylesheet, not the root layout — so its `@layer openui` block registers in a predictable order: + +```css +/* app/globals.css */ +@import "@openuidev/react-ui/layered-components.css"; +``` ```tsx -import "@openuidev/react-ui/layered-components.css"; +// app/layout.tsx — import only globals.css (drop the react-ui CSS imports from step 2) import "./globals.css"; ``` -With Tailwind v4, also declare the layer order in your global stylesheet so `openui` sits above Tailwind's reset (`base`) but below `components` and `utilities`: +With Tailwind v4, declare the layer order at the top of `globals.css`, **before** the layered import, so `openui` sits above Tailwind's reset (`base`) but below `components` and `utilities`: ```css /* app/globals.css */ @layer theme, base, openui, components, utilities; @import "tailwindcss"; +@import "@openuidev/react-ui/layered-components.css"; ``` -This places OpenUI above Tailwind's Preflight (so its element resets don't override component styles) while keeping Tailwind utilities like `bg-red-500` winning over OpenUI. Without this declaration, the cascade order is bundler-dependent and `openui` may end up declared *after* `utilities`, which prevents utility overrides from taking effect. +This places OpenUI above Tailwind's Preflight (so its element resets don't override component styles) while keeping Tailwind utilities like `bg-red-500` winning over OpenUI. The layered import must come **after** the `@layer` declaration: loading it first registers the `openui` layer before the order statement and locks the wrong order (and without the declaration at all, the cascade order is bundler-dependent — `openui` may end up after `utilities`, preventing utility overrides). Two rules when using the layered variant: diff --git a/packages/react-ui/check-css-artifacts.js b/packages/react-ui/check-css-artifacts.js index 1f02eeb33..1f972fb88 100644 --- a/packages/react-ui/check-css-artifacts.js +++ b/packages/react-ui/check-css-artifacts.js @@ -44,6 +44,17 @@ assert( `layered mirror has ${layered.length} css files, unlayered has ${unlayered.length}`, ); +// Every per-component DEFAULT style must stay unlayered too — not just the +// index files checked above. Guards against a regression that re-wraps +// dist/styles/*.css in place (the `wrapComponentCssInPlace` behavior this +// contract intentionally removed); since consumers can import individual +// ./styles/.css, an index-only check would miss it. +// openui-defaults.css is asserted unlayered separately above. +for (const name of unlayered) { + if (name === "openui-defaults.css") continue; + assert(!/^\s*@layer/.test(read(path.join("styles", name))), `styles/${name} must stay unlayered`); +} + for (const f of [ ...layered.map((n) => path.join("layered", "styles", n)), "layered/components/index.css", diff --git a/packages/react-ui/package.json b/packages/react-ui/package.json index b3c8205ce..937012550 100644 --- a/packages/react-ui/package.json +++ b/packages/react-ui/package.json @@ -70,7 +70,7 @@ "copy-css": "node cp-css.js", "generate-scss-index": "node src/scripts/scss-import.js", "generate:css-utils": "tsx src/scripts/generate-css-utils.ts", - "build": "rimraf dist && pnpm generate:css-utils && pnpm build:scss && pnpm build:tsc && pnpm build:cjs && pnpm run copy-css", + "build": "rimraf dist && pnpm generate:css-utils && pnpm build:scss && pnpm build:tsc && pnpm build:cjs && pnpm run copy-css && pnpm run check:css", "typecheck": "tsc --noEmit", "build:tsc": "tsc -p . || node -e \"process.exit(0)\"", "build:cjs": "tsdown", @@ -87,7 +87,7 @@ "check:attw": "attw --pack . --ignore-rules no-resolution", "prepare": "pnpm run build", "prepublishOnly": "pnpm run check:css && pnpm run check:publint && pnpm run check:attw", - "ci": "pnpm run lint:check && pnpm run format:check" + "ci": "pnpm run lint:check && pnpm run format:check && pnpm run test" }, "peerDependencies": { "@openuidev/react-headless": "workspace:^", From f3a81e5db2a47faf74bfdea52249eb07f2ec7f05 Mon Sep 17 00:00:00 2001 From: ankit-thesys Date: Fri, 12 Jun 2026 15:18:34 +0530 Subject: [PATCH 12/13] docs(react-ui): remove nonexistent ./tailwind row from Subpath Exports table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The exports map has no ./tailwind entry and the catch-all resolves it to dist/components/tailwind/index.js, which does not exist — verified require.resolve fails with MODULE_NOT_FOUND. Pre-existing inaccuracy, removed while the table is already in this PR's diff. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/react-ui/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-ui/README.md b/packages/react-ui/README.md index 2e07c2a17..48c5c6677 100644 --- a/packages/react-ui/README.md +++ b/packages/react-ui/README.md @@ -214,7 +214,6 @@ import { Charts } from "@openuidev/react-ui/Charts"; | `@openuidev/react-ui/layered-components.css` | Opt-in aggregate stylesheet wrapped in `@layer openui` | | `@openuidev/react-ui/defaults.css` | Theme tokens, always unlayered | | `@openuidev/react-ui/genui-lib` | OpenUI Lang libraries and prompt options | -| `@openuidev/react-ui/tailwind` | Tailwind CSS plugin | | `@openuidev/react-ui/styles/*` | Per-component compiled styles (unlayered) | | `@openuidev/react-ui/layered/styles/*` | Per-component styles wrapped in `@layer openui` | | `@openuidev/react-ui/scssUtils` | SCSS utility functions | From 828ac801a6b454ddff30d222d40b7ec30821290f Mon Sep 17 00:00:00 2001 From: ankit-thesys Date: Fri, 12 Jun 2026 15:27:26 +0530 Subject: [PATCH 13/13] docs(react-ui): align README Tailwind v4 recipe import order with installation guide Both orders are valid (the @layer statement locks layer priority), but docs and the example apps' globals.css should teach one canonical recipe. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/react-ui/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-ui/README.md b/packages/react-ui/README.md index 48c5c6677..655514018 100644 --- a/packages/react-ui/README.md +++ b/packages/react-ui/README.md @@ -165,8 +165,8 @@ Declare layer order at the top of your entry stylesheet so `openui` sits above T ```css @layer theme, base, openui, components, utilities; -@import "@openuidev/react-ui/layered-components.css"; @import "tailwindcss"; +@import "@openuidev/react-ui/layered-components.css"; ``` This places Tailwind's Preflight (in `base`) below OpenUI components so its element resets don't override them, while keeping utilities (`bg-red-500`, etc.) winning over OpenUI styles.