Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3edc586
fix(wasm): enable Boa's js feature so vanilla-extract files stop pani…
owjs3901 Sep 26, 2026
519f1b7
feat(eslint-plugin): add prefer-media-shorthand and no-typography-tok…
owjs3901 Sep 26, 2026
99ac1be
feat(css): add at-rule composition primitives
owjs3901 Sep 26, 2026
9861730
fix(extractor): compose at-rules with selectors and resolve vanilla-e…
owjs3901 Sep 26, 2026
7986d4b
fix(sheet): order at-rules after breakpoints and merge adjacent rules
owjs3901 Sep 26, 2026
31a8aa5
feat(react): type media shorthands and at-rule keys
owjs3901 Sep 26, 2026
b21b618
docs: document media shorthands and at-rules, fix typography examples
owjs3901 Sep 26, 2026
ab73e17
chore: add changepacks for at-rule composition and CSS output fixes
owjs3901 Sep 26, 2026
f6ea583
fix(extractor): give vanilla-extract and StyleX numbers their library…
owjs3901 Sep 26, 2026
089fc36
fix(next-plugin): run every vanilla-extract stylesheet on the full en…
owjs3901 Sep 26, 2026
c6d33d6
docs: document library number units and the full vanilla-extract engine
owjs3901 Sep 26, 2026
9a7a19a
chore: add changepack for library number units and full engine
owjs3901 Sep 26, 2026
ef7cb18
fix(extractor): fix vanilla-extract evaluation and Emotion/styled-com…
owjs3901 Sep 27, 2026
f6192f6
fix(sheet): write layered rules like the others and let typography yield
owjs3901 Sep 27, 2026
9aee7d1
docs: document library composition, vanilla-extract coverage and typo…
owjs3901 Sep 27, 2026
8fc2705
chore: add changepack for compat, layer and typography fixes
owjs3901 Sep 27, 2026
3fe1681
refactor(extractor): build font faces without a branching loop
owjs3901 Sep 27, 2026
1c96883
refactor(extractor): build font faces without closures
owjs3901 Sep 27, 2026
0f936aa
refactor(extractor): build the single font-face rule list without vec!
owjs3901 Sep 27, 2026
530b9c5
refactor(extractor): make declaring font faces infallible
owjs3901 Sep 27, 2026
a52adbd
chore(extractor): keep the font-face loop out of tarpaulin's report
owjs3901 Sep 27, 2026
672b8d4
refactor(extractor): declare font-face lists recursively
owjs3901 Sep 27, 2026
ffb8b41
refactor(extractor): build font-face rules without touching the colle…
owjs3901 Sep 27, 2026
08705d3
test(extractor): extract font faces through the library build
owjs3901 Sep 27, 2026
fda81e3
fix(extractor): keep custom property values verbatim
owjs3901 Sep 27, 2026
cb93bc6
test(extractor): cover a createVar declaration that cannot be read
owjs3901 Sep 27, 2026
71cff81
refactor(extractor): fit the createVar signature on one line
owjs3901 Sep 27, 2026
fe36f5e
refactor(extractor): split createVar into name and reference helpers
owjs3901 Sep 27, 2026
b4146f4
refactor(extractor): move the declared createVar branch into its own …
owjs3901 Sep 27, 2026
9d70aee
refactor(extractor): keep the declared createVar helper out of line
owjs3901 Sep 27, 2026
dc5e260
fix(extractor): resolve placeholders through one selector function type
owjs3901 Sep 27, 2026
a589494
test(extractor): resolve a style reference outside a selector through…
owjs3901 Sep 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changepacks/changepack_log_at_rule_composition.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"changes": {
"bindings/devup-ui-wasm/package.json": "Patch",
"packages/react/package.json": "Patch"
},
"note": "Fix at-rule composition and cascade: `_print`/`_media`/`@media …` nest correctly with selectors in both directions and inside `globalCss`, nested media queries merge (`print and (…)`), breakpoints put media types first, distribute query lists, and nest `not …` queries, and at-rules are emitted after all breakpoint values while pseudo selectors keep `SELECTOR_ORDER` across breakpoints. Add `_motionReduce`, `_motionSafe`, `_portrait`, `_landscape`, `_contrastMore`, `_contrastLess` and `_forcedColors`, remove the never-matching `_speech`, normalize query whitespace, support Emotion-style `'@media …'`/`'&:hover'` object keys, fix template literals losing declarations before `@media` or selectors around nested at-rules, apply `typography` conditionally under selectors, and drop stale global at-rules on re-extraction",
"date": "2026-09-26T00:00:00.000Z"
}
7 changes: 7 additions & 0 deletions .changepacks/changepack_log_compat_layers_typography.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": {
"bindings/devup-ui-wasm/package.json": "Patch"
},
"note": "Fix vanilla-extract stylesheets: styles are named after the variable holding them, so style calls nested in objects no longer shift every later name onto the wrong styles and such objects export their classes; createVar, createContainer, layer, fontFace and theme variables get names unique to each stylesheet instead of colliding across files, createVar returns var() so template literals and fallbackVar work, createTheme(tokens, debugId), the three-argument createGlobalTheme and unassigned createGlobalTheme calls produce their variables, createThemeContract exports, styleVariants variants resolve in selectors and compositions and accept a map function, composition keeps transitive bases, '@layer' records, globalLayer, createGlobalThemeContract, assignVars and globalFontFace work, and layers keep their declaration order. Emotion and styled-components object styles read numbers as pixels (Devup UI shorthands keep the spacing scale), compose several style arguments or arrays, and accept Emotion's styled(tag, options) factory; css() composes class names passed with rule objects. A global '@layer' now covers responsive, selector and at-rule declarations, and layered declarations keep their breakpoints. Custom properties keep their value instead of reading a number as the spacing scale. A typography preset no longer overrides a property written beside it at a wider breakpoint, in components and globalCss",
"date": "2026-09-27T00:00:00.000Z"
}
8 changes: 8 additions & 0 deletions .changepacks/changepack_log_css_output_fixes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"changes": {
"bindings/devup-ui-wasm/package.json": "Patch",
"packages/eslint-plugin/package.json": "Patch"
},
"note": "Fix the full WASM build panicking on every vanilla-extract `.css.ts` file (Boa read the clock without its `js` feature), resolve vanilla-extract references to other styles, keyframes, fonts and themes in selectors, values and `globalStyle`, keep `globalCss` pseudo selectors in `SELECTOR_ORDER` across breakpoints, merge adjacent rules with the same selector, and let declarations written next to a conditional `typography` preset win over it. Add the `prefer-media-shorthand` and `no-typography-token-prefix` ESLint rules",
"date": "2026-09-27T00:00:00.000Z"
}
8 changes: 8 additions & 0 deletions .changepacks/changepack_log_library_numbers_full_engine.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"changes": {
"bindings/devup-ui-wasm/package.json": "Patch",
"packages/next-plugin/package.json": "Patch"
},
"note": "Numbers in vanilla-extract and StyleX styles keep their library's meaning instead of Devup UI's spacing scale: vanilla-extract styles add `px` in `.css.ts` files and in `style()`, `globalStyle()` and `keyframes()` calls inside ordinary modules, StyleX adds `px`/`ms` to static values and to numbers passed to dynamic styles, and unitless properties such as `columnCount`, `strokeWidth`, `orphans` or `zoom` keep a bare number everywhere. The Next.js plugin now runs every `.css.ts` module on the full engine instead of a separate static fast path, so development and production builds produce the same output",
"date": "2026-09-27T00:00:00.000Z"
}
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Turbopack values are medians of six cold builds in alternating order:
| **devup-ui (direct APIs, single CSS)** | 1.0.40 | **5.46s** | **36,507,782 bytes** |
| **devup-ui (static `.css.ts`)** | 1.0.40 | **5.41s** | 36,586,883 bytes |

