# StyleX Authoring Guide
This document provides guidance on authoring styles with StyleX.
## Writing styles
Styles must be created using `stylex.create()`. Define styles as an object with namespaces containing CSS properties.
```tsx
import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({
container: {
display: 'flex',
alignItems: 'center',
padding: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: 'navy',
},
});
```
**IMPORTANT**
- Use longhand properties and single-value shorthands over multi-value shorthands.
- Use `null` to unset properties.
- Length properties are in pixels by default.
---
## Applying styles
Convert StyleX style objects to props using `stylex.props()`:
```tsx
function Component() {
return (
Hello
);
}
```
### Merging styles
Pass multiple styles to merge them. The last style wins for conflicting properties:
```tsx
// styles.highlighted overrides conflicting properties from styles.base
// Or passed in as arrays
```
### Conditional styles
Use JavaScript expressions for conditional styling:
```tsx
```
### Passing styles as props
Accept styles from parent components:
```tsx
import type { StyleXStyles } from '@stylexjs/stylex';
type Props = {
children: React.ReactNode;
style?: StyleXStyles;
};
const styles = stylex.create({
card: {
padding: 16,
borderRadius: 8,
},
});
function Card({ children, style }: Props) {
// Local styles first, then prop styles (so props can override)
return {children}
;
}
```
### Unsetting styles
Use `null` to remove a style property:
```tsx
const styles = stylex.create({
base: { margin: 16, padding: 16 },
reset: { margin: null, padding: null }, // Removes margin and padding
});
```
---
## Pseudo-classes
Nest pseudo-classes within property values using an object with `default` and pseudo-class keys:
```tsx
const styles = stylex.create({
button: {
backgroundColor: {
default: 'lightblue',
':hover': 'blue',
':active': 'darkblue',
':focus-visible': 'royalblue',
':disabled': 'gray',
},
cursor: {
default: 'pointer',
':disabled': 'not-allowed',
},
},
});
```
Recommended pseudo-classes include:
- `:hover`, `:active`, `:focus`, `:focus-visible`, `:focus-within`
**IMPORTANT: Prefer JS changes over `:first-child` and `:nth-child` pseudo-elements. This reduces CSS bundle size.**
---
## Pseudo-elements
Define pseudo-elements as top-level keys within a style namespace:
```tsx
const styles = stylex.create({
input: {
color: 'black',
'::placeholder': {
color: 'gray',
fontStyle: 'italic',
},
'::selection': {
backgroundColor: 'yellow',
},
},
});
```
**IMPORTANT: Prefer actual HTML elements over `::before` and `::after` pseudo-elements. This reduces CSS bundle size and improves accessibility.**
---
## Media queries and @-rules
Nest media queries within property values:
```tsx
const styles = stylex.create({
container: {
flexDirection: {
default: 'column',
'@media (min-width: 768px)': 'row',
},
padding: {
default: 8,
'@media (min-width: 768px)': 16,
'@media (min-width: 1024px)': 24,
},
},
});
```
**For app-wide breakpoints, use `stylex.defineConsts()` to define shareable media query constants:**
Other supported @-rules include `@supports` and `@container` queries.
**IMPORTANT: The `default` key is required when using nested conditions. Use `null` when no style should apply for the default case.**
---
## Dynamic styles
Use arrow functions for runtime values:
```tsx
const styles = stylex.create({
bar: (width: number) => ({
width,
}),
positioned: (x: number, y: number) => ({
transform: `translate(${x}px, ${y}px)`,
}),
});
```
---
## Defining constants
Use `stylex.defineConsts()` for shareable media queries and static values like animations, colors, and font sizes that aren't themed.
**IMPORTANT: Use `defineConsts` over `defineVars` when values don't need to be themed or overridden at runtime.**
```tsx
// constants.stylex.ts
import * as stylex from '@stylexjs/stylex';
export const breakpoints = stylex.defineConsts({
small: '@media (max-width: 600px)',
medium: '@media (min-width: 601px) and (max-width: 1024px)',
large: '@media (min-width: 1025px)',
});
export const zIndices = stylex.defineConsts({
modal: '1000',
tooltip: '1100',
toast: '1200',
});
```
---
## Defining variables
Use `stylex.defineVars()` when values need theming or runtime overrides. Must be in `.stylex.ts` files:
```tsx
// tokens.stylex.ts
import * as stylex from '@stylexjs/stylex';
export const colors = stylex.defineVars({
primary: 'blue',
secondary: 'gray',
text: 'black',
background: 'white',
});
export const spacing = stylex.defineVars({
small: '8px',
medium: '16px',
large: '24px',
});
```
---
## Using variables and constants
Import and use variables and constants in your styles:
```tsx
import * as stylex from '@stylexjs/stylex';
import { colors, spacing } from './tokens.stylex';
const styles = stylex.create({
container: {
backgroundColor: colors.background,
color: colors.text,
padding: spacing.medium,
},
});
```
**IMPORTANT: For `defineConsts` and `defineVars`:**
- Must be in `.stylex.ts` or `.stylex.js` files
- Must be named exports (not default exports)
- No other exports allowed in the file
---
## Creating themes
Override variable values for DOM sub-trees using `stylex.createTheme()`:
```tsx
import * as stylex from '@stylexjs/stylex';
import { colors } from './tokens.stylex';
export const darkTheme = stylex.createTheme(colors, {
primary: 'lightblue',
text: 'white',
background: '#1a1a1a',
});
// Apply theme to a container
function App({ isDark, children }) {
return (
{children} {/* All descendants use theme values */}
);
}
```
Unlike `defineVars`, themes can be created anywhere and passed across files/components.
---
## Relational selectors
Style elements based on the state of ancestors, descendants, or siblings using `stylex.when.*` selectors: `stylex.when.ancestor()`, `stylex.when.descendant()`, `stylex.when.anySibling()`, `stylex.when.siblingBefore()`, `stylex.when.siblingAfter()`.
Mark the observed element with `stylex.defaultMarker()` or create custom markers using `stylex.defineMarker()`.
```tsx
const styles = stylex.create({
card: {
transform: {
default: 'translateX(0)',
[stylex.when.ancestor(':hover')]: 'translateX(10px)',
},
},
});
Hover the parent to move me
```
---
## Fallback styles
Use `stylex.firstThatWorks()` for browser compatibility fallbacks:
```tsx
const styles = stylex.create({
header: {
position: stylex.firstThatWorks('sticky', '-webkit-sticky', 'fixed'),
display: stylex.firstThatWorks('grid', 'flex'),
},
});
```
---
## Keyframe animations
Define animations with `stylex.keyframes()`:
```tsx
const fadeIn = stylex.keyframes({
from: { opacity: 0 },
to: { opacity: 1 },
});
const slideIn = stylex.keyframes({
'0%': { transform: 'translateX(-100%)' },
'100%': { transform: 'translateX(0)' },
});
const styles = stylex.create({
animated: {
animationName: fadeIn,
animationDuration: '0.3s',
animationTimingFunction: 'ease-out',
},
});
```
---
## View transitions
Use `stylex.viewTransitionClass()` to customize View Transition API animations:
```tsx
import * as stylex from '@stylexjs/stylex';
import { unstable_ViewTransition as ViewTransition } from 'react';
const fadeInUp = stylex.keyframes({
from: { opacity: 0, transform: 'translateY(-30px)' },
to: { opacity: 1, transform: 'translateY(0)' },
});
const transitionClass = stylex.viewTransitionClass({
group: { /* ::view-transition-group styles */ },
imagePair: { /* ::view-transition-image-pair styles */ },
old: { animationDuration: '2s' },
new: { animationName: fadeInUp },
});
{/* ... */}
```
---
## Anchor positioning
Use `stylex.positionTry()` to define CSS anchor positioning fallbacks:
```tsx
const fallback = stylex.positionTry({
positionAnchor: '--anchor',
top: '0',
left: '0',
width: '100px',
height: '100px',
});
const styles = stylex.create({
tooltip: {
positionTryFallbacks: fallback,
},
});
```
---
## TypeScript integration
Use `StyleXStyles` and `StyleXStylesWithout` over `StaticStyles` and `StaticStylesWithout` for type-safe style objects.
### StyleXStyles
Accept any StyleX styles:
```tsx
import type { StyleXStyles } from '@stylexjs/stylex';
type Props = {
style?: StyleXStyles;
};
```
Constrain to specific properties:
```tsx
type Props = {
style?: StyleXStyles<{
color?: string;
backgroundColor?: string;
}>;
};
```
### StyleXStylesWithout
Exclude specific properties:
```tsx
import type { StyleXStylesWithout } from '@stylexjs/stylex';
type Props = {
// Allow all styles except layout properties
style?: StyleXStylesWithout<{
margin: unknown;
padding: unknown;
width: unknown;
height: unknown;
}>;
};
```
### VarGroup
Types for variable groups:
```tsx
import type { VarGroup } from '@stylexjs/stylex';
import { colors } from './tokens.stylex';
function ThemeProvider({ theme }: { theme: VarGroup }) {
return {children}
;
}
```
---
## Common antipatterns
**IMPORTANT: Avoid these common mistakes:**
### Don't import non-StyleX values
```tsx
// invalid: imported non-StyleX variable
import { PADDING } from './constants';
const styles = stylex.create({
container: { padding: PADDING },
});
// valid: use StyleX constants or variables
import { spacing } from './tokens.stylex';
const styles = stylex.create({
container: { padding: spacing.medium },
});
```
### Don't use `style` or `className` props
Do not apply `style` or `className` props on an element with a `stylex.props()` spread.
```tsx
// invalid: no `classname` and `style` prop usage
// valid
```
### Don't use media queries or pseudo-classes at the top level
Media queries and pseudo-classes must be nested inside property values, not at the top level of a style object.
```tsx
// invalid: media query at top level
const styles = stylex.create({
container: {
'@media (min-width: 768px)': {
padding: 16,
},
},
});
// invalid: pseudo-class at top level
const styles = stylex.create({
button: {
':hover': {
backgroundColor: 'blue',
},
},
});
// valid: nest inside property values
const styles = stylex.create({
container: {
padding: {
default: 8,
'@media (min-width: 768px)': 16,
},
},
button: {
backgroundColor: {
default: 'lightblue',
':hover': 'blue',
},
},
});
```
---
## More resources
- Official documentation: https://stylexjs.com
- API reference: https://stylexjs.com/docs/api
- GitHub repository: https://github.com/facebook/stylex