npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

styled-components-to-stylex-codemod

v0.0.9

Published

Codemod to transform styled-components to StyleX

Readme

styled-components-to-stylex-codemod

Transform styled-components to StyleX.

Try it in the online playground — experiment with the transform in your browser.

[!WARNING]

Very much under construction (alpha): this codemod is still early in development — expect rough edges! 🚧

Installation

npm install styled-components-to-stylex-codemod
# or
pnpm add styled-components-to-stylex-codemod

Usage

Use runTransform to transform files matching a glob pattern:

import {
  runTransform,
  defineAdapter,
} from "styled-components-to-stylex-codemod";

const adapter = defineAdapter({
  resolveValue(ctx) {
    if (ctx.kind === "theme") {
      // Called for patterns like: ${(props) => props.theme.color.primary}
      // `ctx.path` is the dotted path after `theme.`
      const varName = ctx.path.replace(/\./g, "_");
      return {
        expr: `tokens.${varName}`,
        imports: [
          {
            from: { kind: "specifier", value: "./design-system.stylex" },
            names: [{ imported: "tokens" }],
          },
        ],
      };
    }

    if (ctx.kind === "cssVariable") {
      // Called for CSS values containing `var(--...)`
      // Note: `fallback` is the raw fallback string inside `var(--x, <fallback>)` (if present).
      // Note: `definedValue` is populated when the transformer sees a local `--x: <value>` definition.
      const { name, fallback, definedValue } = ctx;

      // Example: lift `var(--base-size)` to StyleX vars, and optionally drop a matching local definition.
      if (name === "--base-size") {
        return {
          expr: "calcVars.baseSize",
          imports: [
            {
              from: { kind: "specifier", value: "./css-calc.stylex" },
              names: [{ imported: "calcVars" }],
            },
          ],
          ...(definedValue === "16px" ? { dropDefinition: true } : {}),
        };
      }

      // Generic mapping: `--kebab-case` -> `vars.kebabCase`
      // e.g. `--color-primary` -> `vars.colorPrimary`
      const toCamelCase = (cssVarName: string) =>
        cssVarName
          .replace(/^--/, "")
          .split("-")
          .filter(Boolean)
          .map((part, i) =>
            i === 0 ? part : part[0]?.toUpperCase() + part.slice(1)
          )
          .join("");

      // If you care about fallbacks, you can use `fallback` here to decide whether to resolve or not.
      void fallback;
      return {
        expr: `vars.${toCamelCase(name)}`,
        imports: [
          {
            from: { kind: "specifier", value: "./css-variables.stylex" },
            names: [{ imported: "vars" }],
          },
        ],
      };
    }

    return null;
  },

  resolveCall(ctx) {
    // Called for template interpolations like: ${transitionSpeed("slowTransition")}
    // `calleeImportedName` is the imported symbol name (works even with aliasing).
    // `calleeSource` tells you where it came from:
    // - { kind: "absolutePath", value: "/abs/path" } for relative imports
    // - { kind: "specifier", value: "some-package/foo" } for package imports

    const arg0 = ctx.args[0];
    const key =
      arg0?.kind === "literal" && typeof arg0.value === "string"
        ? arg0.value
        : null;
    if (ctx.calleeImportedName !== "transitionSpeed" || !key) {
      return null;
    }

    return {
      usage: "create",
      expr: `transitionSpeedVars.${key}`,
      imports: [
        {
          from: { kind: "specifier", value: "./lib/helpers.stylex" },
          names: [{ imported: "transitionSpeed", local: "transitionSpeedVars" }],
        },
      ],
    };
  },

  shouldSupportExternalStyling() {
    return false;
  },
});

const result = await runTransform({
  files: "src/**/*.tsx",
  adapter,
  dryRun: false,
  parser: "tsx", // "babel" | "babylon" | "flow" | "ts" | "tsx"
  formatterCommand: "pnpm prettier --write", // optional: format transformed files
});

console.log(result);

Adapter

