|

356 stars
28 forks
Rust
156 views

SKILL.md


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(), globalCss(), keyframes()
  • Configuring devup.json theme (colors, typography, length, shadow, extends)
  • Setting up build plugins (Vite, Next.js, Webpack, Rsbuild, Bun)
  • Debugging "Cannot run on the runtime" errors
  • Working with responsive arrays, pseudo-selectors (_hover, _dark, etc.)
  • Using polymorphic as prop or selectors prop
  • Working with @devup-ui/components (Button, Input, Select, Toggle, etc.)
  • Using responsive length tokens ($containerX, $gutter) or shadow tokens ($card, $sm)

Devup UI

Build-time CSS extraction. No runtime JS for styling.

Critical: Components Are Compile-Time Only

All @devup-ui/react components throw Error('Cannot run on the runtime'). They are placeholders that build plugins transform to native HTML elements with classNames.

// BEFORE BUILD (what you write):
<Box bg="red" p={4} _hover={{ bg: "blue" }} />

// AFTER BUILD (what runs in browser):
<div className="a b c" />  // + CSS: .a{background:red} .b{padding:16px} .c:hover{background:blue}

Components

@devup-ui/react (Layout Primitives)

All are polymorphic (accept as prop). Default element is <div> unless noted.

Component Default Element Purpose
Box div Base layout primitive, accepts all style props
Flex div Flexbox container (shorthand for display: flex)
Grid div CSS Grid container
VStack div Vertical stack (flex column)
Center div Centered content
Text p Text/typography
Image img Image element
Input input Input element
Button button Button element
ThemeScript -- SSR theme hydration (add to <head>)

@devup-ui/components (Pre-built UI)

Higher-level components with built-in behavior. These are runtime components (not compile-time only).

Component Key Props
Button variant (primary/default), size (sm/md/lg), loading, danger, icon, colors
Checkbox children (label), onChange(checked), colors
Input error, errorMessage, allowClear, icon, typography, colors
Textarea error, errorMessage, typography, colors
Radio variant (default/button), colors
RadioGroup options[], direction (row/column), variant, value, onChange
Toggle variant (default/switch), value, onChange(boolean), colors
Select type (default/radio/checkbox), options[], value, onChange, colors
Stepper min, max, type (input/text), value, onValueChange

Select compound: SelectTrigger, SelectContainer, SelectOption, SelectDivider Stepper compound: StepperContainer, StepperDecreaseButton, StepperIncreaseButton, StepperInput Hooks: useSelect(), useStepper()

All components accept a colors prop object for runtime color customization via CSS variables.

Style Prop Syntax

Shorthand Props (ALWAYS prefer these)

Spacing (unitless number x 4 = px)

Shorthand CSS Property
m, mt, mr, mb, ml, mx, my margin-*
p, pt, pr, pb, pl, px, py padding-*

Sizing

Shorthand CSS Property
w width
h height
minW, maxW min-width, max-width
minH, maxH min-height, max-height
boxSize width + height (same value)

Background

Shorthand CSS Property
bg background
bgColor background-color
bgImage, bgImg, backgroundImg background-image
bgSize background-size
bgPosition, bgPos background-position
bgPositionX, bgPosX background-position-x
bgPositionY, bgPosY background-position-y
bgRepeat background-repeat
bgAttachment background-attachment
bgClip background-clip
bgOrigin background-origin
bgBlendMode background-blend-mode

Border

Shorthand CSS Property
borderTopRadius border-top-left-radius + border-top-right-radius
borderBottomRadius border-bottom-left-radius + border-bottom-right-radius
borderLeftRadius border-top-left-radius + border-bottom-left-radius
borderRightRadius border-top-right-radius + border-bottom-right-radius

Layout & Position

Shorthand CSS Property
flexDir flex-direction
pos position
positioning Helper: "top", "bottom-right", etc. (sets edges to 0)
objectPos object-position
offsetPos offset-position
maskPos mask-position
maskImg mask-image

