Styling
Pre-alphaThe registry and the CLI are not published yet.Roadmap

Styling

One component, three styling variants. Axiom adapts to the tool your project already uses.

Axiom doesn’t come with its own styling syntax. Each component exists in several variants, and your project uses the one that matches its stack.

Your components, your styling.

VariantToolDependency
stylesheetReact Native StyleSheetnone
unistylesreact-native-unistylesreact-native-unistyles
tailwindNativeWind or Uniwindnativewind or uniwind

NativeWind and Uniwind share the tailwind variant. Component classes are identical; only the setup differs (config, Babel or Metro plugin, supported Tailwind versions), and init handles it.

Rollout

stylesheet ships first for every component. unistyles follows, then tailwind once the behavior / style split is proven on the first two.

Choosing a variant

  • stylesheet if you don’t use a styling library, or want zero extra dependencies. It needs no global provider.
  • unistyles if your project already uses Unistyles.
  • tailwind if your project uses NativeWind or Uniwind.

You choose once, at npx axiom init. The choice is stored in axiom.json and every add copies that variant.

Behavior and style are separate

What makes a component useful on mobile (state, gestures, animation, accessibility, touch targets) doesn’t depend on the styling tool. It is written once and shared by every variant.

Behavior (shared)              Style (per variant)
─────────────────              ───────────────────
core/    Tappable, Overlay…    stylesheet/  StyleSheet.create + useTheme
hooks/   useSwitch, useSlider… unistyles/   StyleSheet.create((theme) => …)
                               tailwind/    className + variants
  • Non-trivial logic lives in core/, hooks/ or a hook next to the component.
  • A variant file only renders and styles. It never reimplements a behavior.
  • Components without logic (Badge, Separator…) are just one file per variant.
  • Every variant is written by hand, so it stays readable and easy to change. Nothing is generated from a shared source.

You only receive the shared behavior and the file for your variant, side by side:

components/ui/
├── use-switch.ts   # shared behavior
└── switch.tsx      # your variant

See Where files go.

The theme in each variant

The theme contracts (Tokens, ThemeColors, Components) and the default values don’t depend on the styling tool. Each variant translates them into its own form:

// `light` and `dark` objects, picked by a hook based on the system color scheme.
import { useColorScheme } from "react-native";

export const light = { tokens, colors: lightColors };
export const dark = { tokens, colors: darkColors };

export function useTheme() {
	return useColorScheme() === "dark" ? dark : light;
}
// Themes registered once, then switched automatically by Unistyles.
import { StyleSheet } from "react-native-unistyles";

StyleSheet.configure({
	themes: { light, dark },
	settings: { adaptiveThemes: true },
});
/* Tokens exposed as CSS variables, then referenced in the Tailwind config. */
:root {
	--color-background: hsla(0, 0%, 100%, 1);
	--color-content: hsla(0, 0%, 4%, 1);
}

.dark {
	--color-background: hsla(0, 0%, 4%, 1);
	--color-content: hsla(0, 0%, 98%, 1);
}

See Foundations for the contracts.

How the tools differ

The variants don’t only differ in syntax:

stylesheetunistylestailwind
Theme accessuseTheme()built invariables / classes
Variants (size, variant)style objects picked by handnative variantstailwind-variants or cva
Dark modeuseColorSchemeadaptive themesdark: prefix
Animated styles (Reanimated)animated styleanimated styleanimated style alongside className

Same contract everywhere

For a given component, every variant has:

  • the same props: names, types and defaults;
  • the same behavior and accessibility;
  • the same look with the default theme.

The one exception is each tool’s natural extension: every variant accepts style, and tailwind also accepts className.

Parity is checked in the demo app and in CI.

Switching later

Changing styling in axiom.json only affects components you add afterwards. Files already copied stay as they are, because they belong to your project. To move an existing component to another variant, add it again and confirm the overwrite, or port your version by hand.