roxcss is a runtime atomic CSS engine. It is a template-string tag function that parses class names on the fly and injects the matching CSS rules into the document. No build step, no configuration file, no purge phase — whatever class name you write, the rule is generated when it is first used.
roxcss 是一个运行时原子 CSS 引擎。它是一个模板字符串标签函数,在调用时动态解析类名并注入对应的 CSS 规则。没有构建步骤、没有配置文件、没有清除阶段——你写出什么类名,规则就在首次使用时生成。
-
Zero build, zero config — rules are generated and injected at runtime, the first time a class name is used. Nothing to precompile.
-
Framework-agnostic — works anywhere a template-string tag works: plain HTML, Vue, React, and more.
-
No hidden class names — the class you write is the class in the DOM and the selector in DevTools. What you see is what you typed.
-
Values, not magic —
w-170pxmeans exactlywidth: 170px. No numeric scale sits between the class name and the CSS. -
Tailwind-style preset — a built-in preset reuses Tailwind v4 utility names, ready to extend or replace (see Default Preset / 默认预设).
-
Synchronous — the rules are live the moment the call returns.
-
零构建、零配置:类名首次使用时,规则在运行时生成并注入,无需任何预编译。
-
框架无关:任何模板字符串标签可用的地方都能用——原生 HTML、Vue、React 等。
-
类名即真相:你写下的类名就是 DOM 中的类、DevTools 中的选择器。所见即所写。
-
无魔法值:
w-170px就是width: 170px。类名与 CSS 之间没有数值刻度。 -
Tailwind 风格预设:内置预设复用 Tailwind v4 工具类命名,可自由扩展或替换(见 Default Preset / 默认预设)。
-
天生同步:调用返回的瞬间,规则已经生效。
Install the package, then import the default instance and use it as a template tag. The class names you write are returned unchanged, and the matching rules are injected into <head>:
安装依赖后导入默认实例 rox,并把它作为模板标签使用。你写下的类名会原样返回,匹配的规则注入 <head>:
npm install roxcssimport { rox } from "roxcss";
// class names are returned unchanged / 类名原样返回
const className = rox`flex flex-col gap-16px p-24px rounded-8px hover:bg-blue`;
document.body.className = className;
// <body class="flex flex-col gap-16px p-24px rounded-8px hover:bg-blue">The call injects these rules into a <style data-roxcss> element in <head>:
这次调用向 <head> 中的 <style data-roxcss> 元素注入以下规则:
[class~="flex"] {
display: flex;
}
[class~="flex-col"] {
display: flex;
flex-direction: column;
}
[class~="hover:bg-blue"]:hover {
background: blue;
}The tag function is sync: when the call returns, the rules are live. Repeated calls with the same class names hit the cache and never touch the style layer again — steady-state rendering costs nothing beyond the returned string.
标签函数是同步的:调用返回时规则已生效。相同类名的重复调用命中缓存,之后不再触碰样式层——稳定态渲染除了返回字符串外零开销。
rox is the out-of-the-box instance built on the default preset. When you need a preset-free setup — a design system with its own vocabulary, a framework-specific shim, or an experiment — build your own matcher tree with createRox. The minimal instance below registers two spacing utilities and one layout utility, plus an md breakpoint modifier:
rox 是基于默认预设的开箱即用实例。当需要脱离预设——自有词汇的设计系统、框架专用垫片或临时实验——用 createRox 自建 matcher 树。下面的最小实例注册了两个间距工具、一个布局工具,以及 md 断点修饰符:
import { createRox } from "roxcss";
const myRox = createRox({
matchers: {
p: (v) => `padding:${v}`,
m: (v) => `margin:${v}`,
flex: {
"": () => "display:flex",
col: () => "display:flex;flex-direction:column",
},
},
modifiers: {
md: (selector, cssDecl) => `@media (min-width: 768px) { ${selector} { ${cssDecl} } }`,
},
});
myRox`md:flex-col p-16px`;
// the media-wrapped rule is injected / 媒体查询包裹的规则被注入No install, no bundler — load the package straight from a CDN in a plain HTML page. Two builds are published; pick one by how you use roxcss:
无需安装、无需打包——在普通 HTML 页面中直接从 CDN 加载。两个构建按使用方式选择:
The default build bundles the default preset — rox, createConfig, createModifiers, and the breakpoint table — ready to use:
默认构建内置默认预设——rox、createConfig、createModifiers 与断点表——开箱即用:
<script type="module">
import { rox } from "https://unpkg.com/roxcss/index.min";
// class names are returned unchanged, rules are injected / 类名原样返回并注入规则
document.body.className = rox`flex flex-col gap-16px p-24px hover:bg-blue`;
</script>The core build ships only the engine — createRox plus the DOM injection — at about 2 kB minified. Use it when you bring your own matchers and skip the preset:
核心构建只含引擎——createRox 与 DOM 注入——压缩后约 2 kB。自带 matcher、不需要预设时使用:
<script type="module">
import { createRox } from "https://unpkg.com/roxcss/core.min";
// build your own matchers, no default preset / 自建 matcher,无默认预设
const rox = createRox({
matchers: {
p: (v) => `padding:${v}`,
bg: (v) => `background:${v}`,
},
});
document.body.className = rox`p-24px bg-blue`;
</script>Both URLs serve the latest published version from unpkg; pin an exact version in production, e.g. https://unpkg.com/roxcss@0.0.0/index.min.
两个 URL 均由 unpkg 提供最新发布版本;生产环境建议固定版本,如 https://unpkg.com/roxcss@0.0.0/index.min。
A token is split by - into segments, and the segments walk a nested matcher tree with two kinds of nodes — functions and objects:
token 按 - 拆成段,段在嵌套的 matcher 树中逐段查找。节点只有两种——函数与对象:
A function swallows the remaining segments and is called with one argument per segment. It returns the declaration text — or null to mark failure.
函数吞掉剩余所有段,剩余段每段一个参数调用,返回声明文本——返回 null 表示失败。
// matcher definition (a function node) / matcher 定义(函数节点)
p: (v) => `padding:${v}`;
// token flow / token 解析流程
// p-24px
// → segments ["p", "24px"]
// → "p" is a function, swallows the rest → p("24px")
// → padding:24pxAn object is a container that remaining segments descend through as keys. The "" key is the fallback — used when the segments run out or no key matches.
对象是容器,剩余段作为键逐层下行。"" 键是兜底——段耗尽或无键匹配时使用。
// matcher definition (an object node) / matcher 定义(对象节点)
flex: {
"": () => "display:flex",
col: () => "display:flex;flex-direction:column",
}
// token flow / token 解析流程
// flex-col
// → segments ["flex", "col"]
// → "flex" is an object, descend by key "col" → flex.col()
// → display:flex;flex-direction:column
// flex
// → segments ["flex"], segments run out, object has "" → flex[""]()
// → display:flexMatch resolution, in order: exact key → "" fallback → failure. On failure the token is returned unchanged and no rule is injected. A function returning null also means failure.
匹配解析顺序:精确键 → "" 兜底 → 失败。失败时 token 原样返回且不注入规则。函数返回 null 同样视为失败。
Tokens split by : — the first parts are prefixes, the last part is the matcher key. Each prefix part is checked against the modifiers registry: registered ones are environment modifiers (at most one — they wrap the whole rule in @media, .dark, etc.), the rest are treated as pseudo-classes and appended to the selector freely in any order and amount.
token 按 : 拆分——前面的部分是前缀,最后一段是 matcher 键。每个前缀段先查 modifiers 注册表:命中的是环境修饰符(最多一个——它把整条规则包裹进 @media、.dark 等环境),其余一律视为伪类,任意数量、任意顺序拼接到选择器上。
// → [class~="hover:p-8px"]:hover { padding:8px }
myRox`hover:p-8px`;
// → @media (min-width:768px) { [class~="md:flex-col"] { display:flex;flex-direction:column } }
myRox`md:flex-col`;
// → @media (min-width:768px) { [class~="md:hover:focus:p-8px"]:hover:focus { padding:8px } }
myRox`md:hover:focus:p-8px`;Each token is resolved exactly once per instance. Injected and failed tokens are remembered in two Sets — a repeated render costs a single cache lookup and nothing else.
每个 token 在每个实例中只解析一次。已注入与已失败的 token 分别记入两个 Set——重复渲染只付出一次缓存查询,再无其他。
createRox creates a fully independent instance with its own matcher tree, injection caches (injected / failed), and rule memory. Multiple instances can coexist without sharing any state. Pair it with createConfig for a preset-based instance, or pass a hand-written tree for a preset-free one:
createRox 创建完全独立的实例——各自的 matcher 树、注入缓存(injected / failed)与规则内存。多个实例可并存,互不共享状态。配合 createConfig 得到基于预设的实例;传入手写 matcher 树则脱离预设:
-
matchersRecord<string, MatcherNode>- the matcher tree. Keys are segment roots, values are functions or nested objects (see How It Works / 工作方式).
- matcher 树。键是段根,值是函数或嵌套对象(见 How It Works / 工作方式)。
-
modifiersRecord<string, Modifier>(optional / 可选)- environment modifier registry, keyed by prefix name. Defaults to an empty registry, in which case every prefix is treated as a pseudo-class.
- 环境修饰符注册表,以前缀名为键。默认空注册表,此时所有前缀都按伪类处理。
The object returned by createRox — a callable template tag carrying a getCSS method.
createRox 的返回值——可调用的模板标签函数,挂载 getCSS 方法。
interface RoxInstance {
(strings: TemplateStringsArray, ...values: unknown[]): string;
getCSS(): string;
}-
(strings, ...values)- tag invocation: splits the interpolated string on whitespace, resolves each token, and joins the results with a space — the class-name string you can put on an element. Rules are flushed synchronously when the call returns.
- 标签调用:按空白拆分插值后的字符串,逐个解析 token,用空格连接结果——即可直接放到元素上的类名字符串。调用返回时规则同步写入。
-
getCSS()- returns every rule the instance has injected so far, joined with newlines. Useful for SSR style collection and debugging; works even without a DOM.
- 返回实例迄今注入的全部规则,换行连接。适用于 SSR 样式收集与调试,无 DOM 时同样可用。
The default instance: createRox(createConfig()) — the out-of-the-box matcher tree plus sm–2xl breakpoint modifiers. Use it directly when the default preset fits; build a custom instance with the factories below when it does not.
默认实例:createRox(createConfig())——开箱即用的 matcher 树加 sm–2xl 断点修饰符。默认预设适用时直接使用;不适用时用下面的工厂函数构建自定义实例。
Creates the default preset — a fresh matcher tree (90+ top-level roots) plus fresh breakpoint modifiers. Every call rebuilds the whole tree, so instances sharing a config never share references.
创建默认预设——全新的 matcher 树(90+ 顶层根)与全新的断点 modifiers。每次调用整树重建,共享同一 config 的实例之间零引用共享。
overridesPresetOverrides(optional / 可选)- recursive overrides merged on top of the defaults (see Customization / 自定义).
- 递归覆盖参数,合并到默认配置之上(见 Customization / 自定义)。
Creates breakpoint environment modifiers from a name → pixel table. The passed table completely determines the output — no implicit merging with the default.
根据"名称 → 像素"表创建断点环境修饰符。传入的表完全决定输出——不会隐式合并默认断点。
createModifiers({ ...defaultBreakpoints, xxl: 1700 });
// each breakpoint name produces a min-width media query / 每个断点名生成一条 min-width 媒体查询| Breakpoint / 断点 | Min-width / 最小宽度 |
|---|---|
sm |
640px |
md |
768px |
lg |
1024px |
xl |
1280px |
2xl |
1536px |
Every type is exported from roxcss alongside the functions. The structural types are spelled out in the entries above; the core shapes are:
全部类型与函数一同从 roxcss 导出。结构性类型的完整定义见上方 API 条目,核心形状如下:
-
MatcherFunction(...args: string[]) => string | null- leaf of the matcher tree; one argument per remaining segment. Returning
nullmarks failure. - matcher 树叶子;剩余段每段一个参数。返回
null表示匹配失败。
- leaf of the matcher tree; one argument per remaining segment. Returning
-
MatcherNodeMatcherFunction | { [key: string]: MatcherNode }- a leaf function, or a nested container whose keys are segment names and whose
""key is the fallback. - 叶子函数,或嵌套容器——键是段名,
""键为兜底。
- a leaf function, or a nested container whose keys are segment names and whose
-
Modifier(selector: string, cssDecl: string) => string- wraps a complete rule in an environment (
@media,.dark, …) given the final selector and declaration. - 根据最终选择器与声明,把完整规则包裹进环境(
@media、.dark等)。
- wraps a complete rule in an environment (
-
MatcherPatchMatcherFunction | null | { [key: string]: MatcherPatch }- the override shape for
createConfig: same asMatcherNode, plusnullto delete a key. createConfig的覆盖形状:与MatcherNode相同,多出的null表示删除键。
- the override shape for
-
RoxOptions{ matchers: Record<string, MatcherNode>; modifiers?: Record<string, Modifier> }- the input of
createRox. createRox的入参。
- the input of
-
Preset{ matchers: Record<string, MatcherNode>; modifiers?: Record<string, Modifier> }- the output of
createConfig. createConfig的返回。
- the output of
-
PresetOverrides{ matchers?: Record<string, MatcherPatch>; modifiers?: Record<string, Modifier | null> }- the input of
createConfig. createConfig的覆盖入参。
- the input of
-
RoxInstance— see theRoxInstanceentry above: a callable template tag withgetCSS.- 见上方
RoxInstance条目:可调用模板标签 +getCSS。
- 见上方
Recall the matcher shape from How It Works / 工作方式: each remaining segment arrives as one argument, and the function returns the declaration string. In practice, single-segment values read the argument directly, while multi-segment values spread:
回顾 How It Works / 工作方式 中的 matcher 形态:剩余段每段一个参数,函数返回声明字符串。实际写法上,单段值直接读取参数,多段值用 rest 展开:
createRox({
matchers: {
// one segment / 单段:text-14px → font-size:14px
text: (v) => `font-size:${v}`,
// multiple segments joined with spaces / 多段空格连接:rounded-4px-8px → border-radius:4px 8px
rounded: (...vs) => `border-radius:${vs.join(" ")}`,
},
});A function returning null marks the token as failed (warning in dev, nothing injected).
函数返回 null 表示匹配失败(开发环境警告,不注入)。
A modifier receives the final selector and the CSS declaration, and returns a full rule:
modifier 接收最终选择器与 CSS 声明,返回完整规则:
const modifiers = {
dark: (selector, cssDecl) => `.dark ${selector} { ${cssDecl} }`,
maxLg: (selector, cssDecl) => `@media (max-width: 1024px) { ${selector} { ${cssDecl} } }`,
};createConfig accepts recursive overrides: objects merge key by key (defaults survive), functions and other values replace wholesale, null deletes a key. The engine treats any non-function non-object value as "key absent", so deletion needs no special case.
createConfig 支持递归覆盖:对象逐键合并(默认键保留),函数与其他值整体替换,null 删除键。引擎把非函数非普通对象的值一律视为"键不存在",删除因此无需特判。
import { createRox, createConfig } from "roxcss";
const rox = createRox(
createConfig({
modifiers: {
lg: (selector, cssDecl) =>
// override / 覆盖默认的 min-width 语义
`@media (max-width: 1024px) { ${selector} { ${cssDecl} } }`,
},
matchers: {
// merge into the default flex subtree / 合并进默认 flex 子树
flex: { half: () => "flex:1 1 calc(50% - 8px)" },
// replace a whole subtree / 整体替换某子树
shadow: () => "box-shadow:var(--shadow)",
// delete a root / 删除某个根
animate: null,
},
}),
);roxcss does not interpret values, so CSS variables compose naturally. A common pattern is a private helper that turns segments into a var() reference:
roxcss 不解析值,CSS 变量因此天然可组合。常见做法是私有 helper 把段拼成 var() 引用:
// segments → var(--accent-bg) / 段 → var(--accent-bg)
const cssVar = (vs: string[]) => `var(--${vs.join("-")})`;
createRox(
createConfig({
matchers: {
color: (...vs) => `color:${cssVar(vs)}`,
bg: (...vs) => `background:${cssVar(vs)}`,
border: {
color: (...vs) => `border-color:${cssVar(vs)}`,
t: () => "border-top:1px solid var(--border)",
},
},
}),
);The default preset borrows its vocabulary from Tailwind v4 — the same utility names, the same nesting — so familiar classes keep their meaning. Two conventions differ from Tailwind, and knowing them once is enough to read everything below:
默认预设的词汇取自 Tailwind v4——相同的工具类名、相同的嵌套结构——熟悉的类名含义不变。两处约定与 Tailwind 不同,先了解它们,下面的示例就都能读懂:
roxcss has no numeric scale: p-4 does not mean 1rem as it does in Tailwind. Write the full value and it lands in the declaration verbatim — p-4px → padding:4px, w-170px → width:170px. A bare number produces invalid CSS; that is a caller error.
roxcss 没有数值刻度:p-4 不像在 Tailwind 中那样表示 1rem。单位写全,值原样进入声明——p-4px → padding:4px、w-170px → width:170px。裸数字会生成无效 CSS,属调用方错误。
Tailwind overloads text-* for both sizing and coloring; roxcss keeps them apart — text-16px sets font-size, color-red sets color.
Tailwind 用 text-* 兼任字号与颜色;roxcss 分开处理——text-16px 设置字号,color-red 设置颜色。
The preset covers layout, spacing, typography, borders, colors, and effects. A selection follows, grouped by category; the full listing lives in docs/预设设计.md.
预设覆盖布局、间距、排版、边框、颜色与效果。以下按类别节选,完整清单见 docs/预设设计.md。
flex → display:flex
flex-col → display:flex;flex-direction:column
grid-cols-3 → display:grid;grid-template-columns:repeat(3,minmax(0,1fr))
items-center → align-items:center
justify-between → justify-content:space-between
gap-16px → gap:16px
p-24px → padding:24px
w-170px → width:170px
text-16px → font-size:16px
text-center → text-align:center
font-bold → font-weight:700
leading-16px → line-height:16px
underline → text-decoration-line:underline
truncate → overflow:hidden;text-overflow:ellipsis;white-space:nowrap
rounded-5px → border-radius:5px
border-2px-solid-red → border:2px solid red
bg-blue → background:blue
color-red → color:red
duration-150ms → transition-duration:150ms
delay-75ms → transition-delay:75ms
relative → position:relative
inset-x-0 → inset-inline:0
z-10 → z-index:10
overflow-x-auto → overflow-x:auto
hidden → display:none
list-none → list-style:none
aspect-16/9 → aspect-ratio:16/9
Families share a pattern: every side and axis variant exists — m mirrors p, overflow-y mirrors overflow-x, and so on.
工具类家族遵循同一模式:每个方向与轴向的变体都存在——m 与 p 对应,overflow-y 与 overflow-x 对应,依此类推。
Breakpoints come from createModifiers() with defaultBreakpoints, all min-width media queries. Prefix a class name with a breakpoint and it applies from that width up:
断点由 createModifiers() 基于 defaultBreakpoints 生成,全部为 min-width 媒体查询。在类名前加断点前缀,规则从该宽度起生效:
<div class="flex-col lg:flex-row">…</div>Because class names are returned as plain strings, any class binding works. The string can be produced anywhere — a computed property, an inline handler, or a plain function — and handed to a framework's binding. A Vue example:
类名以普通字符串返回,因此任何 class 绑定方式都适用。字符串可以在任何位置生成——computed、事件处理器或普通函数——再交给框架的绑定。Vue 示例:
<script setup lang="ts">
import { rox } from "./rox"; // your configured instance / 你配置好的实例
</script>
<template>
<button
type="button"
:class="
rox`inline-flex font-mono text-16px px-10px rounded-5px
hover:bg-blue focus-visible:bg-blue`
"
>
Click me
</button>
</template>-
Batch flush per call. All new rules from one call are appended to the active
<style>bucket in a singletextContentwrite — one parse, one style invalidation. No per-ruleinsertRule(measured O(N²) under interleaved forced layout; see 性能分析.md). -
Rolling style buckets. The active
<style data-roxcss>element is reused until it holds 1000 rules, then frozen and a new one is created. Bucket count stays atceil(rules / 1000)— no DevTools panel clutter. -
SSR safe. With no DOM, rules are kept in memory and readable via
getCSS(). -
每次调用批量写入:一次调用产生的新规则,以一次
textContent赋值追加到活动<style>桶——一次解析、一次样式失效。不使用逐条insertRule(实测在交替强制布局场景退化为 O(N²),见 性能分析.md)。 -
滚动 style 桶:活动
<style data-roxcss>元素复用到 1000 条规则后冻结,新建下一个。桶数保持在 ceil(规则数 / 1000)——DevTools 面板不堆积。 -
SSR 安全:无 DOM 时规则保存在内存,经
getCSS()读取。
The design docs are written in Chinese and archived under docs/:
设计文档以中文撰写,归档在 docs/:
| Doc / 文档 | Content / 内容 |
|---|---|
| 设计方案.md | Engine architecture, token parsing, matcher lookup. |
| 引擎架构、token 解析、matcher 查找。 | |
| 预设设计.md | Default preset design and full utility listing. |
| 默认预设设计与完整工具类清单。 | |
| 样式表管理策略.md | Rolling <style> bucket injection design. |
| style 滚动桶注入设计。 | |
| 性能分析.md | Performance model and measurements. |
| 性能模型与实测。 | |
| tailwind对齐计划.md | Tailwind v4 alignment decisions and gaps. |
| Tailwind v4 对齐决策与差距。 |