Anti-Patterns to Avoid When Using the Astryx Design System: 10 Critical Mistakes

Avoid hard-coding CSS values, using CSS nesting operators, and toggling classes manually; instead leverage StyleX design tokens, stylex.when helpers, and stylex.props() conditionals to maintain compile-time optimizations and theme consistency.

The Astryx design system from Meta provides a CSS-native styling layer through StyleX alongside a library of reusable components. Understanding the anti-patterns to avoid when using the Astryx design system prevents performance degradation and broken design-token consistency. This guide analyzes the specific mistakes documented in the packages/core/README.md PATTERN section and provides corrected implementations using first-party APIs.

Design Token Anti-Patterns

Hard-coding values like border-radius: 8px or hex colors breaks theme switching and consistency. According to the packages/core/README.md guidelines, always use stylex.defineVars and stylex.createTheme to establish semantic tokens rather than hard-coding radii or colors.

❌ Hard-coded radius values

<div style={{ borderRadius: '8px' }}>Inconsistent</div>

✅ Use design tokens

import { stylex } from '@astryxdesign/stylex';

const styles = stylex.create({
  container: { 
    borderRadius: stylex.vars.radiusMedium 
  },
});

export const Component = () => (
  <div className={styles.container}>Theme-aware</div>
);

CSS Architecture Mistakes

Astryx deliberately avoids CSS nesting. Using & operators produces unexpected specificity and complicates auditing. The internal/stylex-capabilities/CAPABILITIES.md documentation confirms that StyleX handles state through JavaScript rather than nested selectors.

❌ CSS nesting with &

/* This bypasses StyleX processing */
.button {
  &::after { content: ''; }
}

✅ Use stylex.when helpers

import { stylex } from '@astryxdesign/stylex';

const styles = stylex.create({
  button: {
    ...stylex.when.ancestor(':hover', {
      after: { content: '""' },
    }),
  },
});

Similarly, avoid manual fallback CSS. Writing separate fallback rules duplicates logic that StyleX already handles via stylex.firstThatWorks().

❌ Manual fallback rules

const styles = stylex.create({
  element: {
    display: 'flex',
    display: '-webkit-flex', // Redundant manual fallback
  },
});

✅ Leverage built-in fallbacks

const styles = stylex.create({
  element: {
    display: stylex.firstThatWorks('flex', '-webkit-flex'),
  },
});

Dynamic Styling Anti-Patterns

Inline style objects bypass StyleX's static analysis, losing compile-time optimizations and potentially duplicating CSS. Never use className={cond ? 'a' : 'b'} toggling, which undermines the declarative nature of StyleX and risks class name collisions.

❌ Inline styles and manual class toggling

// ❌ Bypasses static analysis
<div style={{ color: dynamicColor }}>

// ❌ Imperative class toggling
<div className={isActive ? 'active' : 'inactive'}>

✅ Conditional props pattern

Use stylex.props() with boolean expressions to conditionally apply generated classes:

const styles = stylex.create({
  base: { color: 'gray' },
  active: { color: 'blue' },
});

export const Component = ({ isActive }: { isActive: boolean }) => (
  <div className={stylex.props(styles.base, isActive && styles.active)}>
    Composable styles
  </div>
);

For ancestor and descendant states, skipping stylex.when leads to fragile selectors or JavaScript state syncing. Use the dedicated helpers instead:

const styles = stylex.create({
  control: {
    ...stylex.when.ancestor(':hover', { opacity: 0.8 }),
    ...stylex.when.descendant(':focus', { borderColor: 'blue' }),
    ...stylex.when.siblingBefore(':checked', { background: 'green' }),
  },
});

Component Scope and Integration

Using generic <a> elements instead of the Link component prevents proper routing integration with Next.js or React Router. Always render links via useLinkComponent() or within a LinkProvider to preserve the abstraction layer so the underlying router can be swapped.

For form controls, avoid the default marker which leaks hover and focus states from outer containers (causing popover glitches). Create component-scoped markers using stylex.defineMarker():

const marker = stylex.defineMarker();
const styles = stylex.create({
  control: { marker: true },
});

Performance and Maintenance

Redundant CSS bloats stylesheets and hurts performance. Rely on StyleX's automatic deduplication and keep selectors lean using the stylex.props() composability model. The internal/eslint-plugin-astryx/README.md file contains ESLint rules that automatically catch many of these anti-patterns during development.

Before modifying components, query the documentation using the CLI command shown in packages/cli/README.md:

$ASTRYX component Button --dense

Summary

  • Never hard-code values—use stylex.defineVars and theme tokens for radii, colors, and spacing.
  • Avoid CSS nesting with &—prefer stylex.when.ancestor() and stylex.when.descendant() for state-based styling.
  • Skip inline styles—encode dynamic values within stylex.create() and apply conditionally via stylex.props().
  • Don't toggle classes manually—use the declarative stylex.props(condition && styles.variant) pattern.
  • Use stylex.firstThatWorks() instead of manual CSS fallbacks.
  • Render links via useLinkComponent() rather than generic <a> tags to preserve routing integration.
  • Scope form markers with stylex.defineMarker() to prevent state leakage from containers.

Frequently Asked Questions

What happens if I use inline style objects with Astryx?

Inline style props bypass StyleX's static analysis, which prevents compile-time optimizations and can lead to duplicated CSS in the final bundle. Always encode dynamic values inside stylex.create() functions or use the stylex.props(condition && styles.x) pattern to maintain design system guarantees.

How do I handle hover states without CSS nesting?

Use stylex.when.ancestor(':hover') or stylex.when.descendant(':focus') to express parent-child relationships. This maintains the flat CSS structure that StyleX compiles while avoiding the specificity issues associated with nested & selectors, as documented in packages/core/README.md.

Why should I avoid manual class name toggling?

Toggling classes via className={cond ? 'a' : 'b'} undermines StyleX's declarative model and risks class name collisions. The stylex.props() API safely composes generated class names and only applies them when conditions are true, ensuring consistency with the design system.

How do I verify I'm not using anti-patterns in my codebase?

The internal/eslint-plugin-astryx package provides ESLint rules that catch common anti-patterns automatically. Additionally, use the CLI command $ASTRYX component <Name> --dense to query component documentation before implementation, as documented in packages/cli/README.md.

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 →