Unistyles 3.0 Patterns

SkillAI & models

Unistyles 3.0 styling - C++ powered StyleSheet superset with zero re-renders, theming, breakpoints, variants, dynamic functions, runtime values

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Unistyles 3.0 Patterns skill

What this skill tells your AI

The instructions your AI receives, as published by agents-inc/skills in src/skills/mobile-styling-unistyles/SKILL.md and read by ahel’s review.

Quick Guide: Unistyles 3.0 is a StyleSheet superset powered by Nitro Modules (C++/JSI). Import StyleSheet from react-native-unistyles instead of react-native -- same API, but with themes, breakpoints, variants, dynamic functions, and runtime values. Zero re-renders: styles update via the Shadow Tree, not React state. Configure with StyleSheet.configure() before any StyleSheet.create(). Never spread styles ({...a, ...b}) -- use array syntax ([a, b]). Requires New Architecture (RN 0.78+).


<critical_requirements>

CRITICAL: Before Using This Skill

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST import StyleSheet from react-native-unistyles, NOT from react-native -- the Unistyles version is a superset that enables all features)

(You MUST call StyleSheet.configure() BEFORE any StyleSheet.create() -- configure in your entry file before importing components)

(You MUST use array syntax [styles.a, styles.b] for merging styles -- NEVER spread {...styles.a, ...styles.b} as it destroys C++ state)

