Skip to content

[atoms] support for inline styles#1406

Open
mellyeliu wants to merge 13 commits intomainfrom
inline-styles
Open

[atoms] support for inline styles#1406
mellyeliu wants to merge 13 commits intomainfrom
inline-styles

Conversation

@mellyeliu
Copy link
Member

@mellyeliu mellyeliu commented Dec 22, 2025

Implementation

Adding support for inline styles authoring with a new @stylexjs/atoms package. This allows you to write styles inline in stylex.props() that are pre-compiled within the props visitor using much of the create logic for static and dynamic styles.

This is something we went back and forth on a few times, but ultimately felt that it's good to provide the optionality. We still recommend (and want to bake in enough friction) for people to use stylex.create namespaces for the majority of usecases for readability/maintainability reasons.

import * as stylex from '@stylexjs/stylex';
import x from '@stylexjs/atoms';

<div {...stylex.props(
  x.display.flex,
  x.padding._16px,
  x.width['calc(100% - 20cqi)'],
  x.color(color),
)} />

The naming of all the above is up for discussion, as well as what styles we should build into this API. I'm also using Properties type from estree for type checking for a first pass, but there might be something better we can use.

Testing

Style types:

  • Static values
  • Leading underscores
  • Object key syntax
  • Dynamic inline styles

Tested within docs page the above +

  • Different merges of dynamic, regular, and inline styles
  • Named imports

Deferring to follow ups:

  • Rethink contextual styles
  • Make work with valid-styles / some way to add linter validation
  • Add docs
  • Rethink package name + standard import alias
  • figure out import and package naming?

@meta-cla meta-cla bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Dec 22, 2025
@mellyeliu mellyeliu marked this pull request as draft December 22, 2025 09:17
@github-actions
Copy link

github-actions bot commented Dec 22, 2025

workflow: benchmarks/perf

Comparison of performance test results, measured in operations per second. Larger is better.
yarn workspace v1.22.22
yarn run v1.22.22
$ node ./compare.js /tmp/tmp.N1GrRq1rh6 /tmp/tmp.KXnxdjCmNw

Results Base Patch Ratio
babel-plugin: stylex.create
· basic create 537 534 0.99 -
· complex create 65 66 1.02 +
babel-plugin: stylex.createTheme
· basic themes 438 442 1.01 +
· complex themes 34 33 0.97 -
Done in 0.07s.
Done in 0.31s.

@github-actions
Copy link

github-actions bot commented Dec 22, 2025

workflow: benchmarks/size

Comparison of minified (terser) and compressed (brotli) size results, measured in bytes. Smaller is better.
yarn workspace v1.22.22
yarn run v1.22.22
$ node ./compare.js /tmp/tmp.6SoQkS5A6T /tmp/tmp.fgMXH0f1hM

Results Base Patch Ratio
@stylexjs/stylex/lib/cjs/stylex.js
· compressed 1,311 1,311 1.00
· minified 4,150 4,150 1.00
@stylexjs/stylex/lib/cjs/inject.js
· compressed 1,793 1,793 1.00
· minified 4,915 4,915 1.00
benchmarks/size/.build/bundle.js
· compressed 496,650 496,650 1.00
· minified 4,847,840 4,847,840 1.00
benchmarks/size/.build/stylex.css
· compressed 99,867 99,867 1.00
· minified 747,613 747,613 1.00
Done in 0.08s.
Done in 0.31s.

} & ((value: V) => StyleXStyles);

