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

@basic-ui/styled-system-to-tailwind-codemod

v0.0.5

Published

Codemod to migrate @emotion/react + styled-system Box props to Tailwind classes

Readme

@basic-ui/styled-system-to-tailwind-codemod

A codemod that migrates @emotion/react + styled-system style props (used by Box, Text, and the rest of @basic-ui/material) into plain Tailwind utility classes plus the project's semantic color/typography utilities.

It rewrites JSX like:

<Box display="flex" alignItems="center" px={3} bg="surface-container" />

into:

<Box className="flex items-center px-4 bg-surface-container" />

Anything that can't be expressed statically (responsive arrays, nested sx selectors, dynamic colors, alpha() calls, …) is left untouched and tagged with:

// TODO(tailwind-migration): too complex to migrate automatically

For a step-by-step guide to resolving those markers by hand (dynamic values, unknown colors, complex sx objects, responsive arrays, tricky borders), see the bundled SKILL.md.

Usage

# from the repo root
yarn workspace @basic-ui/styled-system-to-tailwind-codemod build

# dry run (prints what would change, writes nothing)
node packages/styled-system-to-tailwind-codemod/bin/cli.js --dry-run ./large-codebase

# migrate for real (also runs oxfmt on the changed files)
node packages/styled-system-to-tailwind-codemod/bin/cli.js ./large-codebase

The transform parses with recast so unrelated formatting is preserved. Because recast reflows the small number of elements that have direct text children, the CLI runs oxfmt on changed files afterwards (run yarn format yourself if the automatic step is skipped).

Options

| Flag | Description | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | --dry-run, -d | Report changes without writing files. | | --no-format | Skip the post-run oxfmt pass. | | --no-detect-wrappers | Disable the project-wide local Box-wrapper detection pre-pass. | | --responsive-arrays | Convert responsive array values (display={['none','flex']}hidden sm:flex). Off by default — only correct when styled-system breakpoints are aligned with Tailwind's. | | --components A,B | Also migrate these component names regardless of import source. Rarely needed thanks to auto-detection; use it for wrappers the heuristic can't see. | | --modules m1,m2 | Override the module specifiers whose imports are migration targets (default @basic-ui/material). | | --color-unsafe A,B | Leave the color prop untouched on these components (when color selects a variant rather than text color). |

Targeting

Every component imported from @basic-ui/material is a target, because they all forward styled-system props through Box.

In addition, the CLI runs a project-wide pre-pass that finds local Box wrapper components and migrates their props too. A component counts as a wrapper when its props type references a *Props type from @basic-ui/material (e.g. BoxProps, ButtonProps) — directly, through a local type alias / interface … extends …, or an intersection like BoxProps & { … }. This covers patterns such as:

// Icons/NorthEastIcon.tsx — detected because props is BoxProps
export const NorthEastIcon = (props: BoxProps<SVGSVGElement>) => (
  <Box as="svg" {...props} />
);

// components/Link/ButtonLink.tsx — detected via ButtonLinkProps -> ButtonProps
export type ButtonLinkProps = Omit<ButtonProps, 'href'> & NextLinkProps;
export const ButtonLink: FC<ButtonLinkProps> = forwardRef(/* … */);

Detected wrapper names are matched globally by name. Disable with --no-detect-wrappers, or add names the heuristic misses with --components.

What it migrates

  • Spacingm/p family using the styled-system space scale (p={3}p-4), auto, negative margins, and explicit pixels.
  • Sizingwidth/height/min*/max* as raw pixels (width={24}w-6), percentages (width="100%"w-full), and keywords.
  • Flexboxdisplay, flexDirection, alignItems, justifyContent, flex, flexGrow/flexShrink, flexWrap, gap.
  • Positionposition, top/right/bottom/left, zIndex.
  • Colorbg/backgroundColor/color/borderColor to the project's bg-* / text-* / border-* semantic color utilities, hex/rgb to arbitrary values.
  • Border radius / shadow — theme radii and shadows scales.
  • TypographytextAlign, fontWeight, fontSize, textTransform, whiteSpace, overflow, opacity (incl. string / fractional values), and the variant: 'text.body-small' shorthand → typography-body-small.
  • Bordersborder / borderTop… shorthand ('1px solid'border border-solid), borderWidth, borderStyle, borderColor.
  • SVG / miscfill (incl. none / currentColor), cursor, pointerEvents, textOverflow, textDecoration.
  • Helper callsrem(24) / em(…) are resolved to pixels, and alpha('on.surface', 0.7) becomes the opacity modifier text-on-surface/70. Imports left unused by these rewrites (rem, em, alpha) are pruned.
  • sx / __css objects — converted only when every property maps cleanly; otherwise the object is preserved and flagged.

Responsive arrays

Responsive array values (display={['none', 'flex']}) are left as TODOs by default: styled-system's default 52em breakpoint doesn't line up with Tailwind's md (48em), so an automatic rewrite would be wrong.

If your app overrides styled-system's breakpoints to match Tailwind's (['640px', '768px', '1024px', '1280px', '1536px']), pass --responsive-arrays to convert them by array index:

| index | breakpoint | prefix | | ----- | ---------- | ------ | | 0 | (base) | — | | 1 | 640px | sm: | | 2 | 768px | md: | | 3 | 1024px | lg: | | 4 | 1280px | xl: | | 5 | 1536px | 2xl: |

<Box display={['none', 'none', 'block']} px={[2, 3]} />
// -> className="hidden md:block px-2 sm:px-4"

Array holes / undefined entries inherit the previous breakpoint, and a breakpoint that resolves to the same value as the one before it is dropped. An array with any non-static element still bails to a TODO.

className is merged intelligently: string literals are concatenated, dynamic values are wrapped with clsx(...) (auto-imported), and a generated className keeps its position relative to any {...spread} so precedence is preserved.

Development

# run the test suite (jest, from the repo root)
yarn jest packages/styled-system-to-tailwind-codemod

The mapping logic lives in src/mappings.ts (pure, easily unit tested) and the AST rewriting in src/transform.ts.