Typography

Shorthand Effect
typography Applies theme typography token (fontFamily, fontSize, fontWeight, lineHeight, letterSpacing)

All standard CSS properties from csstype are also accepted directly (e.g., display, gap, opacity, transform, animation, etc.).

Spacing Scale (unitless number x 4 = px)

<Box p={1} />    // padding: 4px
<Box p={4} />    // padding: 16px
<Box p="4" />    // padding: 16px (unitless string also x 4)
<Box p="20px" /> // padding: 20px (with unit = exact value)

Responsive Arrays (5 breakpoints)

// [mobile, mid, tablet, mid, PC] - 5 levels
// Use indices 0, 2, 4 most frequently. Use null to skip.

<Box bg={["red", null, "blue", null, "yellow"]} />  // mobile=red, tablet=blue, PC=yellow
<Box p={[2, null, 4, null, 6]} />                   // mobile=8px, tablet=16px, PC=24px
<Box w={["100%", null, "50%"]} />                   // mobile=100%, tablet+=50%

Pseudo-Selectors (underscore prefix)

<Box
  _hover={{ bg: "blue" }}
  _focus={{ outline: "2px solid blue" }}
  _focusVisible={{ outlineColor: "$primary" }}
  _active={{ bg: "darkblue" }}
  _disabled={{ opacity: 0.5 }}
  _before={{ content: '""' }}
  _after={{ content: '""' }}
  _firstChild={{ mt: 0 }}
  _lastChild={{ mb: 0 }}
  _placeholder={{ color: "gray" }}
/>

All CSS pseudo-classes and pseudo-elements from csstype are supported with _camelCase naming.

Group Selectors

Style children based on parent state:

<Box _groupHover={{ color: "blue" }} />
<Box _groupFocus={{ outline: "2px solid" }} />
<Box _groupActive={{ bg: "darkblue" }} />

Theme Selectors

<Box _themeDark={{ bg: "gray.900" }} />
<Box _themeLight={{ bg: "white" }} />

At-Rules (Media, Container, Supports)

// Underscore prefix syntax
<Box _print={{ display: "none" }} />
<Box _screen={{ display: "block" }} />
<Box _media={{ "(min-width: 768px)": { w: "50%" } }} />
<Box _container={{ "(min-width: 400px)": { p: 4 } }} />
<Box _supports={{ "(display: grid)": { display: "grid" } }} />

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

Custom Selectors

<Box selectors={{
  "&:hover": { color: "red" },
  "&::before": { content: '">"' },
  "&:nth-child(2n)": { bg: "gray" },
}} />

Dynamic Values = CSS Variables

// Static value -> class
<Box bg="red" />  // className="a" + .a{background:red}

// Dynamic value -> CSS variable
<Box bg={props.color} />  // className="a" style={{"--a":props.color}} + .a{background:var(--a)}

// Conditional -> preserved
<Box bg={isActive ? "blue" : "gray"} />  // className={isActive ? "a" : "b"}

Responsive + Pseudo Combined

<Box _hover={{ bg: ['red', 'blue'] }} />
// Alternative syntax:
<Box _hover={[{ bg: 'red' }, { bg: 'blue' }]} />

Special Props

as (Polymorphic Element)

Changes the rendered HTML element or renders a custom component:

<Box as="section" bg="gray" />         // renders <section>
<Box as="a" href="/about" />           // renders <a>
<Box as={MyComponent} bg="red" />      // renders <MyComponent> with extracted styles
<Box as={b ? "div" : "section"} />     // conditional element type

props (Pass-Through to as Component)

When as is a custom component, use props to pass component-specific props:

<Box as={MotionDiv} w="100%" props={{ animate: { duration: 1 } }} />

styleVars (Manual CSS Variable Injection)

<Box styleVars={{ "--custom-color": dynamicValue }} bg="var(--custom-color)" />

styleOrder (CSS Cascade Priority)

