Library / SDK
styled-components/xstyled avatar
styled-components/xstyled

xstyled: utility props on React components, shipped for two styling engines

A utility-first CSS-in-JS framework built for React. 💅👩‍🎤⚡️

2,303 stars102 forksMDXMIT

At a glance

What is it?
A utility-first CSS-in-JS framework for React that moves spacing, colour and responsive values into JSX props, and publishes one adapter for styled-components and one for Emotion. Its monorepo carries benchmark scripts and a type checker while publishing no comparison numbers at all, so an adopter has to measure it first hand.
Who is it for?
Adopt xstyled when a team wants spacing and colour decided inside JSX and is willing to keep a runtime styling engine beside React. Skip it if the project wants markup free of design decisions, or if nobody on the team can absorb a major bump on the adapter layer.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 111 days ago.
What is it written in?
Mainly MDX, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Style props replace class names on an x component

An x component looks like an ordinary React element until you read its props. `bg="white"` and `display="flex"` sit on `<x.div>` next to the attributes you would put on any element, and a single import brings the tag factory into scope:

js
import { x } from '@xstyled/styled-components'

Spacing reads off a numeric scale, so `spaceX={4}` asks for one step along the horizontal axis rather than a hand written margin, while `flexShrink={0}` keeps the logo column from collapsing. Size works the same way, with `h={12}` and `w={12}` on the image element inside the same card.

What that buys is legibility inside a diff. A reviewer reading the JSX sees padding steps and colours without opening a stylesheet, and there is no utility class string to look up in documentation. What it costs is the separation between structure and presentation that every other CSS approach keeps, and the resemblance to Tailwind stops at the vocabulary: the repository lists `tailwindcss` among its topics, yet nothing in the running code is a class name.

Nested objects carry the breakpoint map on every prop

Responsive values reuse the same prop with a different shape. Padding moves from a plain number to an object keyed by breakpoint, `p={{ _: 3, md: 6 }}`, meaning the base step is 3 and the md breakpoint raises it to 6. Type size does the same with scale tokens rather than numbers, `fontSize={{ _: 'md', lg: 'xl' }}`, and one snippet from the example carries both forms:

js
<x.h4
  fontSize={{ _: 'md', lg: 'xl' }}
  fontWeight="medium"
  color="black"
>
  xstyled
</x.h4>
<x.p color="gray-500">A CSS-in-JS framework built for React.</x.p>

The leading key in that object is the fallback that applies whenever no breakpoint matches, so a component never needs a second prop for its small screen version. The repository text gives no pixel value behind the md or the lg key, and the example never shows a misspelled one, so the failure mode for a typo stays undocumented. Whether an unmatched key throws, is dropped, or silently resolves to the base value is exactly the sort of detail that lives on the documentation site rather than in the source tree.

Installing means pairing an adapter with its engine

One command covers the whole setup, and it names two packages:

bash
npm install @xstyled/styled-components styled-components

The adapter without the engine is inert, and the engine without the adapter gives you plain styled-components with no x tag factory, so the two belong in the same install line. The first half of the card component reads as ordinary JSX with style props folded in, and stops before the heading:

js
import { x } from '@xstyled/styled-components'

