diff --git a/.changepacks/changepack_log_known_limitations.json b/.changepacks/changepack_log_known_limitations.json new file mode 100644 index 000000000..1878c32ec --- /dev/null +++ b/.changepacks/changepack_log_known_limitations.json @@ -0,0 +1,7 @@ +{ + "changes": { + "packages/react/package.json": "Patch" + }, + "note": "The README no longer claims every CSS-in-JS pattern compiles: it describes what the build compiles and that anything it cannot know is a CSS variable or a located build error, and links the new Supported Syntax & Limitations page, which lists what compiles, the style override rules (where a later part wins and where the CSS cascade decides), Baseline 2024 browser support, that a component taking the css prop must pass className and style on, the runtime-only APIs and how each is handled, and the known limitations", + "date": "2026-10-01T00:00:00.000Z" +} diff --git a/README.md b/README.md index 354d19968..1ec2a05d3 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@

- Zero Config · Zero FOUC · Zero Runtime · Complete CSS-in-JS Syntax Coverage + Zero Config · Zero FOUC · Zero Runtime · Build-Time CSS-in-JS

--- @@ -41,7 +41,7 @@ English | [한국어](README_ko.md) Traditional CSS-in-JS solutions force you to choose between developer experience and performance. Devup UI eliminates this trade-off entirely by processing all styles at build time using a Rust-powered preprocessor. -- **Complete Syntax Coverage**: Every CSS-in-JS pattern you know — variables, conditionals, responsive arrays, pseudo-selectors — all fully supported +- **Broad Syntax Coverage**: Variables, conditionals, responsive arrays, pseudo-selectors, `styled()`, Emotion's `css` prop and more compile at build time; anything the build cannot know is a located build error, never a silent miss ([supported syntax & limitations](https://devup-ui.com/docs/limitations)) - **Familiar API**: `styled()` API compatible with styled-components and Emotion patterns - **True Zero Runtime**: No JavaScript execution for styling at runtime. Period. - **Smallest Bundle Size**: Optimized class names (`a`, `b`, ... `aa`, `ab`) minimize CSS output @@ -137,7 +137,7 @@ const example =
// .a { background-color: var(--a); } ``` -**Complex expressions and responsive arrays — fully supported:** +**Complex expressions and responsive arrays:** ```tsx // You write: diff --git a/README_ko.md b/README_ko.md index 11c0caf7c..37b283632 100644 --- a/README_ko.md +++ b/README_ko.md @@ -7,7 +7,7 @@

- Zero Config · Zero FOUC · Zero Runtime · 모든 CSS-in-JS 문법 완벽 지원 + Zero Config · Zero FOUC · Zero Runtime · 빌드 타임 CSS-in-JS