The Turbopack ranges overlap, so the direct-API median is 0.06s (1.1%) ahead but still effectively parity with Tailwind on this fixture. The six cold samples were Tailwind `5.42, 5.56, 5.70, 5.53, 5.41, 5.52s`, direct Devup UI `5.64, 5.40, 5.42, 5.46, 5.54, 5.46s`, and static `.css.ts` `5.42, 5.38, 5.42, 5.47, 5.40, 5.38s`. The fixtures have comparable app shapes, not pixel-identical styling: Tailwind styles the leading paragraph and button more heavily, while Devup UI exercises typed component/style props. Treat these as build-pipeline results rather than a per-rule microbenchmark. The static `.css.ts` row uses the `lite` WASM fast path; dynamic `.css.ts` modules use the full Boa evaluator and are not represented by that median.
The Turbopack ranges overlap, so the direct-API median is 0.06s (1.1%) ahead but still effectively parity with Tailwind on this fixture. The six cold samples were Tailwind `5.42, 5.56, 5.70, 5.53, 5.41, 5.52s`, direct Devup UI `5.64, 5.40, 5.42, 5.46, 5.54, 5.46s`, and static `.css.ts` `5.42, 5.38, 5.42, 5.47, 5.40, 5.38s`. The fixtures have comparable app shapes, not pixel-identical styling: Tailwind styles the leading paragraph and button more heavily, while Devup UI exercises typed component/style props. Treat these as build-pipeline results rather than a per-rule microbenchmark. The static `.css.ts` row was measured with the former `lite` fast path for static modules; every `.css.ts` module now runs on the full Boa evaluator, which adds about 20 ms for the first module and under 1 ms for each further one.