function Example() {
  return (
    <x.div p={{ _: 3, md: 6 }} bg="white" display="flex" spaceX={4}>
      <x.div flexShrink={0}>
        <x.img h={12} w={12} src="/img/logo.svg" alt="xstyled Logo" />
      </x.div>
      <x.div>

A second adapter exists for the other engine. Alongside `@xstyled/styled-components` the repository publishes `@xstyled/emotion`, and the root workspace pins `@emotion/react`, `@emotion/styled` and `@emotion/serialize` as dev dependencies, which tells you the same `x` surface is exercised against both engines in this repository. Install instructions cover only the styled-components pairing, so choosing Emotion means reading the documentation site for the equivalent command.

A monorepo whose root package publishes nothing

The thing you install does not come from the repository root. That `package.json` is marked `"private": true` and holds contributor scripts only, while every published artifact lives under `packages/`, with `pnpm-workspace.yaml` and `pnpm-lock.yaml` recording that pnpm does the linking. The root dev dependencies reach back into that directory through `workspace:*` references to `@xstyled/core`, `@xstyled/system`, `@xstyled/util`, `@xstyled/prop-types`, `@xstyled/emotion` and `@xstyled/styled-components`, so a fresh checkout wires six internal packages together instead of resolving them from a registry.

Two more root entries matter when you judge the project. A `website/` directory next to `netlify.toml` means the documentation at xstyled.dev is built from this same tree, which explains why MDX is recorded as the primary language of a library whose runtime is plain JavaScript. And `.changeset/` beside the publish script means version numbers move through declared changesets rather than by hand editing manifests:

bash
pnpm run build && changeset publish

Utility classes from a build step, props from a runtime

The topics list also names `styled-system`, and both entries point at routes other than this one. Build step tooling turns utility names into class attributes that a generator emits into a stylesheet ahead of time, so markup holds utility strings and no component library exposes a style prop. xstyled keeps the numeric key idea but routes it through a component: `p={{ _: 3, md: 6 }}` and `spaceX={4}` are props on a tag factory, and the CSS is computed during render by whichever engine you installed beside the adapter.

That difference is architectural, not cosmetic. Build step output is a fixed sheet with a token scale fixed at generation time, while a runtime adapter recomputes declarations as components render and leaves injection timing to the engine. It also changes what a diff looks like: with class strings you review vocabulary, with props you review numbers whose meaning depends on a scale the README does not print. No comparison between the two approaches appears anywhere in the repository, and the runtime benchmark script is the only place such a number could come from.

A benchmark harness with no numbers attached

Three scripts sit in the root `package.json`, named `bench:runtime`, `bench:types` and `check:types`, and a `benchmarks/` directory at the root holds whatever those scripts load:

bash
node scripts/bench-runtime.mjs
node scripts/check-types.mjs
node scripts/bench-types.mjs

None of that reaches a reader. The README prints an install command, two links and one card component, then stops, so anyone weighing this framework against a plain styled-components setup has to run the runtime script against their own application and judge the shape of the answer. That is a limit on what gets published, not on the library: the measurement tooling is there, its output is not.

The toolchain around those scripts is pinned with carets and dates from the React 18 generation, including `jest` 29.1.2, `rollup` 2.79.1, `esbuild` 0.15.10, `eslint` 8.24.0 and `@babel/preset-env` 7.19.3. Ranges rather than exact versions mean a clean install can drift inside each major, and `check:types` runs against this repository's own components with `csstype` 3.1.3 rather than against yours.

Three header shields match the files, one points at master

The README header carries four shields. Two point at the registry entry for `@xstyled/styled-components` on npm, and the code style shield names prettier with the colour `ff69b4`, which matches a repository that really does ship `.prettierrc`, `.prettierignore` and a `prettier --write .` script. The fourth shield links to a `blob/master/LICENSE` path, and the default branch of this repository is `main`.

A stale branch segment in a badge link costs nothing at runtime. It does tell you something about how the header was kept, because three of the four shields still point at things that exist while one names a branch layout the current default branch contradicts. If a framework's README is the only page you are going to read, that line is worth a glance before you assume the documentation tracks the tree.

An adapter a full major ahead of the system core

The three most recently published packages all carry the 2026-05-21 date, and their numbers do not line up. `@xstyled/system` sits at 3.8.3, `@xstyled/emotion` sits at 3.8.3, and `@xstyled/styled-components`, the package almost every installation pulls in, sits at 4.1.1. The adapter is a whole major ahead of the core it is built on, so the version in your lock file describes one layer of the framework and says nothing about the rest.

That gap is the practical upgrade cost. A major on the adapter can move prop handling while the core stays on its own three line track, and the README states no peer range for `styled-components`, so nothing in the tree tells you which engine versions 4.1.1 was cut against. The front page example is also the only example: no theme object, no provider import, no note about server rendering, even though a type checker and `csstype` are part of the toolchain. Every one of those gaps closes or opens on the documentation site, not in the source.

This repository is not archived, and the last push landed on 2026-07-04, which makes the 2026-05-21 batch above the most recent shipped work the tree records.

Editorial conclusion

Adopt xstyled when a team wants spacing and colour decided inside JSX and is willing to keep a runtime styling engine beside React. Skip it if the project wants markup free of design decisions, or if nobody on the team can absorb a major bump on the adapter layer. Check the peer range recorded for styled-components before moving past @xstyled/styled-components 4.1.1, because the repository states none, and remember that the card component in the README is the only example it prints, with no theme and no provider.

Frequently asked questions

Which packages do I need to install to use xstyled?

`npm install @xstyled/styled-components styled-components` installs the adapter together with the engine it delegates CSS generation to. Install only the adapter and the x tag factory has nothing behind it.

How do I write responsive styles with xstyled?

Pass an object keyed by breakpoint name on any style prop, with the leading key as the base value, as in `p={{ _: 3, md: 6 }}` or `fontSize={{ _: 'md', lg: 'xl' }}`. The repository text does not give the pixel value behind the named breakpoints.

Can I use xstyled with Emotion instead of styled-components?

The repository publishes `@xstyled/emotion` alongside `@xstyled/styled-components`, and the root workspace pins `@emotion/react`, `@emotion/styled` and `@emotion/serialize` as dev dependencies. The install instructions in the README cover only the styled-components pairing.

Does xstyled support themes and TypeScript?

Type support is part of the toolchain, with a `check:types` script, `csstype` and `@types/react` in the root workspace. Theming is not shown anywhere in the README, which prints one component with no theme object and no provider import.

Is xstyled still being updated?

The repository is not archived and the last push landed on 2026-07-04, while the three most recent packages were published on 2026-05-21. The repository states no support policy for older major versions.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. styled-components/xstyled on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/styled-components-xstyled.svg)](https://hysenlabs.com/projects/styled-components-xstyled)

Was this useful?