# Comparisons

Compare how Zyzz, Tailwind CSS, StyleX, and vanilla-extract define, compose, and apply styles.



Each example group below specifies one behavior and uses the same elements, values, inputs, and states across all four libraries. Generated class names and implementation details can differ.

The examples use React and TypeScript with each library's build integration configured. Code block titles name the files in each example. Tailwind examples target v4 and omit its optional [Preflight reset](https://tailwindcss.com/docs/preflight#disabling-preflight) so browser defaults can match.

## Authoring Experience

All four examples render a button labeled "Continue" with `#06c` text and `1rem` padding. DX means developer experience, and AX means agent experience.

### Zyzz

```tsx title="Button.tsx"
import { style } from 'zyzz'

export function Button() {
  return <button {...styles.button()}>Continue</button>
}

namespace styles {
  export const button = style({ color: '#06c', padding: '1rem' })
}
```

Definitions stay beside the component in a `namespace styles`. Calling a definition returns styling props, and the compiler emits CSS ahead of time without inserting rules at runtime. Types constrain property names, supported values, tokens, and variant selections. Root imports contain no design tokens. See [Thinking in Zyzz](/docs/introduction/thinking-in-zyzz).

### Tailwind CSS

```tsx title="Button.tsx"
export function Button() {
  return <button className="text-[#06c] p-[1rem]">Continue</button>
}
```