Controls specificity when combining className with direct props. Required when mixing css() classNames with inline style props.

<Box className={cardStyle} bg="$background" styleOrder={1} />
// Conditional styleOrder
<Box bg="red" styleOrder={isActive ? 1 : 0} />

Styling APIs

css() Returns className String (NOT object)

import { css, globalCss, keyframes } from "@devup-ui/react";
import clsx from "clsx";

// css() returns a className STRING
const cardStyle = css({ bg: "white", p: 4, borderRadius: "8px" });
<div className={cardStyle} />

// Combine with clsx
const baseStyle = css({ p: 4, borderRadius: "8px" });
const activeStyle = css({ bg: "$primary", color: "white" });
<Box className={clsx(baseStyle, isActive && activeStyle)} styleOrder={1} />

globalCss() and keyframes()

globalCss({ body: { margin: 0 }, "*": { boxSizing: "border-box" } });

const spin = keyframes({ from: { transform: "rotate(0)" }, to: { transform: "rotate(360deg)" } });
<Box animation={`${spin} 1s linear infinite`} />

Dynamic Values with Custom Components

css() only accepts static values. For dynamic values on custom components, use <Box as={Component}>:

// WRONG - css() cannot handle dynamic values
<CustomComponent className={css({ w: width })} />

// CORRECT - Box with as prop handles dynamic values via CSS variables
<Box as={CustomComponent} w={width} />

Theme (devup.json)

{
  "extends": ["./base-theme.json"],
  "theme": {
    "colors": {
      "default": { "primary": "#0070f3", "text": "#000", "bg": "#fff" },
      "dark": { "primary": "#3291ff", "text": "#fff", "bg": "#111" }
    },
    "typography": {
      "heading": {
        "fontFamily": "Pretendard",
        "fontSize": "24px",
        "fontWeight": 700,
        "lineHeight": 1.3,
        "letterSpacing": "-0.02em"
      },
      "body": [
        { "fontSize": "14px", "lineHeight": 1.5 },
        null,
        { "fontSize": "16px", "lineHeight": 1.6 }
      ]
    },
    "length": {
      "default": {
        "containerX": ["16px", null, "32px"],
        "gutter": ["8px", null, "16px"]
      }
    },
    "shadow": {
      "default": {
        "card": ["0 1px 2px #0003", null, null, "0 4px 8px #0003"],
        "sm": "0 1px 2px rgba(0,0,0,0.05)"
      }
    }
  }
}
  • Colors: Use with $ prefix in JSX props: <Box color="$primary" />
  • Typography: Use with $ prefix: <Text typography="$heading" />
  • 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)
  • Responsive typography/length/shadow: Use arrays with null for unchanged breakpoints

Length & Shadow Token Behavior

Length and shadow tokens support responsive arrays like typography. The key distinction is how $token behaves depending on syntax:

Syntax Behavior Classes
px="$containerX" Expands to all defined breakpoints Multiple
px={"$containerX"} Expands to all defined breakpoints Multiple
px={["$containerX"]} Single value at index 0 only 1
px={["8px", null, "$containerX"]} 8px at index 0, token at index 2 2

Both "$token" and {"$token"} expand the responsive token. Only {["$token"]} inside a responsive array keeps it as a single class — because the array itself defines the breakpoint levels.

Theme types are auto-generated via module augmentation of DevupTheme and DevupThemeTypography.

Theme API

import { useTheme, setTheme, getTheme, initTheme, ThemeScript } from "@devup-ui/react";

setTheme("dark");             // Switch theme (sets data-theme + localStorage)
const theme = getTheme();     // Get current theme name
const theme = useTheme();     // React hook (reactive)
initTheme();                  // Initialize on startup (auto-detect system preference)
<ThemeScript />               // SSR hydration script (add to <head>, prevents FOUC)

Build Plugin Setup

Vite

import DevupUI from "@devup-ui/vite-plugin";
export default defineConfig({ plugins: [react(), DevupUI()] });

