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
| Approach | Mechanism | Best For |
|---|---|---|
| StyleX | xstyle prop + stylex.create() | TypeScript-first, type-safe |
| Tailwind | Utility classes + Tailwind bridge | Tailwind-first projects |
| Plain CSS | className + CSS custom properties | Small/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
xstylevalues must come fromstylex.create() - ALL
:hoverstyles 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
| Bundler | Plugin |
|---|---|
| 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-Pattern | Correct Approach |
|---|---|
style={{}} on raw <div> wrappers | Use xstyle on the component |
Hardcoded colors (#fff, rgb(...)) | var(--color-*) |
Hardcoded spacing (16px) | var(--spacing-*) |
Using !important | Check 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