Skip to content

feat(react-ui): opt-in layered CSS via ./layered-components.css and ./layered/styles/*#631

Merged
ankit-thesys merged 17 commits into
mainfrom
feat/react-ui-layered-css-optin
Jun 15, 2026
Merged

feat(react-ui): opt-in layered CSS via ./layered-components.css and ./layered/styles/*#631
ankit-thesys merged 17 commits into
mainfrom
feat/react-ui-layered-css-optin

Conversation

@ankit-thesys

Copy link
Copy Markdown
Contributor

Summary

Makes @layer openui an opt-in instead of the default (revises #589/#621's distribution strategy before it ships in any release):

  • ./components.css and ./styles/* return to unlayered 0.11.x behavior — existing consumers see zero change on upgrade. Verified byte-equivalent to the pre-feat(react-ui): adopt CSS cascade layers (@layer openui) via build-side post-process #589 build pipeline.
  • New additive exports ./layered-components.css and ./layered/styles/* carry the same rules wrapped in @layer openui for consumers who want plain-CSS overrides (and the Tailwind v4 recipe).
  • openui-defaults.css / ThemeProvider runtime injection stay unlayered in both modes (runtime theming contract).
  • Removes the package-wide browserslist cascade-layers floor (now only the opt-in path needs it; documented in docs instead).
  • Fixes the BOM bug found in pre-release verification: Sass's leading U+FEFF, once wrapped, landed mid-stylesheet and silently killed the first rule (the :root theme tokens). wrapInLayer now strips it — with unit tests (the package's first real test suite; --passWithNoTests removed).
  • Adds check-css-artifacts.js to prepublishOnly: default-unlayered / layered-wrapped / BOM-free / defaults-unlayered invariants are now mechanically enforced (guard proven to catch an injected BOM).
  • Docs explain both modes; docs app + the nine layer-recipe examples dogfood the layered variant, and multi-agent-chat / vercel-ai-chat are consolidated to a single CSS import site (the fix(css): re-adopt @layer openui (#589) and make the docs honor cascade layers #621 Turbopack hazard). material-ui-chat / fastapi-backend deliberately stay on the unlayered default.

Why

Flipping the default would change override semantics for every existing consumer (unlayered resets suddenly beat component styles — the same silent visual regression that broke our docs site in #620) and would bundle that risk with the react-syntax-highlighter security fix in the next release. Opt-in keeps the next react-ui release a safe patch (0.11.9). Default flip is deferred to 1.0 with a migration window.

Verification

  • 6/6 unit tests; pnpm build + pnpm run check:css + pnpm run ci green
  • Packed tarball inspected: exports resolve, default CSS unlayered, layered mirror wrapped + BOM-free, no catalog: literals, version untouched at 0.11.8 (bump lands in the release PR chore(release): react-ui 0.11.9, lang-core 0.2.6, cli 0.0.8 #630)
  • openui-chat example smoke-built green under Next.js/Turbopack against the new export

🤖 Generated with Claude Code

ankit-thesys and others added 17 commits June 10, 2026 18:39
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ults unlayered

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rop browserslist

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…efaults contract)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…able

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ingle import site

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tion

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.
…s table

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) <noreply@anthropic.com>
…tallation 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) <noreply@anthropic.com>
@ankit-thesys ankit-thesys merged commit 55dafd0 into main Jun 15, 2026
1 check passed
@ankit-thesys ankit-thesys deleted the feat/react-ui-layered-css-optin branch June 15, 2026 15:15
ankit-thesys added a commit that referenced this pull request Jun 15, 2026
Adopt #631's opt-in layered CSS (cp-css.js taken from main; the standalone
BOM fix is superseded by wrapInLayer). react-ui -> 0.11.9 (patch): opt-in CSS
is non-breaking, so the 0.12.0 minor rationale no longer applies; the
react-syntax-highlighter 15->16 bump rides along at patch level.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ankit-thesys added a commit that referenced this pull request Jun 15, 2026
The opt-in re-land (#631) only strips the BOM when wrapping the layered
mirror, so Sass's compressed-mode BOM leaked into the unlayered defaults
(components.css, styles/index.css, markDownRenderer.css). A leading BOM is
harmless to browsers but breaks the package's zero-BOM contract and would
become fatal if those files were ever wrapped. cp-css.js now strips it from
the unlayered output, and check-css-artifacts.js asserts the unlayered
exports are BOM-free too (the guard previously only checked the layered mirror).

Root cause is on main (#631's cp-css.js) — flagged for a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ankit-thesys added a commit that referenced this pull request Jun 16, 2026
* chore(release): bump react-ui to 0.12.0, lang-core to 0.2.6, cli to 0.0.8

Version bumps for the first publish since 2026-05-20 (4b663b9):

- @openuidev/react-ui 0.11.8 -> 0.12.0 (minor): component CSS now ships
  in `@layer openui` (#589/#621) and react-syntax-highlighter moved to
  ^16.1.1 (#577, fixes prismjs CVE-2024-53382)
- @openuidev/lang-core 0.2.5 -> 0.2.6 (patch): parser preserves markdown
  fences and comments inside string props (#605)
- @openuidev/cli 0.0.7 -> 0.0.8 (patch): cross-platform template build
  (#601, #627); no functional changes to the published CLI

Remaining packages have no consumer-visible changes since the last
publish and are not republished; the lang wrappers pick up lang-core
0.2.6 transitively via their ^0.2.5 ranges.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(react-ui): strip leading BOM before wrapping CSS in @layer openui

Sass emits a UTF-8 BOM for compressed output containing non-ASCII
characters. At byte 0 the CSS decoder strips it, but wrapInLayer()
concatenated the layer prelude in front of it, pushing the BOM inside
the block where U+FEFF parses as an identifier: `:root` becomes a
type selector that matches nothing, silently killing the first rule.

In the packed 0.12.0 tarball this dropped the entire :root theme-token
block of dist/components/index.css (and the first rule of
dist/styles/index.css and markDownRenderer.css). Verified in Chrome:
the BOM-poisoned rule does not apply; subsequent rules are unaffected.

Strip the BOM before wrapping. Repacked tarball now has 0 BOMs across
all 171 shipped CSS files and the :root block parses correctly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(react-ui): strip leading BOM from unlayered default CSS

The opt-in re-land (#631) only strips the BOM when wrapping the layered
mirror, so Sass's compressed-mode BOM leaked into the unlayered defaults
(components.css, styles/index.css, markDownRenderer.css). A leading BOM is
harmless to browsers but breaks the package's zero-BOM contract and would
become fatal if those files were ever wrapped. cp-css.js now strips it from
the unlayered output, and check-css-artifacts.js asserts the unlayered
exports are BOM-free too (the guard previously only checked the layered mirror).

Root cause is on main (#631's cp-css.js) — flagged for a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants