typedcss
The same authoring API, merged at runtime instead of at build time.
typedcss is Plumeria with the merge moved to runtime. You write the same css.create, the same sx prop and the same definitions; the compiler still turns every style into atomic CSS. What changes is who picks the winning class: a few lines of styleq on the element, instead of the compiler at the call site.
Both release under the same version number. A typedcss release and a Plumeria release with the same version accept the same source.
What it costs, and what it buys
Plumeria resolves every merge at build time, so it has to see where a style ends up. A style handed through a prop to a component in another package, or stored and applied later, is a merge the compiler cannot see — which is what no-style-prop-relay reports.
typedcss carries a class map to the element and merges there. A style can travel anywhere a value can: through props, context, other packages, or a variable. The price is a small runtime and the maps in the bundle.
import * as css from '@typedcss/css';
const styles = css.create({
badge: { padding: 4, color: 'gray' },
});
export const Badge = ({ sx }: { sx?: css.Style }) => <span sx={[styles.badge, sx]} />;The right side still wins, property by property. Only the place where that is decided moves.
css.props
Plumeria's css.use returns a class string the compiler has already merged. typedcss has css.props instead, which merges at runtime and returns { className, style }, ready to spread onto any element:
<Link href="/" {...css.props(styles.link, active && styles.active)} />The names differ because the results do.
Packages
| typedcss | Plumeria |
|---|---|
@typedcss/css | @plumeria/core |
@typedcss/oxide | @plumeria/compiler |
@typedcss/next-plugin | @plumeria/next-plugin |
@typedcss/unplugin | @plumeria/unplugin |
@typedcss/eslint-plugin | @plumeria/eslint-plugin |
@typedcss/swc-jest | @plumeria/swc-jest |
@typedcss/init | @plumeria/init |
@typedcss/codemod | @plumeria/codemod |
Each takes the same options as its Plumeria counterpart, so the integration guides and the plugin reference apply as written. The ESLint plugin drops the rules that only exist because merges happen at build time: no-style-prop-relay, no-use-in-jsx and custom-props-require-import.
npx @typedcss/initMoving between them
Swap the dependencies and the imports. Code written with sx and the definitions moves as it is; only the merge calls are rewritten by hand, between a class string and a spread:
- <Link href="/" className={css.use(styles.link, active && styles.active)} />
+ <Link href="/" {...css.props(styles.link, active && styles.active)} />