--- @@ -41,7 +41,7 @@ 기존 CSS-in-JS 솔루션들은 개발자 경험과 성능 사이에서 타협을 강요했습니다. Devup UI는 Rust 기반 전처리기를 통해 모든 스타일을 빌드 타임에 처리함으로써 이 트레이드오프를 완전히 제거합니다. -- **완전한 문법 지원**: 변수, 조건문, 반응형 배열, 가상 선택자 등 모든 CSS-in-JS 패턴을 완벽하게 지원 +- **폭넓은 문법 지원**: 변수, 조건문, 반응형 배열, 가상 선택자, `styled()`, Emotion `css` prop 등을 빌드 타임에 컴파일합니다. 빌드가 알 수 없는 것은 조용히 빠지지 않고 위치가 표시된 빌드 오류가 됩니다 ([지원 문법과 한계](https://devup-ui.com/docs/limitations)) - **익숙한 API**: styled-components, Emotion과 호환되는 `styled()` API 제공 - **진정한 제로 런타임**: 런타임에서 스타일링을 위한 JavaScript 실행이 전혀 없습니다 - **가장 작은 번들 크기**: 최적화된 클래스명(`a`, `b`, ... `aa`, `ab`)으로 CSS 출력 최소화 @@ -137,7 +137,7 @@ const generated =
// .a { background-color: var(--a); } ``` -**복잡한 표현식과 반응형 배열 — 완벽 지원:** +**복잡한 표현식과 반응형 배열:** ```tsx // 개발자가 작성: diff --git a/apps/landing/src/app/(detail)/docs/LeftMenu.tsx b/apps/landing/src/app/(detail)/docs/LeftMenu.tsx index 85250e4ff..344434066 100644 --- a/apps/landing/src/app/(detail)/docs/LeftMenu.tsx +++ b/apps/landing/src/app/(detail)/docs/LeftMenu.tsx @@ -36,6 +36,7 @@ export function LeftMenu() { Core Concepts Features + Supported Syntax & Limitations +const example = ``` `css()`, `globalCss()` and `keyframes()` have no element to set a CSS variable on, so their values must be known at build time — literals, theme tokens or constants. Constants may be objects, arrays and TypeScript enums, declared in the file or imported: `css(baseStyles)`, `{ ...baseStyles, color: 'red' }`, `_hover: hoverStyles`, `space[2]`, `Size.M`, and `` all read as if written in place, a later property replacing an earlier one as in JavaScript. `Math` calls over them (`Math.max(SIZE, 20)`, `Math.round(x)`, `Math.PI`) fold like arithmetic. diff --git a/apps/landing/src/app/(detail)/docs/limitations/page.mdx b/apps/landing/src/app/(detail)/docs/limitations/page.mdx new file mode 100644 index 000000000..24939bd4a --- /dev/null +++ b/apps/landing/src/app/(detail)/docs/limitations/page.mdx @@ -0,0 +1,106 @@ +export const metadata = { + title: 'Supported Syntax & Limitations', + alternates: { + canonical: '/docs/limitations', + }, +} + +# Supported Syntax & Limitations + +Devup UI compiles styles at build time and ships no styling runtime. Whatever the build can know is compiled; whatever it cannot know is either kept as a CSS variable on an element or reported as a build error with the file, line, column, the code and what it needs instead. Nothing is silently dropped, and the output is the same on every build. + +This page lists what compiles, what stays as written, and where the limits are. + +## Supported at build time + +| Pattern | Result | +| --------------------------------------------------------------- | ------------------------------------------------------------------------ | +| Style props with literals, theme tokens and constants | Static classes | +| Style props with runtime values (props, state) | A class reading a CSS variable set in `style` | +| Responsive arrays, `_hover` and other selectors, both combined | Classes per breakpoint and selector | +| Conditions (`a ? b : c`, `a && b`, `a \|\| b`, `a ?? b`) | One class per branch, chosen at runtime | +| Constants declared with `const`, in the file or imported | Read as written, including objects, arrays, enums and computed constants | +| `css()`, `globalCss()`, `keyframes()` | Static CSS; values must be known at build time | +| `styled()` in every spelling, `.attrs()`, `as`, `withComponent` | A component rendering its tag with static classes | +| styled-components / Emotion theme reads (`p.theme.a.b`) | `var(--a-b)`, set by `ThemeProvider` | +| Emotion `css` prop, ``, `jsx`, JSX runtime pragmas | Compiled to `className` and `style` | +| Component selectors (`${Child} { ... }`, `[Child]`) | A short marker class on the selected component | +| vanilla-extract `style`, `globalStyle`, `keyframes` | Static CSS | +| StyleX `create`, `props`, `attrs`, `defineVars`, `createTheme` | Static CSS, merged key by key | +| Tailwind v4 classes in `className` | Static classes; unknown classes stay as written | + +## Style override rules + +When styles the build knows are composed, a later part replaces an earlier one for the same property, selector, breakpoint and layer, as it would at runtime in the original library. This holds for: + +- `css(a, b)` and Emotion's `cx(a, b)` with classes the file binds to `css()` +- `styled(Base)` extending a styled component the file defines, and `.attrs()` +- vanilla-extract `style([a, b])`, StyleX `props(a, b)` +- JSX spreads followed by explicit props, and the Emotion `css` prop with a `className` + +Responsive values are always written after non-responsive ones, so a breakpoint value overrides the base value regardless of where the classes land in the stylesheet. + +### Where the cascade decides + +Some composition only happens at runtime, so the build cannot merge it. There the result follows CSS specificity and stylesheet order, not the order the code wrote the parts in: + +- a `className` passed in through props, an external class (CSS Modules, a design system) or a class string built at runtime, joined with Devup UI classes +- a `styled()` base the build cannot read (a `let`, a function result, a component from another package), which keeps wrapping it +- `withComponent(make())` and other targets only the runtime knows +- unknown spread props, which pass through as written +- StyleX arguments the build cannot read key by key (namespaces from other modules, `include()`) + +When the winner matters, pass the variation as a prop, a variant (`cond ? a : b`) or a `styled()` extension the file defines, so the build sees both sides. + +## Browser support + +The generated CSS targets [Baseline 2024](https://web.dev/baseline): it uses `light-dark()` for color themes, `@layer` for cascade layers and `:is()` for group selectors. Older browsers that lack these features are not supported. + +## The `css` prop on your own components + +The Emotion `css` prop compiles to `className` and `style`. A component of your own that takes the prop receives those two props instead, so it must pass both on to the element it renders: + +```tsx +function Card({ className, style, children }) { + return ( +
+ {children} +
+ ) +} + +const example = +``` + +A dynamic value such as `width` becomes a CSS variable set in `style`, so a component that drops `style` loses it. + +## Runtime-only APIs + +| API | Behavior | +| --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `ServerStyleSheet`, `StyleSheetManager`, `CacheProvider` | Accepted and inert; Devup UI writes a real stylesheet at build time. `CacheProvider` ignores `value` | +| stylis plugins (`stylisPlugins`) | Not applied; there is no stylis pass at build time | +| `@emotion/css` `cache`, `flush`, `hydrate`, `merge`, `sheet` | Stay on `@emotion/css`; they work on styles inserted at runtime | +| `ThemeProvider` | Renders a `display: contents` wrapper that sets the theme's CSS variables | +| `jest-styled-components`, `@emotion/jest`, React Native targets | Not supported | +| vanilla-extract `recipes`, `sprinkles` | A build error while `@vanilla-extract/css` is aliased; use `css()` or the vanilla-extract plugin | + +## Known limitations + +- A class, `keyframes` name or styled component defined in another module is not read as a build-time value: its name is only known once that file is built. Declare it in the file that uses it, or read it from a style prop. +- `css()`, `globalCss()` and `keyframes()` have no element to set a CSS variable on, so a runtime value there is a build error. Move it to a style prop. +- A styled-components or Emotion style function may read the theme only as `theme.a.b` (or `theme.a[2]`). Calls, whole-theme reads and runtime keys are build errors. +- Inside ``, `cx(getClass())` is a build error; put the result in a variable first, which then joins as a class. +- The plugins read `jsxImportSource` from the `tsconfig.json` (or `jsconfig.json`) in the working directory. Opt out with `importAliases: { '@emotion/react/jsx-runtime': false }`. +- Code that only the runtime can compute — other globals, `Date`, `Math.random`, `new`, classes, async code — is never run by the build; on an element it stays a CSS variable, elsewhere it is a build error. + +## Build errors + +Every build error names where it is and what the build needs: + +``` +src/App.tsx:6:18: `css()` cannot use `width` at build time: its values must be literals, theme tokens or constants, or be computed from them +src/Card.tsx:3:36: `css` on `
` cannot use `getStyles()` at build time: it must be a style object, CSS text, a class `css()` gives, or a function of the theme giving one, or an array or condition of them +``` + +A file with several problems reports them all at once, in source order. diff --git a/apps/landing/src/app/(detail)/docs/migration/styled-components/page.mdx b/apps/landing/src/app/(detail)/docs/migration/styled-components/page.mdx index 0cb08360b..ffa68a944 100644 --- a/apps/landing/src/app/(detail)/docs/migration/styled-components/page.mdx +++ b/apps/landing/src/app/(detail)/docs/migration/styled-components/page.mdx @@ -121,6 +121,13 @@ Emotion's `` behaves the same way: the `styles` prop is e | `ThemeProvider`, `useTheme`, `withTheme` | `@devup-ui/react/compat`, CSS-variable backed | | `createGlobalStyle`, `Global` | extracted, render nothing | | `ServerStyleSheet`, `StyleSheetManager` | inert — Devup UI already emits a real stylesheet, so there is nothing to collect | +| `CacheProvider` | renders its children; the cache it configures has nothing to hold | | `isStyledComponent` | always `false` | -| `ClassNames`, `CacheProvider` | no equivalent; stays on its own package with a build warning | +| `css` prop, `jsx`, JSX runtime pragmas | compiled to `className` and `style` | +| `ClassNames` | replaced by what its child renders, each `css` / `cx` call compiled to classes | +| Component selectors (`${Child}`) | a marker class on the selected component | | `.attrs()`, `.withConfig()` | attrs merged over props; `withConfig` dropped | + +## Limitations + +Composition only the runtime sees — a `className` from props, an external class, a `styled()` base the build cannot read — follows CSS specificity and stylesheet order rather than the order you wrote. Styled components and classes defined in another module are not read as build-time values, stylis plugins are not applied, and test utilities and React Native targets are not supported. A component of your own that takes the `css` prop must pass on both `className` and `style`. See [Supported Syntax & Limitations](/docs/limitations) for the full list. diff --git a/apps/landing/src/app/(detail)/docs/overview/page.mdx b/apps/landing/src/app/(detail)/docs/overview/page.mdx index b186af1b8..a6d49fd90 100644 --- a/apps/landing/src/app/(detail)/docs/overview/page.mdx +++ b/apps/landing/src/app/(detail)/docs/overview/page.mdx @@ -31,7 +31,7 @@ Libraries like styled-components and Emotion offer great DX but execute JavaScri ### The Devup UI Solution -Devup UI eliminates this trade-off entirely. Our Rust-powered preprocessor analyzes your code at build time and handles every CSS-in-JS pattern: +Devup UI eliminates this trade-off entirely. Our Rust-powered preprocessor analyzes your code at build time; what it cannot know is a CSS variable or a located build error ([supported syntax & limitations](/docs/limitations)): - **Variables** — Dynamic values become CSS custom properties - **Conditionals** — Ternary expressions are statically analyzed diff --git a/e2e/exported-routes.ts b/e2e/exported-routes.ts index 5c8edd2bc..e3e6cbff1 100644 --- a/e2e/exported-routes.ts +++ b/e2e/exported-routes.ts @@ -80,6 +80,7 @@ export const EXPECTED_EXPORTED_ROUTES = [ '/docs/figma-and-theme-integration/devup-figma-plugin', '/docs/figma-and-theme-integration/devup-json', '/docs/installation', + '/docs/limitations', '/docs/migration/overview', '/docs/migration/styled-components', '/docs/migration/stylex', diff --git a/packages/react/README.md b/packages/react/README.md index ede5459d6..fec687dd8 100644 --- a/packages/react/README.md +++ b/packages/react/README.md @@ -103,7 +103,7 @@ The Turbopack ranges overlap, so the direct-API result is effectively parity wit Devup UI is a CSS in JS preprocessor that does not require runtime. Devup UI eliminates the performance degradation of the browser through the CSS in JS preprocessor. -We develop a preprocessor that considers all grammatical cases. +What the build cannot know is kept as a CSS variable or reported as a located build error; see [supported syntax & limitations](https://devup-ui.com/docs/limitations). ```tsx const before = @@ -111,7 +111,7 @@ const before = const after =
``` -Variables are fully supported. +Variables become CSS variables. ```tsx const before = @@ -126,7 +126,7 @@ const after = ( ) ``` -Various expressions and responsiveness are also fully supported. +Conditions and responsive arrays compile too. ```tsx const before = b ? 'yellow' : variable]} />