How to Override Astryx Component Styles with Tailwind, CSS Modules, or Plain CSS

Astryx components expose className, style, and xstyle props that append your custom styles after internal StyleX-generated classes, ensuring your Tailwind utilities, CSS modules, or inline styles always take precedence in the cascade.

Astryx is Meta’s React component library built on StyleX for type-safe styling. While components ship with opinionated, token-driven defaults, the facebook/astryx source code implements a deliberate extension mechanism via the mergeProps utility that guarantees consumer overrides win without stripping base styles.

How Astryx Handles Style Overrides

Every public component in Astryx accepts three styling props that feed into a centralized merging utility:

  • className – Appends a string of CSS classes after the component’s StyleX-generated class. This is the primary hook for Tailwind utilities or CSS module classes.
  • style – Merges an inline style object after the component’s internal StyleX style, ideal for dynamic values.
  • xstyle – Accepts StyleX style objects (e.g., stylex.props(customStyle)) for advanced customizations using pseudo-classes or theme tokens.

The merging logic resides in packages/core/src/utils/mergeProps.ts. This utility concatenates the consumer’s className after internal classes (cls = "…${xdsClass} ${stylexResult.className} ${className}") and spreads the consumer’s style object after internal styles. Because your values are appended last, they naturally override defaults in the CSS cascade.

Method 1: Tailwind CSS Overrides

Pass Tailwind utility classes to the className prop to override colors, spacing, or effects.

import {Button} from '@astryxdesign/core';

export const SaveButton = () => (
  <Button
    label="Save"
    variant="primary"
    className="bg-green-600 hover:ring-2 hover:ring-green-300"
  />
);

Why it works: The Button component’s internal class is generated first, then your Tailwind string is appended. Tailwind’s later position in the cascade allows it to override the default background color when specificity is equal.

Method 2: CSS Modules

Import scoped styles from a CSS module and pass the generated class string via className.

/* MyButton.module.css */
.myButton {
  background-color: #ff6b6b;
  border-radius: 12px;
}
/* MyButton.tsx */
import {Button} from '@astryxdesign/core';
import styles from './MyButton.module.css';

export const MyButton = () => (
  <Button
    label="Delete"
    variant="destructive"
    className={styles.myButton}
  />
);

Why it works: CSS Modules produce unique class names (e.g., MyButton_myButton__abc123). mergeProps appends this class after Astryx’s own classes, so your module’s declarations take precedence without bleeding into other components.

Method 3: Inline Styles

Use the style prop for dynamic values that cannot be expressed with utility classes or when you need runtime calculations.

import {Button} from '@astryxdesign/core';

export const WideButton = ({width}) => (
  <Button
    label="Full-width"
    variant="secondary"
    style={{ width, paddingBlock: '0.75rem' }}
  />
);

Why it works: Inline styles are merged after StyleX’s generated style object. Because the style prop is spread last in packages/core/src/utils/mergeProps.ts, your values overwrite any conflicting internal declarations.

Method 4: StyleX Style Objects (xstyle)

For projects also using StyleX, pass style objects directly to leverage theme tokens and pseudo-classes.

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

const custom = stylex.create({
  emphasis: {
    borderWidth: '2px',
    borderColor: 'var(--color-accent)',
  },
});

export const EmphasizedButton = () => (
  <Button
    label="Important"
    xstyle={stylex.props(custom.emphasis)}
  />
);

Why it works: The xstyle prop integrates with StyleX’s compiler, allowing you to reference design tokens defined in packages/core/src/theme/tokens.stylex.ts while maintaining type safety.

Understanding the Merge Order

When you combine multiple override methods, Astryx applies them in a predictable sequence. According to the implementation in packages/core/src/utils/mergeProps.ts and the component usage in packages/core/src/Button/Button.tsx (lines 78-94), the final computed props follow this hierarchy:

  1. Internal StyleX classes and styles – The component’s base appearance.
  2. xstyle classes – StyleX-specific overrides.
  3. className string – CSS modules, Tailwind, or plain classes.
  4. style object – Inline styles with the highest precedence.
import {Button} from '@astryxdesign/core';
import moduleStyles from './FancyButton.module.css';

export const FancyButton = ({isLoading}) => (
  <Button
    label="Submit"
    variant="primary"
    className={`
      ${moduleStyles.fancy}
      ${isLoading ? 'opacity-50' : ''}
    `}
    style={{ minWidth: isLoading ? '120px' : '80px' }}
  />
);

In this example, the cascade resolves as: StyleX → CSS module → Tailwind → Inline. Your custom styles always win because they are concatenated and spread last.

Summary

  • Use className for Tailwind utilities and CSS modules; values are appended after StyleX classes in packages/core/src/utils/mergeProps.ts.
  • Use style for dynamic inline values that require runtime calculations.
  • Use xstyle when you need StyleX-specific features like theme tokens from packages/core/src/theme/tokens.stylex.ts.
  • Override precedence is guaranteed by the merge order: internal styles first, consumer styles last.

Frequently Asked Questions

Does passing a className replace the default Astryx styles?

No. The component retains its internal StyleX-generated classes. Your className is appended after the internal class string, allowing your CSS to override specific properties through the cascade while preserving the component’s structural styles.

Why do my Tailwind classes fail to override Astryx component styles?

This usually indicates a specificity conflict or purging issue. Ensure your Tailwind content configuration includes paths to Astryx component files so utilities are not tree-shaken. Additionally, verify you are using the standard className prop rather than attempting to style via wrapper elements, as Astryx components often merge classes directly onto the root element defined in packages/core/src/Button/Button.tsx.

Can I combine CSS Modules and Tailwind on the same component?

Yes. Both technologies output standard CSS class strings. Pass them together to the className prop: className={\${styles.myModule} bg-blue-500 hover:bg-blue-600`}`. The order in the string determines cascade precedence, with later classes overriding earlier ones when specificity is equal.

How do I access Astryx theme tokens in my custom CSS?

Reference the CSS custom properties defined in packages/core/src/theme/tokens.stylex.ts. For example, use var(--color-primary) or var(--space-4) in your CSS modules or global styles. These tokens are available at runtime because StyleX generates them as custom properties in the document root.

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 →