(You MUST NOT use useUnistyles hook in regular components -- it forces full re-renders, defeating Unistyles' zero-render architecture)

(You MUST pass only serializable arguments to dynamic functions -- strings, numbers, booleans, arrays, objects (no functions or components))

</critical_requirements>


Auto-detection: Unistyles, react-native-unistyles, StyleSheet.configure, UnistylesRuntime, UnistylesThemes, UnistylesBreakpoints, useVariants, compoundVariants, ScopedTheme, withUnistyles, useUnistyles, miniRuntime, rt.insets, rt.screen, mq.only, Display, Hide

When to use:

  • Styling React Native apps that need dynamic theming (light/dark or custom themes)
  • Building responsive layouts with breakpoints and media queries across mobile and web
  • Creating reusable component variants (size, color, state) without conditional logic
  • Accessing runtime device values (insets, screen size, font scale) inside stylesheets
  • Migrating from Unistyles 2.x to 3.0

When NOT to use:

  • Apps that cannot use the New Architecture (requires RN 0.78+, Fabric)
  • Expo Go apps (requires development builds with native modules)
  • Minimal apps with no theme switching or responsive needs (plain StyleSheet suffices)
  • Apps using a utility-class approach (consider a utility-class styling solution instead)

Key patterns covered:

  • StyleSheet.configure: themes, breakpoints, settings registration
  • StyleSheet.create with theme and miniRuntime (rt) access
  • Variants and compound variants for reusable component styles
  • Dynamic functions with serializable parameters
  • Breakpoints, media queries, and Display/Hide components
  • Runtime values: insets, screen dimensions, font scale, color scheme
  • Scoped themes and adaptive themes
  • withUnistyles for third-party component integration
  • Style merging with array syntax (never spread)

Detailed Resources:


Philosophy

Unistyles 3.0 is a StyleSheet superset -- if you know React Native's StyleSheet.create, you know 80% of Unistyles. The remaining 20% is what makes it powerful: themes, responsive breakpoints, variants, and runtime values, all managed in C++ via JSI with zero React re-renders.

How it works:

  1. Babel plugin analyzes StyleSheets at build time, detecting dependencies (theme, runtime, breakpoints)
  2. C++ core reconstructs StyleSheets natively and tracks which styles depend on what
  3. Shadow Tree updates bypass React entirely -- when a theme changes or the device rotates, only the affected ShadowNodes update their styles directly

Core principles:

  1. Zero re-renders -- No hooks, no context, no state updates for style changes. The C++ core updates the Shadow Tree directly.
  2. StyleSheet superset -- Same API as React Native's StyleSheet. Replace the import, keep your code.
  3. Type-safe themes -- TypeScript declarations ensure full autocompletion for theme properties.
  4. Selective recalculation -- Only styles that depend on a changed value (theme, breakpoint, inset) are recalculated.
  5. Cross-platform -- Same styles work on iOS, Android, and web (with automatic CSS class generation for web).

When Unistyles adds value over plain StyleSheet:

  • Multiple themes (dark/light/custom) with instant switching
  • Responsive layouts that adapt to screen size, orientation, or device type
  • Component variants (primary/secondary, small/large) without conditional style logic
  • Runtime-dependent styles (safe area insets, font scale, keyboard height)

Core Patterns

Pattern 1: StyleSheet.create with Theme and Runtime

Replace react-native import with react-native-unistyles. The create function accepts a callback with theme and rt (miniRuntime) for dynamic styles. Static styles (no theme/runtime) work identically to plain StyleSheet.

import { StyleSheet } from "react-native-unistyles";

const styles = StyleSheet.create((theme, rt) => ({
  container: {
    flex: 1,
    backgroundColor: theme.colors.background,
    paddingTop: rt.insets.top,
  },
  text: {
    color: theme.colors.typography,
    fontSize: rt.fontScale * 16,
  },
  // Static styles work exactly like plain StyleSheet
  separator: {
    height: StyleSheet.hairlineWidth,
    backgroundColor: "#ccc",
  },
}));

Why good: theme and rt are injected by the C++ core -- no hooks needed, no re-renders when theme or device values change

See examples/core.md for static vs themed vs runtime StyleSheets and the full miniRuntime property list.


Pattern 2: Variants and Compound Variants

Define style variations inside variants -- then select them with styles.useVariants(). Compound variants apply styles when multiple variant conditions are met simultaneously.

const styles = StyleSheet.create((theme) => ({
  button: {
    paddingHorizontal: 16,
    paddingVertical: 8,
    borderRadius: 8,
    variants: {
      color: {
        primary: { backgroundColor: theme.colors.primary },
        secondary: { backgroundColor: theme.colors.secondary },
      },
      size: {
        sm: { paddingHorizontal: 8, paddingVertical: 4 },
        lg: { paddingHorizontal: 24, paddingVertical: 12 },
      },
    },
    compoundVariants: [
      {
        color: "primary",
        size: "lg",
        styles: { borderWidth: 2, borderColor: theme.colors.accent },
      },
    ],
  },
}));

// In component -- call useVariants to select active variants
styles.useVariants({ color: "primary", size: "lg" });

Why good: eliminates conditional style objects, compound variants reduce complex if/else chains, TypeScript infers valid variant combinations

See examples/variants.md for boolean variants, default variants, the component props pattern with UnistylesVariants, and multi-style variants.


Pattern 3: Dynamic Functions

When styles depend on component-level values (not just theme/runtime), use dynamic functions. Arguments must be serializable (strings, numbers, booleans, arrays, objects).

const styles = StyleSheet.create((theme) => ({
  card: (isHighlighted: boolean, index: number) => ({
    backgroundColor: isHighlighted
      ? theme.colors.highlight
      : theme.colors.surface,
    opacity: index === 0 ? 1 : 0.8,
  }),
}));

// In JSX -- call the function with arguments
<View style={styles.card(isHighlighted, index)} />

Why good: serializable arguments pass to C++ for native recalculation, full TypeScript inference on parameters

See examples/core.md for dynamic function patterns and the serializable constraint.


Pattern 4: Breakpoints and Media Queries

Define breakpoints in StyleSheet.configure, then use breakpoint objects or the mq utility in styles. At least one breakpoint must start at 0.

// In style definitions -- breakpoint object syntax
const styles = StyleSheet.create((theme) => ({
  container: {
    flexDirection: {
      xs: "column",
      md: "row",
    },
    padding: {
      xs: 8,
      sm: 16,
      lg: 24,
    },
  },
}));
// Media query syntax for precise ranges
import { mq } from "react-native-unistyles";

const styles = StyleSheet.create(() => ({
  sidebar: {
    display: {
      [mq.only.width(0, 768)]: "none",
      [mq.only.width(768)]: "flex",
    },
  },
}));

Why good: breakpoints cascade like CSS (xs applies until sm overrides), mq provides precise range control, web automatically generates CSS media queries

See examples/responsive.md for Display/Hide components, landscape/portrait breakpoints, and mixing breakpoints with mq.


Pattern 5: Style Merging (Array Syntax)

Never spread Unistyles objects. Spreading destroys the C++ state that tracks dependencies. Use React Native's array syntax for merging.

// CORRECT -- array syntax preserves C++ state
<View style={[styles.container, styles.overlay]} />
<View style={[styles.card, isFocused && styles.focused]} />

// WRONG -- spreading destroys C++ state
<View style={{ ...styles.container, ...styles.overlay }} />

Why bad (spread): spreading removes the C++ state Unistyles attaches, forcing it to restore state in unpredictable order; triggers dev-mode warnings

See examples/core.md for merging patterns and conditional style application.


Pattern 6: withUnistyles for Third-Party Components

Third-party components that don't expose native views via ref cannot benefit from Unistyles' Shadow Tree updates. Wrap them with withUnistyles to subscribe to theme and runtime changes.

import { withUnistyles } from "react-native-unistyles";
import { BlurHash } from "react-native-blurhash";

// Static mappings -- re-renders only when dependencies change
const UniBlurHash = withUnistyles(BlurHash, (theme) => ({
  color: theme.colors.tint,
}));

// Dynamic mappings via uniProps
<UniBlurHash
  uniProps={(theme, rt) => ({
    color: rt.colorScheme === "dark"
      ? theme.colors.darkTint
      : theme.colors.lightTint,
  })}
/>

Why good: component re-renders only when its dependencies change, not on every theme/runtime update

When to use: only for third-party components that don't work with standard Unistyles styles. Regular React Native components (View, Text, Pressable) work without it.

See examples/core.md for uniProps priority and when to choose withUnistyles vs useUnistyles.


<decision_framework>

Decision Framework

Choosing the Right Styling Approach

Does the component need theme colors or runtime values?
|-- NO -> Plain StyleSheet.create (static object, no callback)
+-- YES -> StyleSheet.create((theme, rt) => ...)
    |
    Does it also need component-local values (props, state)?
    |-- YES -> Dynamic function: style: (arg) => ({ ... })
    +-- NO -> Static theme/runtime access is enough

Does the style have multiple visual variants (size, color, state)?
|-- YES -> Use variants {} inside the style
|   |
|   Do combinations of variants need special treatment?
|   +-- YES -> Add compoundVariants []
+-- NO -> Regular style properties

Is this a third-party component that doesn't work with Unistyles styles?
|-- Try withUnistyles first (no re-renders)
+-- Only if that fails -> useUnistyles hook (causes re-renders)

Responsive: Breakpoints vs Media Queries vs Display/Hide

NeedSolution
Simple per-breakpoint valuesBreakpoint object { xs: 8, md: 16 }
Precise pixel rangesmq.only.width(0, 500)
Show/hide entire components<Display mq={...}> / <Hide mq={...}>
Orientation-specific stylesBuilt-in portrait / landscape breakpoints

</decision_framework>


<red_flags>

RED FLAGS

High Priority Issues:

  • Spreading styles {...styles.a, ...styles.b} -- destroys C++ state, causes unpredictable style resolution, triggers dev warnings
  • Using useUnistyles in regular components -- forces full re-renders, defeats the zero-render architecture
  • Calling StyleSheet.create before StyleSheet.configure -- styles won't have access to themes or breakpoints
  • Importing StyleSheet from react-native instead of react-native-unistyles -- styles work but lose all Unistyles features (themes, variants, breakpoints)
  • Passing non-serializable arguments to dynamic functions (functions, components, Promises) -- arguments are passed to C++ via folly::dynamic, non-serializable values crash

Medium Priority Issues:

  • Setting both initialTheme and adaptiveThemes: true in configure -- they are mutually exclusive, Unistyles will throw an error
  • Missing Babel plugin configuration -- without it, dependency detection, ref borrowing, and scoped variants don't work
  • Using useUnistyles at the root level -- subscribes the entire app tree to every theme/runtime change
  • Defining breakpoints without a 0 value -- at least one breakpoint must be 0 for CSS-like cascading to work

Gotchas & Edge Cases:

  • The bottom inset is NOT dynamic for keyboard -- use rt.insets.ime (input method editor) for keyboard-responsive padding
  • Babel plugin does NOT support moving functions outside StyleSheet.create or reassigning theme/rt to other variables -- the analysis is scope-bound
  • ScopedTheme does not work correctly above Suspense boundaries -- place it inside suspended components
  • Metro Fast Refresh (HMR) does not propagate child changes to parent ScopedTheme components -- requires manual refresh
  • withUnistyles uniProps are lower priority than inline props -- inline props override uniProps, which override global mappings
  • Boolean variants use string keys "true" and "false" -- they are distinct from a default variant
  • All themes must share the same TypeScript type -- mismatched theme shapes cause type errors
  • On web, Unistyles converts theme colors to CSS variables -- theme switching swaps a single class on <body>, no JS recomputation
  • UnistylesRuntime getters are non-reactive outside StyleSheet -- use useUnistyles or withUnistyles for reactive access in components
  • StyleSheet.addChangeListener() (v3.1.0+) is the escape hatch for animation libraries that need runtime update notifications

</red_flags>


<critical_reminders>

CRITICAL REMINDERS

All code must follow project conventions in CLAUDE.md

(You MUST import StyleSheet from react-native-unistyles, NOT from react-native -- the Unistyles version is a superset that enables all features)

(You MUST call StyleSheet.configure() BEFORE any StyleSheet.create() -- configure in your entry file before importing components)

(You MUST use array syntax [styles.a, styles.b] for merging styles -- NEVER spread {...styles.a, ...styles.b} as it destroys C++ state)

(You MUST NOT use useUnistyles hook in regular components -- it forces full re-renders, defeating Unistyles' zero-render architecture)

(You MUST pass only serializable arguments to dynamic functions -- strings, numbers, booleans, arrays, objects (no functions or components))

Failure to follow these rules will cause broken styles, unnecessary re-renders, and runtime crashes from the C++ core.

</critical_reminders>

Signals

GitHub stars
24
Forks
8
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
mobile-styling-unistyles
Source
github.com/agents-inc/skills