Skip to content
Blog

Styling Your Astryx Application: StyleX, Tailwind, Plain CSS, and Library Interop

Learn the three styling approaches for Astryx — StyleX, Tailwind CSS, and plain CSS — plus integration with MUI, Emotion, Panda CSS, UnoCSS, and more.

Published on July 31, 2026

AI Assistant

Astryx supports three styling approaches and integrates with any CSS-in-JS or utility framework. The core principle: component props first, then xstyle, then className. Never raw hex or px values.

Three Styling Approaches

ApproachMechanismBest For
StyleXxstyle prop + stylex.create()TypeScript-first, type-safe
TailwindUtility classes + Tailwind bridgeTailwind-first projects
Plain CSSclassName + CSS custom propertiesSmall/traditional projects

1. StyleX (First-Class Citizen)

Every Astryx component accepts an xstyle prop. Values must come from stylex.create() — no inline objects or strings.

Basic Usage

import * as stylex from '@stylexjs/stylex';
import { colorVars, spacingVars } from '@astryxdesign/core/theme/tokens.stylex';

const styles = stylex.create({
  card: {
    backgroundColor: colorVars['--color-background-surface'],
    padding: spacingVars['--spacing-4'],
    borderRadius: radiusVars['--radius-container'],
  },
  cardHover: {
    boxShadow: {
      default: 'none',
      ':hover': {
        '@media (hover: hover)': '0 4px 12px rgba(0,0,0,0.1)',
      },
    },
  },
});

<Card xstyle={[styles.card, styles.cardHover]}>
  Content
</Card>

Critical Rules

  • All xstyle values must come from stylex.create()
  • ALL :hover styles MUST use @media (hover: hover) guard — prevents sticky hover on touch screens
  • Token references: use var(--*) strings or typed imports from @astryxdesign/core/theme/tokens.stylex

StyleX Build Setup

BundlerPlugin
Webpack@stylexjs/webpack-plugin
Vite / Rollup@stylexjs/rollup-plugin
Next.js (App Router)@stylexswc/nextjs-plugin (NOT Babel)

Critical Next.js warning: Do NOT add @stylexjs/babel-plugin to a Next.js App Router app — it disables SWC and breaks next/font. Use the SWC-based transform instead.

2. Tailwind CSS Integration

Import the Tailwind bridge CSS to get utility classes backed by Astryx tokens.

Layer Order (Critical for Coexistence)

@layer reset, theme, base, astryx-base, astryx-theme, components, utilities;

@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/preflight.css" layer(base);
@import "@astryxdesign/core/reset.css";
@import "@astryxdesign/core/astryx.css";
@import "@astryxdesign/theme-neutral/theme.css";
@import "@astryxdesign/core/tailwind-theme.css";
@import "tailwindcss/utilities.css" layer(utilities);

Usage

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

The bridge is pure CSS, zero JS. Theme changes apply automatically because the utility classes reference var(--color-*) tokens.

3. Plain CSS

Use className and style normally. className is appended after the component’s own classes.

.my-button {
  background: var(--color-accent);
  color: var(--color-on-accent);
  padding: var(--spacing-4) var(--spacing-6);
  border-radius: var(--radius-element);
}
<Button className="my-button" label="Click" />

Preferred Selector Surface (Data Attributes)

Components reflect all props as data-* attributes on the root element:

.my-app .astryx-button[data-variant="primary"] { }
.my-app .astryx-heading[data-level="2"] { }

Interop with Other Styling Libraries

Panda CSS / Chakra

semanticTokens: {
  colors: {
    text: { primary: { value: 'var(--color-text-primary)' } },
    background: { surface: { value: 'var(--color-background-surface)' } },
  },
},

MUI

const theme = createTheme({
  cssVariables: true,
  colorSchemes: {
    light: {
      palette: {
        primary: { main: 'var(--color-accent)' },
        background: { default: 'var(--color-background-body)' },
        text: { primary: 'var(--color-text-primary)' },
      },
    },
  },
});

Emotion / styled-components

const appTheme = {
  colors: {
    textPrimary: 'var(--color-text-primary)',
    surface: 'var(--color-background-surface)',
    accent: 'var(--color-accent)',
  },
  spacing: { 4: 'var(--spacing-4)' },
};

UnoCSS

export default defineConfig({
  theme: {
    colors: {
      surface: 'var(--color-background-surface)',
      primary: 'var(--color-text-primary)',
      accent: 'var(--color-accent)',
    },
  },
  shortcuts: {
    'xds-card': 'bg-surface text-primary border border-border rounded-lg p-4',
  },
});

Non-CSS Processing (Charts, SVG, Canvas)

'use client';
import { useTheme } from '@astryxdesign/core/theme';

function RevenueChart({ data }) {
  const { mode, tokens } = useTheme();
  const chartOptions = useMemo(() => ({
    textColor: tokens['--color-text-primary'],
    gridColor: tokens['--color-border'],
  }), [mode, tokens]);
  return <ThirdPartyChart data={data} options={chartOptions} />;
}

What NOT to Do

Anti-PatternCorrect Approach
style={{}} on raw <div> wrappersUse xstyle on the component
Hardcoded colors (#fff, rgb(...))var(--color-*)
Hardcoded spacing (16px)var(--spacing-*)
Using !importantCheck specificity instead; xstyle is merged last

Best Practices Summary

  • Prefer CSS variables for runtime theme switching and nested themes
  • Use data visualization tokens (--color-data-categorical-*) for chart series
  • Do NOT copy raw hex/px values into a second theme object when var(...) works
  • Do NOT run a second unsynchronized dark-mode provider