type InlineCSS = {
[Key in keyof Properties<string | number>]: InlineValue<
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like everything in Properties is optional and the Required utility type can help.

Suggested change
[Key in keyof Properties<string | number>]: InlineValue<
[Key in keyof Required<Properties<string | number>>]: InlineValue<

It would be cool if StyleXCSSTypes.js also used csstype or at least the same thing.

Copy link
Collaborator

@nmn nmn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good start, but a few changes are needed.

Comment on lines +22 to +25
if (typeof prop === 'string') {
return valueProxy(prop);
}
return undefined;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can probably just throw here directly and not define valueProxy at all.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, let's add Flow types to this and auto-generate the TS types from that.

Comment on lines +30 to +33
if (specifier.type === 'ImportNamespaceSpecifier') {
state.inlineCSSImports.set(specifier.local.name, '*');
} else if (specifier.type === 'ImportDefaultSpecifier') {
state.inlineCSSImports.set(specifier.local.name, '*');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ESM doesn't allow an arbitrary proxy for exports from a package. So maybe we ONLY allow a named or default import here?

I think limiting this to a named import makes the most sense right now.

Comment on lines +238 to +239
import * as css from '@stylexjs/inline-css';
stylex.props(css.display.flex);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import * as css from '@stylexjs/inline-css';
stylex.props(css.display.flex);
import {css} from '@stylexjs/inline-css';
stylex.props(css.display.flex);

Comment on lines +34 to +35
module.exports = inlineCSS;
module.exports.default = inlineCSS;
Copy link
Collaborator

@henryqdineen henryqdineen Dec 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like it's rare at this point to see frontend code be published CJS-only. Could this be written in ESM and then use babel at build time to create a CJS version?

I have a similar nitpick about styleq but I see there is an open PR for that. One advantage is that it would make the @stylexjs/stylex rollup bundle slightly smaller.

@vercel
Copy link

vercel bot commented Jan 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stylex Ready Ready Preview, Comment Feb 23, 2026 11:43pm

Request Review

k1xSpc: "x78zum5",
$$css: true
};
stylex.props(_temp, styles.opacity(0.5));"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a few more tests with this kind of pattern. I want to see what happens to inline styles when applied conditionally but stylex.props cannot be pre-compiled more.

Also, to validate the hoisting logic, can you make add some examples where the stylex.props() exists inside of a function and not the top level.

@mellyeliu mellyeliu changed the title [inline-css] support for inline styles [stylex/atoms] support for inline styles Jan 21, 2026
@mellyeliu mellyeliu changed the title [stylex/atoms] support for inline styles [atoms] support for inline styles Jan 21, 2026
- Rename package from inline-css to utility-styles
- Update package.json, README, types, and source files
- Update stylex package dependency and re-export
- utility-styles/babel-transform.js: transforms css.display.flex → { display: 'flex' }
- babel-plugin/stylex-props.js: compiles raw objects using styleXCreateSet
- No dependency on @stylexjs/shared refactor (uses ../shared)
- All 797 tests pass
- Rename package from @stylexjs/utility-styles to @stylexjs/atoms
- Update all imports and references in babel-plugin
- Update stylex package dependency and re-export
- Fix type definitions to be compatible with stylex.props()
- Update test descriptions and imports
- Fix TypeScript types: Atoms now uses mapped type over keyof CSSProperties
  with -? to remove optionality, so s.colorr errors and s.color doesn't
  require an undefined check
- Fix state.inlineCSSImports → state.atomImports mismatch in babel-transform
- Remove dead code: inlineCSSImports field, css named import checks, and
  unreachable atoms handling inside importSources guard
- Rename INLINE_CSS_SOURCES → ATOMS_SOURCES throughout
- Add compile-time CSS property validation respecting propertyValidationMode
- Move VALID_CSS_PROPERTIES set to @stylexjs/shared for single source of truth
- Remove circular dependency: stylex no longer depends on or re-exports atoms
- Export CSSProperties from StyleXTypes.d.ts so atoms can import it
- Document normalizeValue underscore-stripping behavior
- Document compileRawStyleObjects scope
The gen-types tool (flow-api-translator) cannot handle runtime code
with type annotations, causing build failures. Move the
VALID_CSS_PROPERTIES set back into atoms/babel-transform.js directly
and remove the @stylexjs/shared dependency from atoms.
@stephenh
Copy link

stephenh commented Mar 7, 2026

(Edit: for those interested, I have a WIP PR where I thought I could get away with codegen-ing stylex.creates into our Css.ts file, but after getting it working surprisingly well, I unfortunately realized that selectors fundamentally need their own per-file/per-usage stylex.create calls, so I'll have to reset this approach.)

Fwiw when this PR is released, I'm hoping to port our truss "DSL on top of emotion" over to this/stylex, by doing things like (very proof-of-concept):

// in our Css.ts file
class CssBuilder {
  styles = {}
  get df() {
    return new CssBuilder({ ...this.styles, ...x.display.flex });
  }
  get aic() {
    return new CssBuilder({ ...this.styles, ...x.alignItems.center });
  }
  get $() {
    return this.styles;
  }
}
export const Css = new CssBuilder();

// in a Component.tsx file
// use/write a `css` prop that calls `stylex.props(...)` on the hash from $
return <div css={Css.df.aic.$} />

// Hope that this is semantically the same as the PR's
return <div {...stylesx.props(x.display.flex, x.alignItems.center)} />

With the admittedly naive hope/assumption that each x.display.etc (and other atoms in the Css.ts builder) will be rewritten into top-level/build-time stylex.creates or what not, and after our CssBuilder accumulates them on each .df, .aic, etc call, we can just do a styles.props(...) on the resulting hash & get the appropriate css classes (or close enough).

Feel free to ignore me, but if you have any "that just might work" / "ofc that will obviously not work" / etc feedback, I'd appreciate it -- thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants