-
Notifications
You must be signed in to change notification settings - Fork 80
Open
Labels
good first issueGood for newcomersGood for newcomershelp wantedExtra attention is neededExtra attention is needed
Milestone
Description
Summary
Add a css prop for inline styles with support for nested selectors, pseudo-classes, and media queries. Styles are scoped to the component using the per-definition identifier (#325).
Prior Art
This proposal is directly inspired by Remix 3's css prop implementation.
Motivation
Current CSS-in-JS options require either:
- Build-time transforms (vanilla-extract, Panda CSS)
- Runtime libraries with large bundles (Emotion, styled-components)
- Manual class name management
A built-in css prop would provide ergonomic scoped styles with zero dependencies.
Proposed API
function Button({primary}) {
return (
<button css={{
color: 'white',
background: primary ? 'blue' : 'gray',
'&:hover': {
opacity: 0.8,
},
'@media (max-width: 768px)': {
padding: 8, // auto-appends 'px'
},
}}>
Click me
</button>
);
}How it works
- Component has stable ID via per-definition identifier (Expose per-definition component identifier #325)
- CSS object is hashed to generate variant suffix
- Final class:
${componentId}-${variantHash}(e.g.,c-7f3a-x9k2) - CSS is generated and injected once per unique class
- Renderer manages style injection/deduplication with ref-counting
Features
- Object syntax with camelCase properties
&for pseudo-selectors:&:hover,&:active,&::before&for attribute selectors:&[disabled],&[aria-selected="true"]- Child selectors:
.icon,> span - Media queries:
@media (...) @keyframessupport- Auto
pxsuffix for numeric values (except unitless props likez-index,opacity)
Why separate from style prop?
Keeping css and style as separate props allows:
style→ always inline via CSSOM, fast updates, no stylesheet overheadcss→ always generated class, supports pseudo/media, deduped
This gives control over rendering strategy. Use style for highly dynamic values (animations), css for static/scoped styles. Also keeps TypeScript types clean.
Implementation Sketch
~200 lines, runtime only:
processStyle(obj)- converts object to CSS string + hashstyleToCss(obj, selector)- handles nested selectors recursivelyStyleManager- injects styles viaadoptedStyleSheets, ref-counts for cleanup
Alternative: Raw Scoped Styles
The component ID also enables manual scoped CSS:
function Button() {
return <>
<button class={this.id}>Click me</button>
<style>{`
.${this.id} { color: white; background: blue; }
.${this.id}:hover { background: darkblue; }
`}</style>
</>;
}Open Questions
- Depends on Expose per-definition component identifier #325 (per-definition identifier)
- SSR story: collect CSS during render, inline in
<style>tag? - Should
<style>in JSX be special-cased for deduplication?
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
good first issueGood for newcomersGood for newcomershelp wantedExtra attention is neededExtra attention is needed