Adapters are the main extension point. They let you control:

  • how theme paths and CSS variables are turned into StyleX-compatible JS values (resolveValue)
  • what extra imports to inject into transformed files (returned from resolveValue)
  • how helper calls are resolved (via resolveCall({ ... }) returning usage: "props" | "create"; null/undefined now bails)
  • which exported components should support external className/style extension (shouldSupportExternalStyling)
  • how className/style merging is handled for components accepting external styling (styleMerger)

Style Merger

When a component accepts external className and/or style props (e.g., via shouldSupportExternalStyling, or when wrapping a base component that already accepts these props), the generated code needs to merge StyleX styles with externally passed values.

Note: Allowing external className/style props is generally discouraged in StyleX as it bypasses the type-safe styling system. However, it can be useful during migration to maintain compatibility with existing code that passes these props.

By default, this generates verbose inline merging code. You can provide a styleMerger to use a helper function instead for cleaner output:

const adapter = defineAdapter({
  resolveValue(ctx) {
    // ... value resolution logic
    return null;
  },

  shouldSupportExternalStyling(ctx) {
    return ctx.filePath.includes("/shared/components/");
  },

  // Use a custom merger function for cleaner output
  styleMerger: {
    functionName: "mergedSx",
    importSource: { kind: "specifier", value: "./lib/mergedSx" },
  },
});

The merger function should have this signature:

function mergedSx(
  styles: StyleXStyles,
  className?: string,
  style?: React.CSSProperties
): ReturnType<typeof stylex.props>;

See test-cases/lib/mergedSx.ts for a reference implementation.

External Styles Support

Transformed components are "closed" by default — they don't accept external className or style props. Use shouldSupportExternalStyling to control which exported components should support external styling:

const adapter = defineAdapter({
  resolveValue(ctx) {
    // ... value resolution logic
    return null;
  },

  shouldSupportExternalStyling(ctx) {
    // ctx: { filePath, componentName, exportName, isDefaultExport }

    // Example: Enable for all exports in shared components folder
    if (ctx.filePath.includes("/shared/components/")) {
      return true;
    }

    // Example: Enable for specific component names
    if (ctx.componentName === "Button" || ctx.componentName === "Card") {
      return true;
    }

    return false;
  },
});

When shouldSupportExternalStyling returns true, the generated component will:

  • Accept className and style props
  • Merge them with the StyleX-generated styles
  • Forward remaining props via ...rest

Dynamic interpolations

When the codemod encounters an interpolation inside a styled template literal, it runs an internal dynamic resolution pipeline which covers common cases like:

  • theme access (props.theme...) via resolveValue({ kind: "theme", path })
  • prop access (props.foo) and conditionals (props.foo ? "a" : "b", props.foo && "color: red;")
  • simple helper calls (transitionSpeed("slowTransition")) via resolveCall({ ... }) returning usage: "create"
  • style helper calls (returning StyleX styles) via resolveCall({ ... }) returning usage: "props"; these are emitted as extra stylex.props(...) args
  • if resolveCall returns null or undefined, the transform now bails the file and logs a warning
  • helper calls applied to prop values (e.g. shadow(props.shadow)) by emitting a StyleX style function that calls the helper at runtime
  • conditional CSS blocks via ternary (e.g. props.$dim ? "opacity: 0.5;" : "")

If the pipeline can’t resolve an interpolation:

  • for some dynamic value cases, the transform preserves the value as a wrapper inline style so output keeps visual parity (at the cost of using style={...} for that prop)
  • otherwise, the declaration containing that interpolation is dropped and a warning is produced (manual follow-up required)

Limitations

  • Flow type generation is non-existing, works best with TypeScript or plain JS right now. Contributions more than welcome!
  • ThemeProvider: if a file imports and uses ThemeProvider from styled-components, the transform skips the entire file (theming strategy is project-specific).
  • createGlobalStyle: detected usage is reported as an unsupported-feature warning (StyleX does not support global styles in the same way).

License

MIT