Next.js

import { DevupUI } from "@devup-ui/next-plugin";
export default DevupUI({ /* Next.js config */ });

Rsbuild

import DevupUI from "@devup-ui/rsbuild-plugin";
export default defineConfig({ plugins: [DevupUI()] });

Webpack

import { DevupUIWebpackPlugin } from "@devup-ui/webpack-plugin";
// Add to plugins array

Bun

import { plugin } from "@devup-ui/bun-plugin";
// Auto-registers, always uses singleCss: true

Plugin Options

DevupUI({
  singleCss: true,           // Single CSS file (recommended for Turbopack)
  include: ["@devup/hello"],  // Process external libs using @devup-ui
  prefix: "du",              // Class name prefix (e.g., "du-a" instead of "a")
  debug: true,               // Enable debug logging
  importAliases: {           // Redirect imports from other CSS-in-JS libs
    "@emotion/styled": "styled",        // default: enabled
    "styled-components": "styled",      // default: enabled
    "@vanilla-extract/css": true,       // default: enabled
  },
})

$token Scope

$token values (colors, length, shadow) only work in JSX props. Use var(--token) in external objects.

// CORRECT - $token in JSX prop
<Box bg="$primary" />
<Box px="$containerX" />
<Box boxShadow="$card" />
<Box bg={{ active: '$primary', inactive: '$gray' }[status]} />

// WRONG - $token in external object (won't be transformed)
const colors = { active: '$primary' }
<Box bg={colors.active} />  // broken!

// CORRECT - var(--token) in external object
const colors = { active: 'var(--primary)' }
<Box bg={colors.active} />

Inline Variant Pattern (Preferred)

Use inline object indexing instead of external config objects:

// PREFERRED - inline object indexing (build-time extractable)
<Box
  h={{ lg: '48px', md: '40px', sm: '32px' }[size]}
  bg={{ primary: '$primary', secondary: '$gray100' }[variant]}
/>

// AVOID - external config object (becomes dynamic, uses CSS variables)
const sizeStyles = { lg: { h: '48px' }, md: { h: '40px' } }
<Box h={sizeStyles[size].h} />

Anti-Patterns (NEVER do)

Wrong Right Why
<Box style={{ color: "red" }}> <Box color="red"> style prop bypasses extraction
<Box {...css({...})} /> <Box className={css({...})} /> css() returns string, not object
css({ bg: variable }) <Box bg={variable}> or <Box as={Comp} bg={variable}> 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
@ts-ignore / @ts-expect-error Fix the type issue Suppression hides real problems
background="red" bg="red" Always use shorthands
padding={4} p={4} Always use shorthands
width="100%" w="100%" Always use shorthands
styled("div", {...}) <Box bg="red" /> Use Box component with props, not styled()
stylex.create({...}) <Box bg="red" /> Use Box component with props, not stylex

README

Devup UI logo

The Future of CSS-in-JS — Zero Runtime, Full Power

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



English | 한국어

Why Devup UI?

Devup UI isn't just another CSS-in-JS library — it's the next evolution.

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
  • 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
  • Fastest Build Times: Rust + WebAssembly delivers unmatched preprocessing speed

Install

npm install @devup-ui/react

# on next.js
npm install @devup-ui/next-plugin

# on vite
npm install @devup-ui/vite-plugin

# on rsbuild
npm install @devup-ui/rsbuild-plugin

# on webpack
npm install @devup-ui/webpack-plugin

Features

  • Preprocessor — All CSS extraction happens at build time
  • Zero Config — Works out of the box with sensible defaults
  • Zero FOUC — No flash of unstyled content, no Provider required
  • Zero Runtime — No client-side JavaScript for styling
  • RSC Support — Full React Server Components compatibility
  • Library Mode — Build component libraries with extracted styles
  • Dynamic Themes — Zero-cost theme switching via CSS variables
  • Type-Safe Themes — Full TypeScript support for theme tokens
  • Smallest & Fastest — Proven by benchmarks

