Claude Skill

mobile-styling-unistyles

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

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download agents-inc-skills-dist_plugins_mobile-styling-unistyles_skills_mobile-styling-unistyles-3a51ef5.zip · 19 KB
Part of agents-inc/skills — 130 skills

Install

skills CLI npx skills add https://github.com/agents-inc/skills/tree/main/dist/plugins/mobile-styling-unistyles/skills/mobile-styling-unistyles
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install agents-inc-skills@llmmart
Git git clone https://github.com/agents-inc/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole agents-inc/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Unistyles 3.0 Patterns

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:




<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

Need Solution
Simple per-breakpoint values Breakpoint object { xs: 8, md: 16 }
Precise pixel ranges mq.only.width(0, 500)
Show/hide entire components <Display mq={...}> / <Hide mq={...}>
Orientation-specific styles Built-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>

Files (skills)
  • examples
    • core.md 10.3 KB
      # Unistyles Core Patterns
      
      > Related: [theming.md](theming.md) for theme setup, [responsive.md](responsive.md) for breakpoints, [variants.md](variants.md) for variants
      
      ---
      
      ## Pattern 1: Static vs Themed vs Runtime StyleSheets
      
      Unistyles supports three levels of StyleSheet complexity. Choose the simplest one that meets your needs.
      
      ### Static (no callback)
      
      Identical to React Native's StyleSheet.create. No theme or runtime access.
      
      ```typescript
      import { StyleSheet } from "react-native-unistyles";
      
      const styles = StyleSheet.create({
        container: {
          flex: 1,
          backgroundColor: "#fff",
        },
        separator: {
          height: StyleSheet.hairlineWidth,
          backgroundColor: "#e0e0e0",
        },
      });
      ```
      
      ### Themed (theme callback)
      
      Access theme properties. Styles recalculate when theme changes -- no re-renders.
      
      ```typescript
      import { StyleSheet } from "react-native-unistyles";
      
      const styles = StyleSheet.create((theme) => ({
        container: {
          flex: 1,
          backgroundColor: theme.colors.background,
        },
        title: {
          color: theme.colors.typography,
          fontWeight: "600",
        },
        link: {
          color: theme.colors.link,
          textDecorationLine: "underline",
        },
      }));
      ```
      
      ### Themed + Runtime (theme + rt callback)
      
      Access both theme and device runtime values. Use `rt` for insets, screen dimensions, font scale, orientation, and color scheme.
      
      ```typescript
      import { StyleSheet } from "react-native-unistyles";
      
      const styles = StyleSheet.create((theme, rt) => ({
        safeContainer: {
          flex: 1,
          backgroundColor: theme.colors.background,
          paddingTop: rt.insets.top,
          paddingBottom: rt.insets.bottom,
        },
        responsiveText: {
          color: theme.colors.typography,
          fontSize: rt.fontScale * 16,
        },
        fullScreenImage: {
          width: rt.screen.width,
          height: rt.screen.height * 0.4,
        },
      }));
      ```
      
      ### miniRuntime (rt) Properties
      
      | Property                 | Type              | Description                              |
      | ------------------------ | ----------------- | ---------------------------------------- |
      | `rt.insets.top`          | number            | Safe area top (notch/Dynamic Island)     |
      | `rt.insets.bottom`       | number            | Safe area bottom (home indicator)        |
      | `rt.insets.left`         | number            | Safe area left                           |
      | `rt.insets.right`        | number            | Safe area right                          |
      | `rt.insets.ime`          | number            | Keyboard height (input method editor)    |
      | `rt.screen.width`        | number            | Screen width in pixels                   |
      | `rt.screen.height`       | number            | Screen height in pixels                  |
      | `rt.fontScale`           | number            | System font scale (e.g. 1.0, 1.5)        |
      | `rt.pixelRatio`          | number            | Device pixel density (e.g. 2.0, 3.0)     |
      | `rt.colorScheme`         | string            | `"light"`, `"dark"`, or `"unspecified"`  |
      | `rt.isPortrait`          | boolean           | True when device is in portrait          |
      | `rt.isLandscape`         | boolean           | True when device is in landscape         |
      | `rt.contentSizeCategory` | string            | Accessibility text size (e.g. `"Large"`) |
      | `rt.statusBar`           | `{width, height}` | Status bar dimensions                    |
      | `rt.navigationBar`       | `{width, height}` | Navigation bar dimensions (Android)      |
      | `rt.rtl`                 | boolean           | Right-to-left language mode              |
      | `rt.themeName`           | string?           | Currently active theme name              |
      | `rt.breakpoint`          | string?           | Currently active breakpoint              |
      
      **Gotcha:** `rt.insets.bottom` is NOT dynamic for keyboard. Use `rt.insets.ime` for keyboard-responsive padding.
      
      ---
      
      ## Pattern 2: Dynamic Functions
      
      Dynamic functions accept component-level values as arguments. All arguments must be serializable (strings, numbers, booleans, arrays, objects).
      
      ### Basic Dynamic Function
      
      ```typescript
      const styles = StyleSheet.create((theme) => ({
        // Dynamic function with parameters
        listItem: (isOdd: boolean) => ({
          backgroundColor: isOdd
            ? theme.colors.surface
            : theme.colors.background,
          padding: 16,
        }),
      }));
      
      // Usage in JSX
      <View style={styles.listItem(index % 2 === 1)} />
      ```
      
      ### Multiple Parameters
      
      ```typescript
      const MAX_CARD_WIDTH = 400;
      
      const styles = StyleSheet.create((theme) => ({
        card: (width: number, isActive: boolean) => ({
          maxWidth: Math.min(width, MAX_CARD_WIDTH),
          backgroundColor: isActive
            ? theme.colors.activeCard
            : theme.colors.card,
          borderWidth: isActive ? 2 : 0,
          borderColor: theme.colors.accent,
        }),
      }));
      
      // Usage
      <View style={styles.card(containerWidth, isSelected)} />
      ```
      
      ### Mixing Static and Dynamic Styles
      
      ```typescript
      const styles = StyleSheet.create((theme) => ({
        // Static -- no parameters, just theme access
        container: {
          flex: 1,
          backgroundColor: theme.colors.background,
        },
        // Dynamic -- accepts component-level values
        avatar: (size: number) => ({
          width: size,
          height: size,
          borderRadius: size / 2,
          backgroundColor: theme.colors.surface,
        }),
      }));
      
      // Static styles used normally
      <View style={styles.container}>
        {/* Dynamic styles called with arguments */}
        <View style={styles.avatar(48)} />
      </View>
      ```
      
      ### Serializable Constraint
      
      ```typescript
      // CORRECT -- all serializable types
      styles.card(42)                    // number
      styles.card("primary")             // string
      styles.card(true)                  // boolean
      styles.card([1, 2, 3])            // array
      styles.card({ key: "value" })     // object
      
      // WRONG -- non-serializable types crash C++
      styles.card(() => {})              // function
      styles.card(<Component />)         // React element
      styles.card(new Promise(() => {})) // Promise
      ```
      
      ---
      
      ## Pattern 3: Style Merging
      
      ### Correct: Array Syntax
      
      ```typescript
      // Merge multiple styles -- order matters (last wins)
      <View style={[styles.container, styles.overlay]} />
      
      // Conditional styles
      <View style={[styles.card, isFocused && styles.cardFocused]} />
      
      // Dynamic + static merge
      <View style={[styles.card(isOdd), styles.shadow]} />
      
      // Multiple conditions
      <View style={[
        styles.button,
        isDisabled && styles.disabled,
        isPressed && styles.pressed,
      ]} />
      ```
      
      ### Wrong: Spreading
      
      ```typescript
      // NEVER do this -- destroys C++ state
      <View style={{ ...styles.container, ...styles.overlay }} />
      
      // NEVER do this either -- inline overrides also break state
      <View style={{ ...styles.container, backgroundColor: "red" }} />
      ```
      
      **What happens with spreading:** Unistyles detects the lost C++ state in dev mode, restores it, but merges in unpredictable order. Production builds may silently produce wrong styles.
      
      ---
      
      ## Pattern 4: withUnistyles for Third-Party Components
      
      Use `withUnistyles` only for third-party components that cannot receive Unistyles styles through normal `style` props. Regular RN components (`View`, `Text`, `Pressable`) work without it.
      
      ### Auto-Mapping (Components with style Prop)
      
      ```typescript
      import { withUnistyles } from "react-native-unistyles";
      import { BlurView } from "expo-blur";
      
      const UniBlurView = withUnistyles(BlurView);
      
      // style prop is automatically mapped
      <UniBlurView style={styles.blur} intensity={50} />
      ```
      
      ### Static Prop Mappings
      
      ```typescript
      import { withUnistyles } from "react-native-unistyles";
      import { Switch } from "react-native";
      
      // Map theme values to component props
      const UniSwitch = withUnistyles(Switch, (theme) => ({
        trackColor: {
          false: theme.colors.dimmed,
          true: theme.colors.tint,
        },
        thumbColor: theme.colors.background,
      }));
      
      <UniSwitch value={isEnabled} onValueChange={setIsEnabled} />
      ```
      
      ### Dynamic Props (uniProps)
      
      ```typescript
      // Use uniProps when mappings depend on component state
      <UniSwitch
        value={isEnabled}
        onValueChange={setIsEnabled}
        uniProps={(theme, rt) => ({
          trackColor: {
            false: theme.colors.dimmed,
            true: isEnabled
              ? theme.colors.primary
              : theme.colors.secondary,
          },
        })}
      />
      ```
      
      ### Props Priority (Lowest to Highest)
      
      1. Global mappings (second arg of `withUnistyles`)
      2. `uniProps` function
      3. Inline props on the component
      
      Inline props always win over `uniProps`, which win over global mappings.
      
      ### When to Use What
      
      | Component Type                              | Solution                                          |
      | ------------------------------------------- | ------------------------------------------------- |
      | React Native built-ins (`View`, `Text`)     | Normal `style` prop -- no wrapper needed          |
      | Third-party with `style` prop               | `withUnistyles(Component)` -- auto-maps style     |
      | Third-party with custom props (color, tint) | `withUnistyles(Component, mappings)` + `uniProps` |
      | Last resort / migration from v2             | `useUnistyles()` hook (causes re-renders)         |
      
      ---
      
      ## Pattern 5: UnistylesRuntime (Imperative Access)
      
      `UnistylesRuntime` provides read/write access to Unistyles state from anywhere -- including outside React components.
      
      ### Reading Values
      
      ```typescript
      import { UnistylesRuntime } from "react-native-unistyles";
      
      // Current state
      const currentTheme = UnistylesRuntime.themeName; // "light" | "dark" | ...
      const currentBreakpoint = UnistylesRuntime.breakpoint; // "xs" | "sm" | ...
      const { width, height } = UnistylesRuntime.screen;
      const isPortrait = UnistylesRuntime.isPortrait;
      const colorScheme = UnistylesRuntime.colorScheme; // "light" | "dark" | "unspecified"
      const insets = UnistylesRuntime.insets; // { top, bottom, left, right, ime }
      ```
      
      ### Mutating State
      
      ```typescript
      // Switch theme
      UnistylesRuntime.setTheme("dark");
      
      // Toggle adaptive themes
      UnistylesRuntime.setAdaptiveThemes(true);
      
      // Update theme at runtime (e.g. user picks accent color)
      UnistylesRuntime.updateTheme("light", (currentTheme) => ({
        ...currentTheme,
        colors: {
          ...currentTheme.colors,
          primary: userSelectedColor,
        },
      }));
      
      // System bars
      UnistylesRuntime.statusBar.setHidden(true);
      UnistylesRuntime.navigationBar.setHidden(true);
      UnistylesRuntime.setImmersiveMode(true); // hides both
      
      // Root view background
      UnistylesRuntime.setRootViewBackgroundColor("#000");
      ```
      
      **Important:** `UnistylesRuntime` getters are non-reactive outside StyleSheet callbacks. Reading `UnistylesRuntime.themeName` in a component does not subscribe to changes. Use `useUnistyles()` or `withUnistyles` when you need reactive access in JSX.
      
    • responsive.md 6.2 KB
      # Unistyles Responsive Patterns
      
      > Related: [core.md](core.md) for StyleSheet basics, [theming.md](theming.md) for theme setup
      
      ---
      
      ## Pattern 1: Breakpoint Object Syntax
      
      Convert any style property to a breakpoint object. Values cascade upward -- `xs` applies until a larger breakpoint overrides it.
      
      ```typescript
      import { StyleSheet } from "react-native-unistyles";
      
      const styles = StyleSheet.create((theme) => ({
        container: {
          padding: {
            xs: theme.spacing.sm, // 0px and up
            md: theme.spacing.md, // 768px and up
            xl: theme.spacing.xl, // 1200px and up
          },
          flexDirection: {
            xs: "column" as const,
            md: "row" as const,
          },
        },
        sidebar: {
          width: {
            xs: "100%" as const,
            md: 280,
            lg: 320,
          },
        },
      }));
      ```
      
      **Why good:** values cascade like CSS media queries -- `xs` stays active until `md` overrides it. No need to repeat values at every breakpoint.
      
      ### Breakpoints with Nested Objects
      
      Breakpoint objects work with complex style properties like `transform` and `shadowOffset`:
      
      ```typescript
      const styles = StyleSheet.create(() => ({
        card: {
          transform: {
            xs: [{ scale: 0.9 }],
            md: [{ scale: 1.0 }],
          },
          shadowOffset: {
            xs: { width: 0, height: 1 },
            md: { width: 0, height: 4 },
          },
        },
      }));
      ```
      
      ---
      
      ## Pattern 2: Media Queries (mq)
      
      For precise pixel ranges that don't align with named breakpoints, use the `mq` utility. Media queries always have higher priority than breakpoints.
      
      ```typescript
      import { StyleSheet, mq } from "react-native-unistyles";
      
      const styles = StyleSheet.create((theme) => ({
        sidebar: {
          display: {
            // Hidden on small screens, visible on 768px+
            [mq.only.width(0, 768)]: "none" as const,
            [mq.only.width(768)]: "flex" as const,
          },
        },
        grid: {
          flexDirection: {
            // Column layout below 500px width, row above
            [mq.only.width(0, 500)]: "column" as const,
            [mq.only.width(500)]: "row" as const,
          },
        },
      }));
      ```
      
      ### mq Syntax Reference
      
      ```typescript
      // Width-only ranges
      mq.only.width(0, 500); // 0px to 499px
      mq.only.width(500); // 500px and up
      mq.only.width(null, 800); // 0px to 799px
      mq.only.width("sm", "md"); // sm breakpoint to md breakpoint
      
      // Height-only ranges
      mq.only.height(300, 600); // 300px to 599px height
      mq.only.height(600); // 600px height and up
      
      // Combined width AND height
      mq.width(240, 380).and.height(300); // width 240-379 AND height 300+
      mq.height(500).and.width("sm"); // height 500+ AND width from sm breakpoint
      ```
      
      **Gotcha:** Invalid ranges like `mq.only.width(500, 200)` or `mq.only.width("xl", "sm")` are silently ignored.
      
      ---
      
      ## Pattern 3: Display and Hide Components
      
      Conditionally render entire components based on breakpoints or media queries. These are simple if/else wrappers -- no extra view layers added.
      
      ```typescript
      import { Display, Hide, mq } from "react-native-unistyles";
      
      function ResponsiveLayout() {
        return (
          <View style={styles.container}>
            {/* Show sidebar only on md and up */}
            <Display mq={mq.only.width("md")}>
              <Sidebar />
            </Display>
      
            {/* Hide desktop header on small screens */}
            <Hide mq={mq.only.width(0, 768)}>
              <DesktopHeader />
            </Hide>
      
            {/* Show mobile nav only on small screens */}
            <Display mq={mq.only.width(0, 768)}>
              <MobileNav />
            </Display>
      
            <MainContent />
          </View>
        );
      }
      ```
      
      **Why good:** no conditional logic in component code, no wasted renders -- Display/Hide evaluate at the C++ level
      
      ---
      
      ## Pattern 4: Built-in Orientation Breakpoints
      
      Even without custom breakpoints, Unistyles provides `portrait` and `landscape` breakpoints that resolve to the device's width in each orientation.
      
      ```typescript
      const styles = StyleSheet.create((theme) => ({
        header: {
          height: {
            portrait: 120,
            landscape: 64,
          },
          flexDirection: {
            portrait: "column" as const,
            landscape: "row" as const,
          },
        },
      }));
      ```
      
      ---
      
      ## Pattern 5: Runtime Values for Responsive Layouts
      
      Use the miniRuntime (`rt`) for device-specific values that update automatically.
      
      ### Safe Area Insets
      
      ```typescript
      const styles = StyleSheet.create((theme, rt) => ({
        screen: {
          flex: 1,
          paddingTop: rt.insets.top,
          paddingBottom: rt.insets.bottom,
          paddingLeft: rt.insets.left,
          paddingRight: rt.insets.right,
        },
      }));
      ```
      
      ### Keyboard-Aware Padding
      
      ```typescript
      const styles = StyleSheet.create((theme, rt) => ({
        input: {
          // rt.insets.ime = keyboard height (input method editor)
          // rt.insets.bottom = static safe area bottom
          paddingBottom: rt.insets.ime > 0 ? rt.insets.ime : rt.insets.bottom,
        },
      }));
      ```
      
      **Gotcha:** `rt.insets.bottom` is the static safe area inset (home indicator). It does NOT change when the keyboard appears. Use `rt.insets.ime` for keyboard-responsive padding.
      
      ### Font Scale Aware Styles
      
      ```typescript
      const BASE_FONT_SIZE = 16;
      const MAX_FONT_SIZE = 24;
      
      const styles = StyleSheet.create((_theme, rt) => ({
        body: {
          fontSize: Math.min(rt.fontScale * BASE_FONT_SIZE, MAX_FONT_SIZE),
        },
      }));
      ```
      
      ### Screen Dimension Dependent Styles
      
      ```typescript
      const IMAGE_ASPECT_RATIO = 0.5625; // 16:9 inverted
      const COLUMN_COUNT = 2;
      const COLUMN_GAP = 16;
      
      const styles = StyleSheet.create((_theme, rt) => ({
        heroImage: {
          width: rt.screen.width,
          height: rt.screen.width * IMAGE_ASPECT_RATIO,
        },
        gridItem: {
          width: (rt.screen.width - COLUMN_GAP * (COLUMN_COUNT + 1)) / COLUMN_COUNT,
        },
      }));
      ```
      
      ---
      
      ## Pattern 6: Mixing Breakpoints and Media Queries
      
      Breakpoints and mq can coexist on the same style. Media queries always take priority when both match.
      
      ```typescript
      const styles = StyleSheet.create((theme) => ({
        container: {
          padding: {
            xs: 8,
            md: 16,
            // mq overrides breakpoint when its range matches
            [mq.only.width(0, 320)]: 4,
          },
        },
      }));
      ```
      
      ### Native vs Web Breakpoint Behavior
      
      - **Native (iOS/Android):** Breakpoints are calculated based on screen pixels (default) or points (`nativeBreakpointsMode: "points"`)
      - **Web:** Breakpoints automatically generate CSS `@media` queries -- no JavaScript recalculation on resize
      
      ```typescript
      // To use screen points instead of pixels on native:
      StyleSheet.configure({
        breakpoints,
        settings: {
          nativeBreakpointsMode: "points",
        },
      });
      ```
      
    • theming.md 7.1 KB
      # Unistyles Theming Patterns
      
      > Related: [core.md](core.md) for StyleSheet basics, [responsive.md](responsive.md) for breakpoints
      
      ---
      
      ## Pattern 1: Theme Definition and Type Safety
      
      Themes are plain JavaScript objects. All themes must share the same TypeScript type.
      
      ### Define Themes
      
      ```typescript
      // unistyles.ts
      const lightTheme = {
        colors: {
          background: "#FCFAF8",
          foreground: "#EDEAE6",
          typography: "#1B140C",
          dimmed: "#ECE8E4",
          tint: "#9A734C",
          primary: "#2563EB",
          secondary: "#64748B",
          surface: "#FFFFFF",
          error: "#DC2626",
          link: "#1E3799",
          accents: {
            warning: "#F6E58D",
            success: "#BADC58",
            danger: "#FF7979",
          },
        },
        spacing: {
          xs: 4,
          sm: 8,
          md: 16,
          lg: 24,
          xl: 32,
        },
        // Functions in themes are fine
        gap: (multiplier: number) => multiplier * 8,
      } as const;
      
      const darkTheme = {
        colors: {
          background: "#221A11",
          foreground: "#332618",
          typography: "#FFFFFF",
          dimmed: "#A8A198",
          tint: "#C9AD92",
          primary: "#3B82F6",
          secondary: "#94A3B8",
          surface: "#2D2D2D",
          error: "#EF4444",
          link: "#0C2461",
          accents: {
            warning: "#F9CA24",
            success: "#6AB04C",
            danger: "#EB4D4B",
          },
        },
        spacing: {
          xs: 4,
          sm: 8,
          md: 16,
          lg: 24,
          xl: 32,
        },
        gap: (multiplier: number) => multiplier * 8,
      } as const;
      ```
      
      ### TypeScript Declarations
      
      ```typescript
      // Must be in the same file or imported before StyleSheet.configure
      type AppThemes = {
        light: typeof lightTheme;
        dark: typeof darkTheme;
      };
      
      declare module "react-native-unistyles" {
        export interface UnistylesThemes extends AppThemes {}
      }
      ```
      
      **Why this matters:** Without the module declaration, `theme` in `StyleSheet.create((theme) => ...)` is typed as `unknown`. With it, you get full autocompletion for `theme.colors.primary`, `theme.spacing.md`, etc.
      
      ---
      
      ## Pattern 2: StyleSheet.configure
      
      Call `StyleSheet.configure` once, in your entry file, before any component imports.
      
      ### Basic Setup
      
      ```typescript
      // unistyles.ts -- import this FIRST in your entry file
      import { StyleSheet } from "react-native-unistyles";
      
      StyleSheet.configure({
        themes: {
          light: lightTheme,
          dark: darkTheme,
        },
        breakpoints: {
          xs: 0, // Required: at least one must be 0
          sm: 576,
          md: 768,
          lg: 992,
          xl: 1200,
        },
        settings: {
          initialTheme: "light",
        },
      });
      ```
      
      ### Entry File Import Order
      
      ```typescript
      // index.ts (entry point)
      import "./unistyles"; // MUST be first -- before any component imports
      import "expo-router/entry";
      ```
      
      ### Settings Options
      
      | Setting                 | Type                     | Description                                                    |
      | ----------------------- | ------------------------ | -------------------------------------------------------------- |
      | `initialTheme`          | string or `() => string` | Sets the first theme. Function must be synchronous.            |
      | `adaptiveThemes`        | boolean                  | Auto-switch theme based on device color scheme                 |
      | `CSSVars`               | boolean                  | Enable CSS variables on web (default: true)                    |
      | `nativeBreakpointsMode` | `"pixels"` or `"points"` | How breakpoints are calculated on native (default: `"pixels"`) |
      
      **Mutually exclusive:** `initialTheme` and `adaptiveThemes` cannot both be set -- Unistyles throws an error.
      
      ### Initial Theme from Storage
      
      ```typescript
      StyleSheet.configure({
        themes: { light: lightTheme, dark: darkTheme },
        settings: {
          // Synchronous function -- read from MMKV or similar sync storage
          initialTheme: () => {
            const stored = storage.getString("theme");
            return stored === "dark" ? "dark" : "light";
          },
        },
      });
      ```
      
      ---
      
      ## Pattern 3: Adaptive Themes
      
      Adaptive themes automatically follow the device's color scheme (light/dark mode). Theme names must match: `"light"` and `"dark"`.
      
      ```typescript
      StyleSheet.configure({
        themes: {
          light: lightTheme,
          dark: darkTheme,
        },
        settings: {
          adaptiveThemes: true,
        },
      });
      ```
      
      ### Runtime Control
      
      ```typescript
      import { UnistylesRuntime } from "react-native-unistyles";
      
      // Check if adaptive themes are active
      const isAdaptive = UnistylesRuntime.hasAdaptiveThemes;
      
      // Disable adaptive themes (user chose manual theme)
      UnistylesRuntime.setAdaptiveThemes(false);
      
      // Then set a specific theme
      UnistylesRuntime.setTheme("dark");
      
      // Re-enable adaptive themes
      UnistylesRuntime.setAdaptiveThemes(true);
      
      // Read device color scheme
      const scheme = UnistylesRuntime.colorScheme; // "light" | "dark" | "unspecified"
      ```
      
      ---
      
      ## Pattern 4: Theme Switching at Runtime
      
      ```typescript
      import { UnistylesRuntime } from "react-native-unistyles";
      
      // Switch to a named theme
      UnistylesRuntime.setTheme("dark");
      
      // Read current theme name
      const current = UnistylesRuntime.themeName; // "dark"
      
      // Get the full theme object
      const theme = UnistylesRuntime.getTheme("light");
      ```
      
      **Important:** `setTheme` is incompatible with `adaptiveThemes: true`. Disable adaptive themes first if you want manual control.
      
      ---
      
      ## Pattern 5: Runtime Theme Updates
      
      Modify theme properties without switching themes. Useful for user-customizable accent colors.
      
      ```typescript
      import { UnistylesRuntime } from "react-native-unistyles";
      
      // Update specific properties in current theme
      UnistylesRuntime.updateTheme("light", (currentTheme) => ({
        ...currentTheme,
        colors: {
          ...currentTheme.colors,
          primary: userSelectedColor,
          tint: userSelectedAccent,
        },
      }));
      ```
      
      All components using the updated theme properties recalculate their styles automatically. No re-renders.
      
      ---
      
      ## Pattern 6: Scoped Themes
      
      Force a specific theme on a subtree, regardless of the global theme. Useful for camera screens (always dark), modals, or preview components.
      
      ```typescript
      import { ScopedTheme } from "react-native-unistyles";
      
      // Force dark theme on camera screen
      function CameraScreen() {
        return (
          <ScopedTheme name="dark">
            <View style={styles.container}>
              <Text style={styles.label}>Camera Preview</Text>
            </View>
          </ScopedTheme>
        );
      }
      
      // Invert the adaptive theme (light when global is dark, vice versa)
      <ScopedTheme invertedAdaptive>
        <View style={styles.invertedSection} />
      </ScopedTheme>
      
      // Reset to parent theme inside a scoped section
      <ScopedTheme name="dark">
        <View>
          <Text>Dark themed</Text>
          <ScopedTheme reset>
            <Text>Back to global theme</Text>
          </ScopedTheme>
        </View>
      </ScopedTheme>
      ```
      
      ### ScopedTheme Props
      
      | Prop               | Type    | Description                                |
      | ------------------ | ------- | ------------------------------------------ |
      | `name`             | string  | Force a specific theme by name             |
      | `invertedAdaptive` | boolean | Use the opposite of current adaptive theme |
      | `reset`            | boolean | Reset to parent/global theme               |
      
      ### Gotchas
      
      - Place `ScopedTheme` **inside** suspended components, not above `Suspense` boundaries
      - Metro HMR does not propagate child changes to parent `ScopedTheme` -- requires manual refresh
      - `ScopedTheme` does not use React Context (by design, for performance)
      
    • variants.md 7.1 KB
      # Unistyles Variants Patterns
      
      > Related: [core.md](core.md) for StyleSheet basics, [theming.md](theming.md) for themes
      
      ---
      
      ## Pattern 1: Basic Variants
      
      Define named groups of style options inside `variants`. Select the active combination with `styles.useVariants()`.
      
      ```typescript
      import { StyleSheet } from "react-native-unistyles";
      
      const styles = StyleSheet.create((theme) => ({
        badge: {
          paddingHorizontal: 8,
          paddingVertical: 4,
          borderRadius: 12,
          variants: {
            status: {
              success: { backgroundColor: theme.colors.accents.success },
              warning: { backgroundColor: theme.colors.accents.warning },
              error: { backgroundColor: theme.colors.accents.danger },
            },
            size: {
              sm: { paddingHorizontal: 6, paddingVertical: 2, borderRadius: 8 },
              md: { paddingHorizontal: 8, paddingVertical: 4, borderRadius: 12 },
              lg: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 16 },
            },
          },
        },
      }));
      
      // In component
      styles.useVariants({ status: "success", size: "md" });
      
      <View style={styles.badge}>
        <Text>Active</Text>
      </View>
      ```
      
      **Why good:** eliminates conditional style objects, TypeScript infers valid status/size combinations, all variant logic lives in the stylesheet
      
      ---
      
      ## Pattern 2: Boolean Variants
      
      Use `"true"` and `"false"` as string keys for toggle-style variants. These are distinct from a `default` variant.
      
      ```typescript
      const styles = StyleSheet.create((theme) => ({
        input: {
          borderWidth: 1,
          borderColor: theme.colors.dimmed,
          padding: 12,
          borderRadius: 8,
          variants: {
            isDisabled: {
              true: {
                opacity: 0.5,
                backgroundColor: theme.colors.dimmed,
              },
              false: {
                opacity: 1,
                backgroundColor: theme.colors.surface,
              },
            },
            hasError: {
              true: {
                borderColor: theme.colors.error,
                borderWidth: 2,
              },
              // "false" variant is optional -- base styles apply when not "true"
            },
          },
        },
      }));
      
      // In component
      styles.useVariants({
        isDisabled: false,
        hasError: hasValidationError,
      });
      ```
      
      **Gotcha:** Boolean variant keys are strings `"true"` and `"false"`, but you pass actual booleans to `useVariants`. You don't need to define both `"true"` and `"false"` -- omitting one means the base styles apply for that value.
      
      ---
      
      ## Pattern 3: Default Variants
      
      Define a `default` key that applies when no variant is selected (undefined) or when `useVariants({})` is called.
      
      ```typescript
      const styles = StyleSheet.create((theme) => ({
        button: {
          paddingHorizontal: 16,
          paddingVertical: 8,
          borderRadius: 8,
          variants: {
            color: {
              default: { backgroundColor: theme.colors.surface },
              primary: { backgroundColor: theme.colors.primary },
              danger: { backgroundColor: theme.colors.error },
            },
          },
        },
      }));
      
      // All of these use the "default" variant:
      styles.useVariants({});
      styles.useVariants(undefined);
      styles.useVariants({ color: undefined });
      ```
      
      ---
      
      ## Pattern 4: Compound Variants
      
      Apply additional styles when multiple variant conditions are met simultaneously. Compound variant styles override regular variant styles.
      
      ```typescript
      const styles = StyleSheet.create((theme) => ({
        text: {
          fontSize: 14,
          variants: {
            weight: {
              normal: { fontWeight: "400" },
              bold: { fontWeight: "700" },
            },
            color: {
              default: { color: theme.colors.typography },
              primary: { color: theme.colors.primary },
              link: { color: theme.colors.link },
            },
          },
          compoundVariants: [
            // When bold AND link, add underline
            {
              weight: "bold",
              color: "link",
              styles: {
                textDecorationLine: "underline",
              },
            },
            // When bold AND primary, increase size
            {
              weight: "bold",
              color: "primary",
              styles: {
                fontSize: 16,
              },
            },
          ],
        },
      }));
      ```
      
      **Precedence order (lowest to highest):**
      
      1. Base styles (outside `variants`)
      2. Regular variant styles
      3. Compound variant styles (always win over regular variants)
      
      ---
      
      ## Pattern 5: Component Props Pattern
      
      Wire variant selections to component props using the `UnistylesVariants` utility type. This provides full type safety from StyleSheet to JSX.
      
      ```typescript
      import { StyleSheet } from "react-native-unistyles";
      import type { UnistylesVariants } from "react-native-unistyles";
      import { View, Text, Pressable } from "react-native";
      
      const styles = StyleSheet.create((theme) => ({
        button: {
          borderRadius: 8,
          alignItems: "center" as const,
          justifyContent: "center" as const,
          variants: {
            variant: {
              filled: { backgroundColor: theme.colors.primary },
              outlined: {
                backgroundColor: "transparent",
                borderWidth: 1,
                borderColor: theme.colors.primary,
              },
              ghost: { backgroundColor: "transparent" },
            },
            size: {
              sm: { paddingHorizontal: 12, paddingVertical: 6 },
              md: { paddingHorizontal: 16, paddingVertical: 10 },
              lg: { paddingHorizontal: 24, paddingVertical: 14 },
            },
          },
        },
      }));
      
      // Derive props type from the stylesheet
      type ButtonVariants = UnistylesVariants<typeof styles>;
      
      interface ButtonProps extends ButtonVariants {
        title: string;
        onPress: () => void;
      }
      
      export function Button({ title, onPress, variant = "filled", size = "md" }: ButtonProps) {
        // useVariants binds the component to the selected variants
        styles.useVariants({ variant, size });
      
        return (
          <Pressable style={styles.button} onPress={onPress}>
            <Text>{title}</Text>
          </Pressable>
        );
      }
      
      // Usage -- TypeScript enforces valid variant combinations
      <Button title="Save" onPress={handleSave} variant="filled" size="lg" />
      <Button title="Cancel" onPress={handleCancel} variant="ghost" size="sm" />
      ```
      
      **Why good:** `UnistylesVariants` derives the exact variant options from the stylesheet -- adding a new variant in StyleSheet automatically adds it to the component's type
      
      ---
      
      ## Pattern 6: Multi-Style Variants
      
      When multiple style keys in the same StyleSheet need the same variant groups, define identical variant keys in each. This is common for components with separate container and text styles.
      
      ```typescript
      const styles = StyleSheet.create((theme) => ({
        container: {
          padding: 16,
          borderRadius: 8,
          variants: {
            intent: {
              info: { backgroundColor: theme.colors.accents.success },
              warning: { backgroundColor: theme.colors.accents.warning },
              error: { backgroundColor: theme.colors.accents.danger },
            },
          },
        },
        label: {
          fontWeight: "600",
          variants: {
            intent: {
              info: { color: "#1a472a" },
              warning: { color: "#7c4a03" },
              error: { color: "#7f1d1d" },
            },
          },
        },
      }));
      
      // Single useVariants call controls all style keys
      styles.useVariants({ intent: "warning" });
      
      <View style={styles.container}>
        <Text style={styles.label}>Warning message</Text>
      </View>
      ```
      
      **Important:** All variant options must appear in each style key that uses that variant group. If `container` has `info/warning/error`, then `label` must also define all three. Missing options cause TypeScript errors.
      
  • reference.md 9.5 KB
    # Unistyles Quick Reference
    
    ## API Cheat Sheet
    
    ### Imports
    
    ```typescript
    import {
      StyleSheet, // StyleSheet.create, StyleSheet.configure
      UnistylesRuntime, // Imperative read/write access
      ScopedTheme, // Force theme on subtree
      Display, // Show children at breakpoint/mq
      Hide, // Hide children at breakpoint/mq
      mq, // Media query utility
      withUnistyles, // HOC for third-party components
      useUnistyles, // Hook (avoid -- causes re-renders)
    } from "react-native-unistyles";
    
    import type {
      UnistylesVariants, // Derive variant props from stylesheet
      UnistylesThemes, // Module augmentation target
      UnistylesBreakpoints, // Module augmentation target
    } from "react-native-unistyles";
    ```
    
    ### StyleSheet Methods
    
    | Method                                                    | Purpose                                              |
    | --------------------------------------------------------- | ---------------------------------------------------- |
    | `StyleSheet.create(styles)`                               | Static styles (no theme)                             |
    | `StyleSheet.create((theme) => styles)`                    | Theme-dependent styles                               |
    | `StyleSheet.create((theme, rt) => styles)`                | Theme + runtime-dependent styles                     |
    | `StyleSheet.configure({ themes, breakpoints, settings })` | One-time setup                                       |
    | `StyleSheet.hairlineWidth`                                | Thinnest drawable line                               |
    | `StyleSheet.absoluteFillObject`                           | `{ position: 'absolute', left/top/right/bottom: 0 }` |
    | `StyleSheet.compose(a, b)`                                | Compose two styles                                   |
    | `StyleSheet.flatten(styles)`                              | Flatten nested styles                                |
    | `StyleSheet.addChangeListener(cb)`                        | Subscribe to dependency changes (v3.1.0+)            |
    
    ### UnistylesRuntime Properties
    
    | Property                     | Type                                   | Reactive? |
    | ---------------------------- | -------------------------------------- | --------- |
    | `themeName`                  | `string?`                              | No\*      |
    | `breakpoint`                 | `string?`                              | No\*      |
    | `screen`                     | `{ width, height }`                    | No\*      |
    | `isPortrait` / `isLandscape` | boolean                                | No\*      |
    | `colorScheme`                | `"light"` / `"dark"` / `"unspecified"` | No\*      |
    | `insets`                     | `{ top, bottom, left, right, ime }`    | No\*      |
    | `statusBar`                  | `{ width, height }`                    | No\*      |
    | `navigationBar`              | `{ width, height }`                    | No\*      |
    | `pixelRatio`                 | number                                 | No\*      |
    | `fontScale`                  | number                                 | No\*      |
    | `rtl`                        | boolean                                | No\*      |
    | `hasAdaptiveThemes`          | boolean                                | No\*      |
    | `contentSizeCategory`        | string                                 | No\*      |
    | `breakpoints`                | object                                 | No\*      |
    | `getTheme(name?)`            | Theme object                           | No\*      |
    
    \*Non-reactive outside StyleSheet callbacks. Use `useUnistyles()` or `withUnistyles` for reactive access in components.
    
    ### UnistylesRuntime Methods
    
    | Method                              | Purpose                 |
    | ----------------------------------- | ----------------------- |
    | `setTheme(name)`                    | Switch active theme     |
    | `setAdaptiveThemes(bool)`           | Toggle adaptive theming |
    | `updateTheme(name, updater)`        | Modify theme at runtime |
    | `statusBar.setHidden(bool)`         | Show/hide status bar    |
    | `navigationBar.setHidden(bool)`     | Show/hide nav bar       |
    | `setImmersiveMode(bool)`            | Hide both bars          |
    | `setRootViewBackgroundColor(color)` | Set root background     |
    
    ---
    
    ## Decision Framework
    
    ### When to Use Which API
    
    ```
    Do you need theme colors in styles?
    |-- NO -> StyleSheet.create({ ... }) -- static, no callback
    +-- YES -> StyleSheet.create((theme) => ...)
        |
        Do you also need device values (insets, screen, fontScale)?
        +-- YES -> StyleSheet.create((theme, rt) => ...)
    
    Do styles depend on component props/state?
    |-- YES -> Dynamic function: style: (arg) => ({ ... })
    +-- NO -> Static or theme-only style is enough
    
    Need different visual modes (size, color, state)?
    |-- YES -> Use variants: { ... } inside the style
    +-- NO -> Regular style properties
    
    Need to conditionally show/hide entire components by screen size?
    |-- YES -> <Display mq={...}> / <Hide mq={...}>
    +-- NO -> Use breakpoint objects on style properties
    ```
    
    ---
    
    ## v2 to v3 Migration Checklist
    
    ### Configuration
    
    | v2                                   | v3                                           |
    | ------------------------------------ | -------------------------------------------- |
    | `UnistylesRegistry.addConfig()`      | `StyleSheet.configure()`                     |
    | `UnistylesRegistry.addThemes()`      | `StyleSheet.configure({ themes: ... })`      |
    | `UnistylesRegistry.addBreakpoints()` | `StyleSheet.configure({ breakpoints: ... })` |
    | `useInitialTheme(...)`               | `settings: { initialTheme: "..." }`          |
    | `UnistylesProvider`                  | Remove -- not needed in v3                   |
    
    ### StyleSheet
    
    | v2                                        | v3                                     |
    | ----------------------------------------- | -------------------------------------- |
    | `createStyleSheet(...)`                   | `StyleSheet.create(...)`               |
    | `useStyles(stylesheet)`                   | Use styles directly -- no hook needed  |
    | `useStyles(stylesheet, { variant: "x" })` | `styles.useVariants({ variant: "x" })` |
    | `UnistylesRuntime.hairlineWidth`          | `StyleSheet.hairlineWidth`             |
    
    ### Hooks and Components
    
    | v2                                          | v3                                                                                               |
    | ------------------------------------------- | ------------------------------------------------------------------------------------------------ |
    | `useStyles()` for theme access              | `StyleSheet.create((theme) => ...)` in styles, or `withUnistyles` / `useUnistyles` in components |
    | Breakpoint conditionals in JS               | `<Display mq={...}>` / `<Hide mq={...}>`                                                         |
    | `UnistylesRuntime.statusBar.setColor()`     | Removed (Android 15 deprecation)                                                                 |
    | `UnistylesRuntime.navigationBar.setColor()` | Removed (Android 15 deprecation)                                                                 |
    | `addPlugin()` / `removePlugin()`            | Removed (plugins eliminated)                                                                     |
    
    ### Style Syntax
    
    | v2                                 | v3                                          |
    | ---------------------------------- | ------------------------------------------- |
    | `{ ...styles.a, ...styles.b }`     | `[styles.a, styles.b]` (array syntax)       |
    | Color methods with alpha parameter | Color methods accept single color parameter |
    
    ### Removed Settings
    
    - `plugins` -- eliminated entirely
    - `experimentalCSSMediaQueries` -- now always enabled
    - `windowResizeDebounceTimeMs` -- no debouncing
    - `disableAnimatedInsets` -- insets no longer re-render
    
    ### Insets
    
    | v2                               | v3                                                                  |
    | -------------------------------- | ------------------------------------------------------------------- |
    | `insets.bottom` (keyboard-aware) | `insets.ime` for keyboard, `insets.bottom` is static safe area only |
    
    ---
    
    ## Requirements
    
    | Requirement     | Minimum                                      |
    | --------------- | -------------------------------------------- |
    | React Native    | 0.78.0+                                      |
    | Architecture    | New Architecture (Fabric) required           |
    | Expo SDK        | 53+ (if using Expo)                          |
    | Xcode           | 16+ recommended                              |
    | Peer dependency | `react-native-nitro-modules` (fixed version) |
    
    ### Babel Plugin
    
    ```javascript
    // babel.config.js
    plugins: [["react-native-unistyles/plugin", { root: "src" }]];
    ```
    
    | Option               | Purpose                                                |
    | -------------------- | ------------------------------------------------------ |
    | `root` (required)    | Folder containing components to process                |
    | `autoProcessImports` | Process files with specific imports (monorepo support) |
    | `autoRemapImports`   | Remap uncommon imports to Unistyles components         |
    | `autoProcessPaths`   | Extend processing to node_modules packages             |
    | `debug`              | Log detected dependencies to console                   |
    
    ### TypeScript Module Augmentation
    
    ```typescript
    type AppThemes = { light: typeof lightTheme; dark: typeof darkTheme };
    type AppBreakpoints = typeof breakpoints;
    
    declare module "react-native-unistyles" {
      export interface UnistylesThemes extends AppThemes {}
      export interface UnistylesBreakpoints extends AppBreakpoints {}
    }
    ```
    
  • SKILL.md 16.1 KB
    ---
    name: mobile-styling-unistyles
    description: Unistyles 3.0 styling - C++ powered StyleSheet superset with zero re-renders, theming, breakpoints, variants, dynamic functions, runtime values
    ---
    
    # Unistyles 3.0 Patterns
    
    > **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:**
    
    - [examples/core.md](examples/core.md) - StyleSheet setup, theme access, dynamic functions, style merging
    - [examples/theming.md](examples/theming.md) - Theme configuration, adaptive themes, scoped themes, runtime switching
    - [examples/responsive.md](examples/responsive.md) - Breakpoints, media queries, Display/Hide, runtime values
    - [examples/variants.md](examples/variants.md) - Variants, compound variants, boolean variants, component props pattern
    - [reference.md](reference.md) - API quick reference, decision frameworks, v2-to-v3 migration
    
    ---
    
    <philosophy>
    
    ## 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)
    
    </philosophy>
    
    ---
    
    <patterns>
    
    ## 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.
    
    ```typescript
    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](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.
    
    ```typescript
    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](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).
    
    ```typescript
    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](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`.
    
    ```typescript
    // In style definitions -- breakpoint object syntax
    const styles = StyleSheet.create((theme) => ({
      container: {
        flexDirection: {
          xs: "column",
          md: "row",
        },
        padding: {
          xs: 8,
          sm: 16,
          lg: 24,
        },
      },
    }));
    ```
    
    ```typescript
    // 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](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.
    
    ```typescript
    // 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](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.
    
    ```typescript
    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](examples/core.md) for uniProps priority and when to choose withUnistyles vs useUnistyles.
    
    </patterns>
    
    ---
    
    <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
    
    | Need                         | Solution                                      |
    | ---------------------------- | --------------------------------------------- |
    | Simple per-breakpoint values | Breakpoint object `{ xs: 8, md: 16 }`         |
    | Precise pixel ranges         | `mq.only.width(0, 500)`                       |
    | Show/hide entire components  | `<Display mq={...}>` / `<Hide mq={...}>`      |
    | Orientation-specific styles  | Built-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>
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related