---
name: devup-ui
description: |
Zero-runtime CSS-in-JS preprocessor for React. Transforms JSX styles to static CSS at build time.
TRIGGER WHEN:
- Writing/modifying Devup UI components (Box, Flex, Grid, Text, Button, etc.)
- Using styling APIs: css(), styled(), globalCss(), keyframes()
- Configuring devup.json theme (colors, typography)
- Setting up build plugins (Vite, Next.js, Webpack, Rsbuild, Bun)
- Debugging "Cannot run on the runtime" errors
- Working with responsive arrays or pseudo-selectors (_hover, _dark, etc.)
---
# Devup UI
Build-time CSS extraction. No runtime JS for styling.
## Critical: Components Are Compile-Time Only
All `@devup-ui/react` components (`Box`, `Flex`, `Text`, etc.) throw `Error('Cannot run on the runtime')`. They are **placeholders** that build plugins transform to `
`.
```tsx
// BEFORE BUILD (what you write):
// AFTER BUILD (what runs in browser):
// + CSS: .a{background:red} .b{padding:16px} .c:hover{background:blue}
```
## Style Prop Syntax
### Shorthands (ALWAYS use these)
| Short | Full | Short | Full |
|-------|------|-------|------|
| `bg` | background | `m`, `mt`, `mr`, `mb`, `ml`, `mx`, `my` | margin-* |
| `p`, `pt`, `pr`, `pb`, `pl`, `px`, `py` | padding-* | `w`, `h` | width, height |
| `minW`, `maxW`, `minH`, `maxH` | min/max width/height | `boxSize` | width + height (same value) |
| `gap` | gap | | |
### Spacing Scale (× 4 = px)
```tsx
// padding: 4px
// padding: 16px
// padding: 16px (unitless string also × 4)
// padding: 20px (with unit = exact value)
```
### Responsive Arrays (5 breakpoints)
```tsx
// [mobile, mid, tablet, mid, PC] - 5 levels
// Use indices 0, 2, 4 most frequently. Use null to skip.
// mobile=red, tablet=blue, PC=yellow
// mobile=8px, tablet=16px, PC=24px
// mobile=100%, tablet+=50%
```
### Pseudo-Selectors (underscore prefix)
```tsx
```
### Dynamic Values = CSS Variables
```tsx
// Static value -> class
// className="a" + .a{background:red}
// Dynamic value -> CSS variable
// className="a" style={{"--a":props.color}} + .a{background:var(--a)}
// Conditional -> preserved
// className={isActive ? "a" : "b"}
```
### Dynamic Values with Custom Components
`css()` only accepts **static values** (extracted at build time). For dynamic values on custom components, use `
`:
```tsx
// WRONG - css() cannot handle dynamic values
const MyComponent = ({ width }) => (
// ERROR: width is dynamic!
);
// CORRECT - use Box with `as` prop for dynamic values
const MyComponent = ({ width }) => (
// Works: generates CSS variable
);
```
## Styling APIs
### css() Returns className String (NOT object)
```tsx
import { css, styled, globalCss, keyframes } from "@devup-ui/react";
import clsx from "clsx";
// css() returns a className STRING - use with className prop
const cardStyle = css({ bg: "white", p: 4, borderRadius: "8px" });
// CORRECT
// WRONG - css() is NOT an object to spread
// // ERROR!
// Combine multiple styles with clsx
const baseStyle = css({ p: 4, borderRadius: "8px" });
const activeStyle = css({ bg: "$primary", color: "white" });
// styleOrder={1} REQUIRED when mixing className with direct props
```
### styled() API
```tsx
// Styled component (familiar styled-components/Emotion API)
const Card = styled("div", { bg: "white", p: 4, _hover: { shadow: "lg" } });
```
### globalCss() and keyframes()
```tsx
// Global styles
globalCss({ body: { margin: 0 }, "*": { boxSizing: "border-box" } });
// Keyframes
const spin = keyframes({ from: { transform: "rotate(0)" }, to: { transform: "rotate(360deg)" } });
```
## Theme (devup.json)
```json
{
"theme": {
"colors": {
"default": { "primary": "#0070f3", "text": "#000" },
"dark": { "primary": "#3291ff", "text": "#fff" }
},
"typography": {
"heading": { "fontFamily": "Pretendard", "fontSize": "24px", "fontWeight": 700 }
}
}
}
```
Use colors with `$` prefix: ``
Use typography without prefix: ``
Theme API:
```tsx
import { useTheme, setTheme, getTheme, initTheme, ThemeScript } from "@devup-ui/react";
setTheme("dark"); // switch theme
const theme = useTheme(); // hook for current theme
// SSR hydration (add to )
```
## Build Plugin Setup
```ts
// vite.config.ts
import DevupUI from "@devup-ui/vite-plugin";
export default defineConfig({ plugins: [react(), DevupUI()] });
// next.config.ts
import { DevupUI } from "@devup-ui/next-plugin";
export default DevupUI({
// Next.js config here
});
// rsbuild.config.ts
import DevupUI from "@devup-ui/rsbuild-plugin";
export default defineConfig({ plugins: [DevupUI()] });
```
Options:
- `singleCss: true` - single CSS file (recommended for Turbopack)
- `include: ["@devup/hello"]` - process external libraries that use @devup-ui internally
```ts
// When using external library that uses @devup-ui (e.g. @devup/hello)
DevupUI({ include: ["@devup/hello"] }) // required to extract and merge their styles
```
## $color Token Scope
`$color` tokens only work in **JSX props**. Use `var(--color)` in external objects.
```tsx
// CORRECT - $color in JSX prop
// inline object OK
// WRONG - $color in external object (won't be transformed)
const colors = { active: '$primary' } // '$primary' stays as string literal
// broken!
// CORRECT - var(--color) in external object
const colors = { active: 'var(--primary)' }
// works
```
## Inline Variant Pattern (Preferred)
Use inline object indexing instead of external config objects:
```tsx
// PREFERRED - inline object indexing
// AVOID - external config object
const sizeStyles = { lg: { h: '48px' }, md: { h: '40px' } }
// unnecessary indirection
```
## Anti-Patterns (NEVER do)
| Wrong | Right | Why |
|-------|-------|-----|
| `` | `` | style prop bypasses extraction |
| `` | `` | css() returns string, not object |
| `css({ bg: variable })` | `` or `` | css()/globalCss() only accept static values |
| `$color` in external object | `var(--color)` in external object | $color only transformed in JSX props |
| No build plugin configured | Configure plugin first | Components throw at runtime without transformation |
| `as any` on style props | Fix types properly | Type errors indicate real issues |