Comparison Benchmarks

Next.js Build Time and Build Size (github action - ubuntu-latest)

Library Version Build Time Build Size
tailwindcss 4.1.13 19.31s 59,521,539 bytes
styleX 0.15.4 41.78s 86,869,452 bytes
vanilla-extract 1.17.4 19.50s 61,494,033 bytes
kuma-ui 1.5.9 20.93s 69,924,179 bytes
panda-css 1.3.1 20.64s 64,573,260 bytes
chakra-ui 3.27.0 28.81s 222,435,802 bytes
mui 7.3.2 20.86s 97,964,458 bytes
devup-ui(per-file css) 1.0.18 16.90s 59,540,459 bytes
devup-ui(single css) 1.0.18 17.05s 59,520,196 bytes
tailwindcss(turbopack) 4.1.13 6.72s 5,355,082 bytes
devup-ui(single css+turbopack) 1.0.18 10.34s 4,772,050 bytes

How it works

Devup UI transforms your components at build time. Class names are generated using a compact base-37 encoding for minimal CSS size.

Basic transformation:

// You write:
const variable = <Box _hover={{ bg: 'blue' }} bg="red" p={4} />

// Devup UI generates:
const variable = <div className="a b c" />

// With CSS:
// .a { background-color: red; }
// .b { padding: 1rem; }
// .c:hover { background-color: blue; }

Dynamic values become CSS variables:

// You write:
const example = <Box bg={colorVariable} />

// Devup UI generates:
const example = <div className="a" style={{ '--a': colorVariable }} />

// With CSS:
// .a { background-color: var(--a); }

Complex expressions and responsive arrays — fully supported:

// You write:
const example = <Box bg={['red', 'blue', isActive ? 'green' : dynamicColor]} />

// Devup UI generates:
const example = (
  <div
    className={`a b ${isActive ? 'c' : 'd'}`}
    style={{ '--d': dynamicColor }}
  />
)

// With responsive CSS for each breakpoint

Type-safe theming:

devup.json

{
  "theme": {
    "colors": {
      "default": {
        "primary": "#0070f3",
        "text": "#000"
      },
      "dark": {
        "primary": "#3291ff",
        "text": "#fff"
      }
    },
    "typography": {
      "heading": {
        "fontFamily": "Pretendard",
        "fontSize": "24px",
        "fontWeight": 700,
        "lineHeight": 1.3
      }
    }
  }
}
// Type-safe theme tokens
const textExample = <Text color="$primary" />
const boxExample = <Box typography="$heading" />

Responsive + Pseudo selectors together:

// Responsive with pseudo selector
const example = <Box _hover={{ bg: ['red', 'blue'] }} />

// Equivalent syntax
const example2 = <Box _hover={[{ bg: 'red' }, { bg: 'blue' }]} />

styled-components / Emotion compatible styled() API:

import { styled } from '@devup-ui/react'

// Familiar syntax for styled-components and Emotion users
const Card = styled('div', {
  bg: 'white',
  p: 4, // 4 * 4 = 16px
  borderRadius: '8px',
  boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
  _hover: {
    boxShadow: '0 10px 15px rgba(0, 0, 0, 0.1)',
  },
})

const Button = styled('button', {
  px: 4, // 4 * 4 = 16px
  py: 2, // 2 * 4 = 8px
  borderRadius: '4px',
  cursor: 'pointer',
})

// Usage
const cardExample = <Card>Content</Card>
const buttonExample = <Button>Click me</Button>

Inspirations

  • Styled System
  • Chakra UI
  • Theme UI
  • Vanilla Extract
  • Rainbow Sprinkles
  • Kuma UI

How to Contribute

Requirements

Development Setup

To set up the development environment, install the following packages:

bun install
bun run build
cargo install cargo-tarpaulin
cargo install wasm-pack

After installation, run bun run test to ensure everything works correctly.