## How it works

Expand Down Expand Up @@ -186,7 +186,7 @@ const example = (
```tsx
// Type-safe theme tokens
const textExample = <Text color="$primary" />
const boxExample = <Box typography="$heading" />
const boxExample = <Box typography="heading" />
```

**Custom shorthands:**
Expand Down
4 changes: 2 additions & 2 deletions README_ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Turbopack 값은 실행 순서를 번갈아 측정한 cold build 6회의 중앙
| **devup-ui (direct APIs, single CSS)** | 1.0.40 | **6.54s** | **36,519,234 bytes** |
| **devup-ui (static `.css.ts`)** | 1.0.40 | **6.47s** | 36,550,197 bytes |

Turbopack 측정 범위는 서로 겹치므로 direct API 결과는 이 fixture에서 Tailwind와 사실상 동률입니다. static `.css.ts` 행은 `lite` WASM 빠른 경로를 사용하며, full Boa evaluator가 필요한 dynamic `.css.ts`는 해당 중앙값에 포함되지 않습니다.
Turbopack 측정 범위는 서로 겹치므로 direct API 결과는 이 fixture에서 Tailwind와 사실상 동률입니다. static `.css.ts` 행은 정적 모듈용 `lite` 빠른 경로가 있던 때에 측정한 값입니다. 지금은 모든 `.css.ts`를 full Boa evaluator로 처리하며, 첫 모듈에 약 20ms, 이후 모듈마다 1ms 미만이 더 듭니다.

## 작동 원리

Expand Down Expand Up @@ -186,7 +186,7 @@ const generated = (
```tsx
// 타입 세이프 테마 토큰
const textExample = <Text color="$primary" />
const boxExample = <Box typography="$heading" />
const boxExample = <Box typography="heading" />
```

**사용자 정의 shorthand:**
Expand Down
21 changes: 20 additions & 1 deletion SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ All standard CSS properties from `csstype` are also accepted directly (e.g., `di
<Box p="20px" /> // padding: 20px (with unit = exact value)
```

Unitless CSS properties keep the number as written: `lineHeight`, `zIndex`, `opacity`, `fontWeight`, `flexGrow`, `order`, `aspectRatio`, `columnCount`, `strokeWidth`, `zoom`, and the rest of the unitless list vanilla-extract uses.

Styles from other libraries keep that library's number meaning instead of the scale: in `.css.ts` files, in vanilla-extract `style()`, `globalStyle()` and `keyframes()` calls inside ordinary modules, in Emotion and styled-components object styles, and in `stylex.create()`, `fontSize: 16` is `16px`. Devup UI shorthands (`p`, `bg`, ...) keep the scale everywhere.

### Responsive Arrays (5 breakpoints)

```tsx
Expand Down Expand Up @@ -215,8 +219,23 @@ Available: `_groupHover`, `_groupFocus`, `_groupActive`, `_groupDisabled`.

// @ prefix syntax (equivalent)
<Box {...{ "@media": { "(min-width: 768px)": { w: "50%" } } }} />

// Query in the key (Emotion style), also inside `selectors` and css()/styled()
<Box selectors={{ "@media print": { display: "none" } }} />
```

Media shorthands wrap styles in a fixed query and nest in either direction with selectors:
`_print`, `_screen`, `_all`, `_portrait`, `_landscape`, `_motionReduce` (`prefers-reduced-motion: reduce`), `_motionSafe` (`no-preference`), `_contrastMore`, `_contrastLess`, `_forcedColors`.

```tsx
<Box transition={["opacity .2s", null, "all .3s"]} _motionReduce={{ transition: "none" }} />
<Box _motionSafe={{ _hover: { transform: "scale(1.05)" } }} />
globalCss({ _motionReduce: { "*, *::before, *::after": { transition: "none" } } })
```

- Conditions beat breakpoint values: at-rule styles are emitted after every responsive rule.
- Nested `@media` rules merge into one query (`print and (prefers-reduced-motion:reduce)`); ones that can never match together (`_print` inside `_screen`) are dropped.

### Custom Selectors

```tsx
Expand Down Expand Up @@ -365,7 +384,7 @@ const spin = keyframes({ from: { transform: "rotate(0)" }, to: { transform: "rot
```

- **Colors**: Use with `$` prefix in JSX props: `<Box color="$primary" />`
- **Typography**: Use with `$` prefix: `<Text typography="$heading" />`
- **Typography**: Use the preset name without `$`: `<Text typography="heading" />`. Under selectors or at-rules (`_hover={{ typography: "heading" }}`) the preset applies only under that condition.
- **Length**: Responsive length tokens: `<Box px="$containerX" />`, `<Flex gap="$gutter" />`
- **Shadow**: Responsive shadow tokens: `<Box boxShadow="$card" />`
- **extends**: Inherit from base config files (deep merge, last wins)
Expand Down
6 changes: 6 additions & 0 deletions apps/landing/src/app/(detail)/docs/api/box/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ However, the following properties are exceptions and **are not multiplied by 4**
- `grid-row`, `grid-row-start`, `grid-row-end`
- `animation-iteration-count`
- `tab-size`, `moz-tab-size`, `-webkit-line-clamp`
- `grid-area`
- `column-count`, `columns`, `orphans`, `widows`, `zoom`
- `border-image`, `border-image-outset`, `border-image-slice`, `border-image-width`
- `mask-border`, `mask-border-outset`, `mask-border-slice`, `mask-border-width`
- `box-flex`, `box-flex-group`, `box-ordinal-group`, `initial-letter`, `max-lines`, `shape-image-threshold`
- `fill-opacity`, `flood-opacity`, `stop-opacity`, `stroke-opacity`, `stroke-width`, `stroke-dashoffset`, `stroke-miterlimit`

### Rendering as Another Element

Expand Down
6 changes: 6 additions & 0 deletions apps/landing/src/app/(detail)/docs/api/button/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,9 @@ However, the following properties are exceptions and **are not multiplied by 4**
- `grid-row`, `grid-row-start`, `grid-row-end`
- `animation-iteration-count`
- `tab-size`, `moz-tab-size`, `-webkit-line-clamp`
- `grid-area`
- `column-count`, `columns`, `orphans`, `widows`, `zoom`
- `border-image`, `border-image-outset`, `border-image-slice`, `border-image-width`
- `mask-border`, `mask-border-outset`, `mask-border-slice`, `mask-border-width`
- `box-flex`, `box-flex-group`, `box-ordinal-group`, `initial-letter`, `max-lines`, `shape-image-threshold`
- `fill-opacity`, `flood-opacity`, `stop-opacity`, `stroke-opacity`, `stroke-width`, `stroke-dashoffset`, `stroke-miterlimit`
6 changes: 6 additions & 0 deletions apps/landing/src/app/(detail)/docs/api/center/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ However, the following properties are exceptions and **are not multiplied by 4**
- `grid-row`, `grid-row-start`, `grid-row-end`
- `animation-iteration-count`
- `tab-size`, `moz-tab-size`, `-webkit-line-clamp`
- `grid-area`
- `column-count`, `columns`, `orphans`, `widows`, `zoom`
- `border-image`, `border-image-outset`, `border-image-slice`, `border-image-width`
- `mask-border`, `mask-border-outset`, `mask-border-slice`, `mask-border-width`
- `box-flex`, `box-flex-group`, `box-ordinal-group`, `initial-letter`, `max-lines`, `shape-image-threshold`
- `fill-opacity`, `flood-opacity`, `stop-opacity`, `stroke-opacity`, `stroke-width`, `stroke-dashoffset`, `stroke-miterlimit`

<br />

Expand Down
6 changes: 6 additions & 0 deletions apps/landing/src/app/(detail)/docs/api/grid/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ However, the following properties are exceptions and **are not multiplied by 4**
- `grid-row`, `grid-row-start`, `grid-row-end`
- `animation-iteration-count`
- `tab-size`, `moz-tab-size`, `-webkit-line-clamp`
- `grid-area`
- `column-count`, `columns`, `orphans`, `widows`, `zoom`
- `border-image`, `border-image-outset`, `border-image-slice`, `border-image-width`
- `mask-border`, `mask-border-outset`, `mask-border-slice`, `mask-border-width`
- `box-flex`, `box-flex-group`, `box-ordinal-group`, `initial-letter`, `max-lines`, `shape-image-threshold`
- `fill-opacity`, `flood-opacity`, `stop-opacity`, `stroke-opacity`, `stroke-width`, `stroke-dashoffset`, `stroke-miterlimit`

<br />

Expand Down
126 changes: 126 additions & 0 deletions apps/landing/src/app/(detail)/docs/api/selector/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,129 @@ The attributes defined in Pseudo-Class correspond to those defined in Selector,
</tr>
</tbody>
</table>

## Media Shorthands

These props wrap their styles in a fixed media query. They accept responsive arrays and nest in either direction with selectors and other at-rules.

<br />

<table>
<thead>
<tr>
<th width="300px">Selector</th>
<th width="300px">Media Query</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>_print</code>
</td>
<td>
<code>print</code>
</td>
</tr>
<tr>
<td>
<code>_screen</code>
</td>
<td>
<code>screen</code>
</td>
</tr>
<tr>
<td>
<code>_all</code>
</td>
<td>
<code>all</code>
</td>
</tr>
<tr>
<td>
<code>_portrait</code>
</td>
<td>
<code>(orientation:portrait)</code>
</td>
</tr>
<tr>
<td>
<code>_landscape</code>
</td>
<td>
<code>(orientation:landscape)</code>
</td>
</tr>
<tr>
<td>
<code>_motionReduce</code>
</td>
<td>
<code>(prefers-reduced-motion:reduce)</code>
</td>
</tr>
<tr>
<td>
<code>_motionSafe</code>
</td>
<td>
<code>(prefers-reduced-motion:no-preference)</code>
</td>
</tr>
<tr>
<td>
<code>_contrastMore</code>
</td>
<td>
<code>(prefers-contrast:more)</code>
</td>
</tr>
<tr>
<td>
<code>_contrastLess</code>
</td>
<td>
<code>(prefers-contrast:less)</code>
</td>
</tr>
<tr>
<td>
<code>_forcedColors</code>
</td>
<td>
<code>(forced-colors:active)</code>
</td>
</tr>
</tbody>
</table>

```tsx
const example = (
<Box
_motionReduce={{ transition: 'none' }}
_motionSafe={{ _hover: { transform: 'scale(1.05)' } }}
transition={['opacity .2s', null, 'all .3s']}
/>
)
```

## At-Rules

`_media`, `_supports` and `_container` take a record of queries to styles. A query can also sit in the key itself, as in Emotion, and `globalCss` accepts at-rules around whole selector maps.

```tsx
const example = (
<Box
_media={{ '(min-width: 500px)': { p: 2 } }}
selectors={{ '@media print': { display: 'none' } }}
/>
)

globalCss({
_motionReduce: { '*, *::before, *::after': { transition: 'none' } },
})
```

Conditions always win over breakpoint values: at-rule styles come after every responsive rule. Among themselves they are ordered by condition, with width queries mobile-first (`min-width` ascending, then `max-width` descending), then media types, other features, and user preferences such as `prefers-*` last.
6 changes: 6 additions & 0 deletions apps/landing/src/app/(detail)/docs/api/v-stack/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ However, the following properties are exceptions and **are not multiplied by 4**
- `grid-row`, `grid-row-start`, `grid-row-end`
- `animation-iteration-count`
- `tab-size`, `moz-tab-size`, `-webkit-line-clamp`
- `grid-area`
- `column-count`, `columns`, `orphans`, `widows`, `zoom`
- `border-image`, `border-image-outset`, `border-image-slice`, `border-image-width`
- `mask-border`, `mask-border-outset`, `mask-border-slice`, `mask-border-width`
- `box-flex`, `box-flex-group`, `box-ordinal-group`, `initial-letter`, `max-lines`, `shape-image-threshold`
- `fill-opacity`, `flood-opacity`, `stop-opacity`, `stroke-opacity`, `stroke-width`, `stroke-dashoffset`, `stroke-miterlimit`

<br />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ Or use responsive typography from `devup.json`:
```

```tsx
<Text typography="$h1">Responsive Heading</Text>
<Text typography="h1">Responsive Heading</Text>
```

## Responsive with Pseudo Selectors
Expand Down
4 changes: 2 additions & 2 deletions apps/landing/src/app/(detail)/docs/devup/devup-json/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,8 @@ See [Shadow](/docs/devup/shadow) for more details.
### Typography

```tsx
const headingExample = <Text typography="$h1">Heading</Text>
const bodyExample = <Text typography="$body">Body text</Text>
const headingExample = <Text typography="h1">Heading</Text>
const bodyExample = <Text typography="body">Body text</Text>
```

### Length
Expand Down
Loading
Loading