Utilities keep declarations in markup. Class strings do not carry TypeScript property contracts, and the official [editor extension](https://tailwindcss.com/docs/editor-setup) supplies completions and diagnostics. Developers and agents must keep [complete class names](https://tailwindcss.com/docs/detecting-classes-in-source-files) in source, including inside conditional mappings.

### StyleX

```tsx title="Button.tsx"
import * as stylex from '@stylexjs/stylex'

export function Button() {
  return <button {...stylex.props(styles.button)}>Continue</button>
}

const styles = stylex.create({
  button: { color: '#06c', padding: '1rem' },
})
```

Typed objects use `create`, and `props` applies them. StyleX compiles the objects into atomic CSS. Definitions must follow its [supported expression rules](https://stylexjs.com/docs/learn/styling-ui/defining-styles/#constraints), and its [static types](https://stylexjs.com/docs/learn/static-types/) constrain style contracts. StyleX also publishes [LLM resources](https://stylexjs.com/docs/llm-resources).

### vanilla-extract

```ts title="button.css.ts"
import { style } from '@vanilla-extract/css'

export const button = style({ color: '#06c', padding: '1rem' })
```

```tsx title="Button.tsx"
import { button } from './button.css.js'

export function Button() {
  return <button className={button}>Continue</button>
}
```

Typed styles live in `.css.ts` modules that the build integration evaluates, and components import the exported class names. [Sprinkles](https://vanilla-extract.style/documentation/packages/sprinkles/) optionally adds typed utilities and token mappings. This example uses the core [style API](https://vanilla-extract.style/documentation/api/style/).

## Themes and Schemes

Each panel has `1rem` padding and displays "Content". Text is `#111` in light mode and `#eee` in dark mode. All four examples use `light-dark()` with `color-scheme: light dark`, so selection follows the browser preference without application state.

### Zyzz

```ts title="zyzz.config.ts"
import { defineConfig } from 'zyzz'

export const { style, vars } = defineConfig({
  vars: {
    color: { content: { dark: '#eee', light: '#111' } },
    spacing: { panel: '1rem' },
  },
})
```

```tsx title="Panel.tsx"
import { style } from './zyzz.config.js'

export function Panel() {
  return <section {...styles.panel()}>Content</section>
}

namespace styles {
  export const panel = style({
    color: 'content',
    colorScheme: 'light dark',
    padding: 'panel',
  })
}
```

`defineConfig` binds tokens to named authoring helpers. Color pairs compile to `light-dark()`, and property domains constrain where each token applies. The optional default theme is available from `zyzz/default`. See [Themes & Tokens](/docs/guides/themes).

### Tailwind CSS

```css title="app.css"
@layer theme, base, components, utilities;
@import 'tailwindcss/theme.css' layer(theme);
@import 'tailwindcss/utilities.css' layer(utilities);

@theme {
  --color-content: light-dark(#111, #eee); 
  --spacing-panel: 1rem;
}
```

```tsx title="Panel.tsx"
export function Panel() {
  return (
    <section className="text-content p-panel [color-scheme:light_dark]">
      Content
    </section>
  )
}
```

`@theme` defines variables and the utilities that use them. The standard import also includes default tokens. These imports keep the default theme and utilities while omitting Preflight. See [theme variables](https://tailwindcss.com/docs/theme).

### StyleX

```ts title="tokens.stylex.ts"
import * as stylex from '@stylexjs/stylex'

export const tokens = stylex.defineVars({
  content: 'light-dark(#111, #eee)',
  panel: '1rem',
})
```

```tsx title="Panel.tsx"
import * as stylex from '@stylexjs/stylex'
import { tokens } from './tokens.stylex.js'

export function Panel() {
  return <section {...stylex.props(styles.panel)}>Content</section>
}

const styles = stylex.create({
  panel: {
    color: tokens.content,
    colorScheme: 'light dark',
    padding: tokens.panel,
  },
})
```

`defineVars` exports a variable contract from a `.stylex.ts` module, and the consuming style references those variables. [Theming](https://stylexjs.com/docs/learn/theming/defining-variables/) requires the documented module-resolution option.

### vanilla-extract

```ts title="panel.css.ts"
import { createTheme, style } from '@vanilla-extract/css'

export const [theme, tokens] = createTheme({
  color: { content: 'light-dark(#111, #eee)' },
  spacing: { panel: '1rem' },
})

export const panel = style({
  color: tokens.color.content,
  colorScheme: 'light dark',
  padding: tokens.spacing.panel,
})
```

```tsx title="Panel.tsx"
import { panel, theme } from './panel.css.js'

export function Panel() {
  return <section className={`${theme} ${panel}`}>Content</section>
}
```

`createTheme` returns a theme class and a variable contract. Applying the theme class supplies the variables that the panel consumes. See [createTheme](https://vanilla-extract.style/documentation/api/create-theme/).

## Selectors and Queries

Each panel is a grid inside an inline-size container. Padding changes from `0.5rem` to `1rem` at a `48rem` viewport, and gap changes from `0` to `1rem` at a `24rem` container width. Hover sets opacity to `0.8` on devices that support hover.

### Zyzz

```tsx title="Panel.tsx"
import { style } from 'zyzz'

export function Panel() {
  return (
    <div {...styles.container()}>
      <section {...styles.panel()}>
        <span>First</span>
        <span>Second</span>
      </section>
    </div>
  )
}

namespace styles {
  export const container = style({ containerType: 'inline-size' })

  export const panel = style({
    display: 'grid',
    gap: '0',
    opacity: 1,
    padding: '0.5rem',
    '@container (min-width: 24rem)': { gap: '1rem' },
    '@media (min-width: 48rem)': { padding: '1rem' },
    '@media (hover: hover)': { ':hover': { opacity: 0.8 } },
  })
}
```

Conditions nest beside declarations, with literal thresholds in this example. Configured breakpoint and container aliases can name the same conditions. See [Styling](/docs/guides/styling).

### Tailwind CSS

```tsx title="Panel.tsx"
export function Panel() {
  return (
    <div className="@container">
      <section
        className="grid gap-0 opacity-100 p-[0.5rem] min-[48rem]:p-[1rem] @min-[24rem]:gap-[1rem] hover:opacity-80"
      >
        <span>First</span>
        <span>Second</span>
      </section>
    </div>
  )
}
```

Prefixes express conditions. Explicit thresholds avoid depending on a project's breakpoint overrides, and the `hover:` variant includes the hover-capability query. See [responsive design](https://tailwindcss.com/docs/responsive-design) and [states](https://tailwindcss.com/docs/hover-focus-and-other-states).

### StyleX

```tsx title="Panel.tsx"
import * as stylex from '@stylexjs/stylex'

export function Panel() {
  return (
    <div {...stylex.props(styles.container)}>
      <section {...stylex.props(styles.panel)}>
        <span>First</span>
        <span>Second</span>
      </section>
    </div>
  )
}

const styles = stylex.create({
  container: { containerType: 'inline-size' },
  panel: {
    display: 'grid',
    gap: { default: '0', '@container (min-width: 24rem)': '1rem' },
    opacity: {
      default: 1,
      '@media (hover: hover)': { default: 1, ':hover': 0.8 },
    },
    padding: { default: '0.5rem', '@media (min-width: 48rem)': '1rem' },
  },
})
```

Conditions belong inside property values, and `default` supplies the value outside each condition. [Style definitions](https://stylexjs.com/docs/learn/styling-ui/defining-styles/#media-queries-and-other--rules) support nested conditions, including media and container queries.

### vanilla-extract

```ts title="panel.css.ts"
import { style } from '@vanilla-extract/css'

export const container = style({ containerType: 'inline-size' })

export const panel = style({
  display: 'grid',
  gap: '0',
  opacity: 1,
  padding: '0.5rem',
  '@container': { '(min-width: 24rem)': { gap: '1rem' } },
  '@media': {
    '(min-width: 48rem)': { padding: '1rem' },
    '(hover: hover)': { ':hover': { opacity: 0.8 } },
  },
})
```

```tsx title="Panel.tsx"
import { container, panel } from './panel.css.js'

export function Panel() {
  return (
    <div className={container}>
      <section className={panel}>
        <span>First</span>
        <span>Second</span>
      </section>
    </div>
  )
}
```

Style objects group media and container conditions under their respective keys. See [conditional styles](https://vanilla-extract.style/documentation/styling/).

Fallback values are a separate concern. Zyzz and vanilla-extract accept declaration arrays in authored order, while StyleX's `firstThatWorks` takes the preferred value first. Tailwind expresses feature-dependent alternatives with `supports-*` variants. These forms should not be compared by argument order alone.

## Variants and Overrides

Every `Button` accepts the same optional `size` and `tone` choices, with `regular` and `neutral` as defaults. All buttons use `inline-flex`, `400` font weight, and `0.5rem` or `1rem` padding. Text is `#111` or `#06c`, and the `regular` and `accent` combination uses `600` font weight.

### Zyzz

```tsx title="Button.tsx"
import { type Props, variants } from 'zyzz'

export function Button(props: Button.Props) {
  return <button {...styles.button(props)}>Continue</button>
}

export declare namespace Button {
  type Props = Props.Variants<typeof styles.button>
}

namespace styles {
  export const button = variants({
    base: { display: 'inline-flex', fontWeight: 400 },
    compoundVariants: [
      { when: { size: 'regular', tone: 'accent' }, style: { fontWeight: 600 } },
    ],
    defaultVariants: { size: 'regular', tone: 'neutral' },
    variants: {
      size: {
        compact: { padding: '0.5rem' },
        regular: { padding: '1rem' },
      },
      tone: { accent: { color: '#06c' }, neutral: { color: '#111' } },
    },
  })
}
```

`variants` owns choices, defaults, and compound rules. `Props.Variants` infers the component's props from the definition, including styling overrides, so call sites complete the declared choices:

```tsx title="Toolbar.tsx"
import { Button } from './Button.js'

export function Toolbar() {
  return <Button size="compact" tone="" />
  // completions: accent, neutral
}
```

See the [variants contract](/docs/api/core/variants).

### Tailwind CSS

```tsx title="Button.tsx"
export function Button(props: Button.Props) {
  const size = props.size ?? 'regular'
  const tone = props.tone ?? 'neutral'
  const weight =
    size === 'regular' && tone === 'accent' ? 'font-[600]' : 'font-[400]'

  return (
    <button className={`inline-flex ${sizes[size]} ${tones[tone]} ${weight}`}>
      Continue
    </button>
  )
}

export declare namespace Button {
  type Props = {
    size?: 'compact' | 'regular' | undefined
    tone?: 'accent' | 'neutral' | undefined
  }
}

const sizes = { compact: 'p-[0.5rem]', regular: 'p-[1rem]' } as const
const tones = { accent: 'text-[#06c]', neutral: 'text-[#111]' } as const
```

Maps preserve complete utility strings, while defaults and the compound condition are ordinary component logic. Each property selects one utility, so the result does not depend on class-string order. See [conditional styling](https://tailwindcss.com/docs/styling-with-utility-classes).

### StyleX

```tsx title="Button.tsx"
import * as stylex from '@stylexjs/stylex'

export function Button(props: Button.Props) {
  const size = props.size ?? 'regular'
  const tone = props.tone ?? 'neutral'

  return (
    <button
      {...stylex.props(
        styles.base,
        sizes[size],
        tones[tone],
        size === 'regular' && tone === 'accent' && styles.emphasized,
      )}
    >
      Continue
    </button>
  )
}

export declare namespace Button {
  type Props = {
    size?: 'compact' | 'regular' | undefined
    tone?: 'accent' | 'neutral' | undefined
  }
}

const styles = stylex.create({
  base: { display: 'inline-flex', fontWeight: 400 },
  emphasized: { fontWeight: 600 },
})

const sizes = stylex.create({
  compact: { padding: '0.5rem' },
  regular: { padding: '1rem' },
})

const tones = stylex.create({
  accent: { color: '#06c' },
  neutral: { color: '#111' },
})
```

Style maps and conditional `props` arguments implement the [variant pattern](https://stylexjs.com/docs/learn/recipes/variants/). Later arguments resolve conflicts for the same property.

### vanilla-extract

```ts title="button.css.ts"
import { recipe } from '@vanilla-extract/recipes'

export const button = recipe({
  base: { display: 'inline-flex', fontWeight: 400 },
  compoundVariants: [
    {
      variants: { size: 'regular', tone: 'accent' },
      style: { fontWeight: 600 },
    },
  ],
  defaultVariants: { size: 'regular', tone: 'neutral' },
  variants: {
    size: {
      compact: { padding: '0.5rem' },
      regular: { padding: '1rem' },
    },
    tone: { accent: { color: '#06c' }, neutral: { color: '#111' } },
  },
})
```

```tsx title="Button.tsx"
import { button } from './button.css.js'

export function Button(props: Button.Props) {
  return (
    <button className={button({ size: props.size, tone: props.tone })}>
      Continue
    </button>
  )
}

export declare namespace Button {
  type Props = {
    size?: 'compact' | 'regular' | undefined
    tone?: 'accent' | 'neutral' | undefined
  }
}
```

The optional [Recipes package](https://vanilla-extract.style/documentation/packages/recipes/) owns typed choices, defaults, and compound rules. `RecipeVariants` can infer the recipe contract, and its callable returns a class string.

## Composition

Each example combines a button's `1rem` padding with an independent focus ring. Keyboard focus produces a `2px solid #06c` outline with a `2px` offset. Size and tone choices belong in variants, as above.

### Zyzz

```tsx title="Button.tsx"
import { cx, style } from 'zyzz'

export function Button() {
  return <button {...cx(styles.button(), styles.focusRing())}>Continue</button>
}

namespace styles {
  export const button = style({ padding: '1rem' })

  export const focusRing = style({
    ':focus-visible': { outline: '2px solid #06c', outlineOffset: '2px' },
  })
}
```

`cx` merges applied styles in argument order, including variable bindings and recipe attributes. Later declarations win in matching contexts at equal specificity and importance. Multiple JSX spreads would replace overlapping props instead. See [Styling](/docs/guides/styling).

### Tailwind CSS

```tsx title="Button.tsx"
export function Button() {
  return (
    <button
      className="p-[1rem] focus-visible:outline-2 focus-visible:outline-solid focus-visible:outline-[#06c] focus-visible:outline-offset-2"
    >
      Continue
    </button>
  )
}
```

Utilities express both declarations directly. For [conflicting utilities](https://tailwindcss.com/docs/styling-with-utility-classes#conflicting-utility-classes), stylesheet order determines precedence, so concatenating class names does not create an ordered override API.

### StyleX

```tsx title="Button.tsx"
import * as stylex from '@stylexjs/stylex'

export function Button() {
  return (
    <button {...stylex.props(styles.button, styles.focusRing)}>Continue</button>
  )
}

const styles = stylex.create({
  button: { padding: '1rem' },
  focusRing: {
    outlineColor: { default: null, ':focus-visible': '#06c' },
    outlineOffset: { default: null, ':focus-visible': '2px' },
    outlineStyle: { default: null, ':focus-visible': 'solid' },
    outlineWidth: { default: null, ':focus-visible': '2px' },
  },
})
```

[`stylex.props`](https://stylexjs.com/docs/learn/styling-ui/using-styles/) merges applied styles, and later arguments take priority for conflicts on the same property. By default, overlapping longhands take priority over shorthands regardless of argument order.

### vanilla-extract

```ts title="button.css.ts"
import { style } from '@vanilla-extract/css'

const base = style({ padding: '1rem' })

const focusRing = style({
  ':focus-visible': { outline: '2px solid #06c', outlineOffset: '2px' },
})

export const button = style([base, focusRing])
```

```tsx title="Button.tsx"
import { button } from './button.css.js'

export function Button() {
  return <button className={button}>Continue</button>
}
```

The style array performs [build-time composition](https://vanilla-extract.style/documentation/style-composition/). Concatenating unrelated classes at runtime keeps normal CSS cascade behavior.

## Element Relationships

Each `Card` receives `open`, writes `data-state="open"` or `"closed"` on a section, and renders a "Details" span inside it. The span has opacity `1` when open and `0.5` when closed. Each example observes the ancestor attribute through CSS.

### Zyzz

```tsx title="Card.tsx"
import { style } from 'zyzz'

export function Card(props: Card.Props) {
  return (
    <section {...styles.card()} data-state={props.open ? 'open' : 'closed'}>
      <span {...styles.label()}>Details</span>
    </section>
  )
}

export declare namespace Card {
  type Props = { open: boolean }
}

namespace styles {
  export const card = style()

  export const label = style({
    opacity: 0.5,
    selectors: { [`${card}[data-state="open"] &`]: { opacity: 1 } },
  })
}
```

An empty `style()` supplies the card's selector identity, and the label references that definition inside `selectors`. See the [selectors contract](/docs/api/core/style#stylesselectors).

### Tailwind CSS

```tsx title="Card.tsx"
export function Card(props: Card.Props) {
  return (
    <section className="group/card" data-state={props.open ? 'open' : 'closed'}>
      <span
        className="opacity-50 group-data-[state=open]/card:opacity-100"
      >
        Details
      </span>
    </section>
  )
}

export declare namespace Card {
  type Props = { open: boolean }
}
```

A named group marks the ancestor, and a data variant selects its state. See [parent state](https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state).

### StyleX

```tsx title="Card.tsx"
import * as stylex from '@stylexjs/stylex'

export function Card(props: Card.Props) {
  return (
    <section
      {...stylex.props(stylex.defaultMarker())}
      data-state={props.open ? 'open' : 'closed'}
    >
      <span {...stylex.props(styles.label)}>Details</span>
    </section>
  )
}

export declare namespace Card {
  type Props = { open: boolean }
}

const styles = stylex.create({
  label: {
    opacity: {
      default: 0.5,
      [stylex.when.ancestor('[data-state="open"]')]: 1,
    },
  },
})
```

A marker identifies the ancestor, and `when.ancestor` observes its attribute. These APIs support [element relationships](https://stylexjs.com/docs/api/javascript/when/) within StyleX's selector model.

### vanilla-extract

```ts title="card.css.ts"
import { style } from '@vanilla-extract/css'

export const card = style({})

export const label = style({
  opacity: 0.5,
  selectors: { [`${card}[data-state="open"] &`]: { opacity: 1 } },
})
```

```tsx title="Card.tsx"
import { card, label } from './card.css.js'

export function Card(props: Card.Props) {
  return (
    <section className={card} data-state={props.open ? 'open' : 'closed'}>
      <span className={label}>Details</span>
    </section>
  )
}

export declare namespace Card {
  type Props = { open: boolean }
}
```

An exported class can be interpolated into another style's selector. See [complex selectors](https://vanilla-extract.style/documentation/styling/#complex-selectors).

## Runtime Values

Each `Bar` accepts a `width` percentage and renders a div with that width, `0.5rem` height, and a `#06c` background. All four approaches reuse static CSS while changing a per-instance value.

### Zyzz

```tsx title="Bar.tsx"
import { style } from 'zyzz'

export function Bar(props: Bar.Props) {
  return <div {...styles.bar({ width: props.width })} />
}

export declare namespace Bar {
  type Props = { width: `${number}%` }
}

namespace styles {
  export const bar = style((values: { width: `${number}%` }) => ({
    backgroundColor: '#06c',
    height: '0.5rem',
    width: values.width,
  }))
}
```

The typed callback binds its input to a precompiled CSS variable. Runtime applications set the value without generating CSS rules. See [Styling](/docs/guides/styling).

### Tailwind CSS

```tsx title="Bar.tsx"
export function Bar(props: Bar.Props) {
  return <div className="h-[0.5rem] bg-[#06c]" style={{ width: props.width }} />
}

export declare namespace Bar {
  type Props = { width: `${number}%` }
}
```

An [inline style](https://tailwindcss.com/docs/styling-with-utility-classes#when-to-use-inline-styles) supplies the width without constructing a dynamic utility name that the source scanner cannot detect.

### StyleX

```tsx title="Bar.tsx"
import * as stylex from '@stylexjs/stylex'

export function Bar(props: Bar.Props) {
  return <div {...stylex.props(styles.bar(props.width))} />
}

export declare namespace Bar {
  type Props = { width: `${number}%` }
}

const styles = stylex.create({
  bar: (width: `${number}%`) => ({
    backgroundColor: '#06c',
    height: '0.5rem',
    width,
  }),
})
```

A [dynamic style function](https://stylexjs.com/docs/learn/styling-ui/defining-styles/#dynamic-styles) binds the width to a compiled CSS variable.

### vanilla-extract

```ts title="bar.css.ts"
import { createVar, style } from '@vanilla-extract/css'

export const widthVar = createVar()

export const bar = style({
  backgroundColor: '#06c',
  height: '0.5rem',
  width: widthVar,
})
```

```tsx title="Bar.tsx"
import { assignInlineVars } from '@vanilla-extract/dynamic'
import { bar, widthVar } from './bar.css.js'

export function Bar(props: Bar.Props) {
  return (
    <div
      className={bar}
      style={assignInlineVars({ [widthVar]: props.width })}
    />
  )
}

export declare namespace Bar {
  type Props = { width: `${number}%` }
}
```

`createVar` declares the variable, and the optional [Dynamic package](https://vanilla-extract.style/documentation/packages/dynamic/) sets its value through inline styles.

## Additional Zyzz APIs

The paired examples cover common tasks. These documented APIs cover further requirements, without implying that another library lacks an equivalent.

| API | Purpose |
| --- | --- |
| [Appearance](/docs/guides/themes) | Select named variable sets and color schemes, with initialization scripts for saved preferences. |
| [CSS output](/docs/guides/css-output) | Choose atomic or grouped output without changing authoring or composition semantics. |
| [Property mappings](/docs/guides/themes#customize-property-mappings) | Configure optional aliases and property-specific token scales. |
| Stylesheets | Declare [globals](/docs/guides/global-styles), [cascade layers](/docs/guides/layers), [fonts](/docs/guides/typography), [keyframes](/docs/guides/keyframes), and supported [CSS at-rules](/docs/guides/at-rules). |
| [Typography sets](/docs/guides/themes) | Apply a named set of font properties, with explicit field overrides. |
| [Typed variables](/docs/api/core/variable) | Declare independent variables, constrain assignments with `.set`, and optionally emit `@property` registration. |
| [Variant conditions and payloads](/docs/guides/variants) | Select choices through media or supports conditions, or bind typed payloads to a dynamic choice. |

## Compilation and Platforms

Build commands perform different jobs, so they are not interchangeable examples. The useful comparison is which artifacts each library produces and what a consuming application must load.

| Library | Build and delivery | Platforms |
| --- | --- | --- |
| Zyzz | The CLI's `zyzz build` and `zyzz dev` emit transformed modules, CSS, maps, and metadata. Bundler integrations deliver styles automatically, and [published libraries](https://github.com/wevm/zyzz/blob/main/docs/guides/compilation.md#publish-libraries) include matching code, CSS, declarations, and contracts. | Web CSS and [React Native](/docs/guides/native) through separate adapters. Unsupported native semantics produce errors. |
| Tailwind CSS | The [CLI](https://tailwindcss.com/docs/installation/tailwind-cli) emits CSS from detected utilities. Libraries distribute CSS or arrange [consumer source scanning](https://tailwindcss.com/docs/detecting-classes-in-source-files). | Web CSS. Native use requires a separate integration. |
| StyleX | The [CLI](https://stylexjs.com/docs/learn/installation/cli/) transforms modules, emits CSS, and can add CSS imports. Precompiled libraries ship generated artifacts. | Web CSS. |
| vanilla-extract | [Build integrations](https://vanilla-extract.style/documentation/integrations/vite/) evaluate `.css.ts` modules and extract CSS. Libraries publish compiled JavaScript and CSS. | Web CSS. |

Zyzz's `Css` namespace compiles style data in memory. Stylesheet helpers such as `global`, `fontFace`, and `keyframes` are direct exports from `zyzz/web`. Source optimization and native output use separate compiler and host entrypoints.

## Performance and Size

Equivalent examples do not establish a performance winner. Compare generated CSS, JavaScript, and markup together, and account for selection helpers, variable bindings, metadata, and delivery conventions. [Benchmarks](/docs/introduction/benchmarks) records the measured workloads and their limits.

| Library | Output considerations |
| --- | --- |
| Zyzz | Atomic or grouped CSS. Static applications can fold into props, while dynamic selection and composition can retain helpers. |
| Tailwind CSS | Shared utilities and class strings. Styling needs no JavaScript runtime, but component variant logic still contributes code. |
| StyleX | Shared atomic declarations. Cross-module and dynamic applications can retain mappings and runtime work. See [Thinking in StyleX](https://stylexjs.com/docs/learn/thinking-in-stylex/). |
| vanilla-extract | Scoped CSS. Optional [Sprinkles](https://vanilla-extract.style/documentation/packages/sprinkles/) produces atomic utilities, while Recipes and dynamic bindings can retain helpers. |

Measure cold builds, warm builds, and incremental edits separately. For browser comparisons, test the same states and computed styles before recording timing. Report raw, gzip, and Brotli sizes with versions and workload details, count class strings once, and report package download size separately from delivered application assets.
