xstyle vs className vs style Props in Astryx Components: The Complete Guide

Astryx components accept three distinct styling mechanisms: xstyle for compile-time StyleX objects with pseudo-class support, className for arbitrary CSS class strings appended after internal styles, and style for inline React objects that override all other values.

In the facebook/astryx repository, the component architecture exposes a three-layer styling system that balances design-system consistency with flexible overrides. Understanding how xstyle, className, and style interact is essential for optimizing bundle size via static analysis while maintaining compatibility with utility-first CSS workflows.

Understanding the xstyle Prop

The xstyle prop is not a CSS class name string. Instead, it accepts typed StyleX objects created via stylex.create(), enabling compile-time optimizations that standard CSS cannot achieve.

Compile-Time Processing and Static Analysis

When you pass an xstyle value, the component forwards it to stylex.props() for merging at compile-time. This processing enables dead-code elimination, automatic deduplication of styles, and deterministic class name generation. According to the styling documentation in packages/cli/docs/styling.doc.mjs (lines 38-44), the xstyle prop supports pseudo-classes like :hover and :focus-visible expressed directly inside the stylex.create object.

Only values produced by stylex.create() are accepted—attempting to pass a raw string or inline style object triggers a TypeScript error. This type safety ensures design-token consistency across the application.

Implementation in Core Components

In packages/core/src/Table/TableHeader.tsx, the component forwards received xstyle values to the StyleX compiler:

export function TableHeader({ref, children, xstyle}: TableHeaderProps) {
  return (
    <thead
      ref={ref}
      {...mergeProps(themeProps('table-header'), stylex.props(xstyle))}
    >
      {children}
    </thead>
  );
}

This pattern appears throughout the codebase, including in packages/core/src/ToggleButton/ToggleButtonGroup.tsx where the xstyle?: StyleXStyles type is defined, and packages/lab/src/Stepper/Stepper.tsx which demonstrates passing xstyle through nested component hierarchies.

How className Works in Astryx

The className prop accepts arbitrary strings of CSS class names, including Tailwind utilities or custom framework classes. Unlike xstyle, these values are not processed by the StyleX compiler.

Classes passed via className are appended after the component’s internal class list, as documented in packages/cli/docs/styling.doc.mjs (lines 30-33). This ordering allows utility-first classes to layer on top of the component’s built-in token classes without interfering with the design system’s base styles.

The style Prop and Specificity

The style prop accepts plain React style objects (e.g., {margin: 8, backgroundColor: 'red'}). These inline styles are merged after both xstyle and className, giving them the highest specificity in the cascade.

Because inline styles win on any overlapping property, use this prop for one-off pixel values or dynamic calculations where defining a CSS class would be excessive.

Processing Order and Specificity Hierarchy

Astryx applies styles in the following sequence, with each layer potentially overriding the previous:

  1. xstyle – StyleX rules compiled to deterministic class names
  2. className – Arbitrary CSS classes appended to the class list
  3. style – Inline styles applied last, winning specificity conflicts

This hierarchy means a style={{backgroundColor: 'pink'}} declaration will override a background color set via xstyle, regardless of the order props are declared in JSX.

Practical Code Examples

Component-Specific Overrides with xstyle

import * as stylex from '@stylexjs/stylex';
import {Card, Button} from '@astryxdesign/core';

const overrides = stylex.create({
  card: {
    maxWidth: 400,
    marginBlock: 16,
    ':hover': { '@media (hover: hover)': { boxShadow: '0 4px 12px rgba(0,0,0,0.1)' } },
  },
  saveButton: {
    alignSelf: 'flex-end',
  },
});

export function Example() {
  return (
    <>
      <Card xstyle={overrides.card}>Content</Card>
      <Button label="Save" xstyle={overrides.saveButton} />
    </>
  );
}

Source: packages/cli/docs/styling.doc.mjs (lines 48-57)

Utility-First Styling with className

<div className="flex gap-3 p-4 bg-surface text-primary rounded-container">
  <Button label="Save" variant="primary" />
  <Button label="Cancel" variant="secondary" />
</div>

Inline Tweaks with style

<Card
  xstyle={overrides.card}
  style={{border: '2px solid var(--color-accent)'}}
>
  Content
</Card>

Combining All Three Props

<Button
  xstyle={overrides.saveButton}
  className="hover:scale-105 transition-transform"
  style={{backgroundColor: 'var(--color-success)'}}
/>

In this example, the button receives StyleX token-based styling (xstyle), Tailwind utility classes for hover effects (className), and an explicit inline background color (style). The final CSS cascade follows the order: StyleX → Tailwind → Inline.

When to Use Each Prop

Situation Recommended Prop
Override design tokens (spacing, color, border radius) while preserving pseudo-class support xstyle
Add layout utilities or integrate external CSS frameworks (Tailwind) className
Apply single, one-off pixel values or CSS properties not covered by the design system style

Summary

  • xstyle accepts StyleX objects from stylex.create(), enabling compile-time deduplication, dead-code elimination, and pseudo-class support via stylex.props() processing.
  • className appends arbitrary CSS class strings after internal component classes, supporting utility-first frameworks like Tailwind.
  • style applies inline React style objects with highest specificity, overriding any conflicting properties from xstyle or className.
  • The processing order is xstyleclassNamestyle, with each subsequent layer winning specificity conflicts.

Frequently Asked Questions

Can I use Tailwind CSS with Astryx components?

Yes. Pass Tailwind utility classes to the className prop. These classes are appended after the component’s internal StyleX-generated classes, allowing utilities to override layout and spacing while preserving the component’s design-token base styles.

Why does my inline style override my xstyle definition?

Because the style prop is merged after xstyle in the component’s internal logic. Inline styles have higher specificity in CSS than class-based styles, so a style={{backgroundColor: 'red'}} declaration will always win over a background color defined in your xstyle object, regardless of source order.

Can I pass a CSS class string to the xstyle prop?

No. The xstyle prop is strictly typed to accept only StyleX objects created via stylex.create(). Passing a string like xstyle="my-class" results in a TypeScript compilation error. Use the className prop for arbitrary CSS class strings.

Does xstyle support responsive breakpoints and media queries?

Yes. StyleX supports media queries and container queries within the stylex.create() definition. You can define responsive behavior by nesting @media rules inside your style objects, which the compiler transforms into proper CSS selectors while maintaining type safety.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →