Agent skill

Uniwind

by CherryHQ in CherryHQ/cherry-studio-app

Uniwind — Tailwind CSS v4 styling for React Native. An agent skill from CherryHQ/cherry-studio-app.

AGPL-3.0Auto-check: warningsMobile

Install Uniwind

The automated check flagged lines worth reading first. See the safety section below.

skills CLI
$ npx skills add CherryHQ/cherry-studio-app --skill uniwind -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install CherryHQ/cherry-studio-app uniwind --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/CherryHQ/cherry-studio-app.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.agents/skills/uniwind .claude/skills/uniwind && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
uniwind
GitHub stars
4k
Token cost
~20k tokens
SKILL.md length
5,407 words
Files
1
Skills in repo
13
Repo updated
First seen
Licence
AGPL-3.0

At a glance

Uniwind — Tailwind CSS v4 styling for React Native. An agent skill from CherryHQ/cherry-studio-app.

  • Works in 3 steps: Variable-driven utilities… → Brand-new utilities (no Tailwind… → Overriding existing Tailwind utilities
  • Styling components in a React Native project that uses Tailwind with className
  • SKILL.md covers Critical Rules, Setup, Component Bindings and The accent- Prefix Pattern, plus 6 more sections
  • Calls npx, npm and bun

What it does

Uniwind is an agent skill from CherryHQ/cherry-studio-app. Uniwind — Tailwind CSS v4 styling for React Native. Use when adding, building, or styling components in a React Native project that uses Tailwind with className.

Its SKILL.md is about 20k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Mobile, covering Cross-platform mobile apps and CSS and styling. It works with Tailwind CSS, React Native and Expo. The repository describes itself as: 🍒 This is the mobile version of Cherry Studio. The licence is AGPL-3.0.

When your agent uses it

  • Styling components in a React Native project that uses Tailwind with className
  • Tasks that involve Cross-platform mobile apps
  • Tasks that involve CSS and styling

Example prompts

  • “/uniwind”

Workflow steps

3 steps, taken from the step headings in SKILL.md.

  1. Variable-driven utilities (runtime-injected values)
  2. Brand-new utilities (no Tailwind equivalent)
  3. Overriding existing Tailwind utilities

What it can do on your machine

Read from SKILL.md and the folder at commit fcfd953. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • npx
    • npm
    • bun
    • pnpm

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Links to these hosts (documentation or services it may open):

    • docs.uniwind.dev
    • uniwind.dev

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Uniwind loads about 20k tokens when it runs. Until then it costs about 42 tokens; SKILL.md has 5,407 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~42
When it runs · the whole SKILL.md, loaded when a task matches
~20k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check: warnings

The automated check found patterns that need a careful read before installing.

  • WarningMentions a credentials file (SSH keys, cloud or package-manager tokens)SKILL.md:1617
    # .npmrc
  • WarningMentions a credentials file (SSH keys, cloud or package-manager tokens)SKILL.md:2047
    blic-hoist-pattern[]=heroui-native` to `.npmrc` |

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from CherryHQ/cherry-studio-app at commit fcfd953, republished under its AGPL-3.0 licence (© CherryHQ). 5,407 words, ~20,315 tokens.

Download SKILL.mdSave it as .claude/skills/uniwind/SKILL.md (or your agent's skills folder).
name
uniwind
description
Uniwind — Tailwind CSS v4 styling for React Native. Use when adding, building, or styling components in a React Native project that uses Tailwind with className.

Uniwind — Complete Reference

Uniwind 1.6.0+ / Tailwind CSS v4 / React Native 0.81+ / Expo SDK 54+

If user has lower version, recommend updating to 1.6.0+ for best experience.

Uniwind brings Tailwind CSS v4 to React Native. All core React Native components support the className prop out of the box. Styles are compiled at build time — no runtime overhead.

Critical Rules

  1. Tailwind v4 only — Use @import 'tailwindcss' not @tailwind base. Tailwind v3 is not supported.
  2. Never construct classNames dynamically — Tailwind scans at build time. bg-${color}-500 will NOT work. Use complete string literals, mapping objects, or ternaries.
  3. Never use cssInterop or remapProps — Those are NativeWind APIs. Uniwind does not override global components.
  4. No tailwind.config.js — All config goes in global.css via @theme and @layer theme.
  5. No ThemeProvider required — Use Uniwind.setTheme() directly.
  6. withUniwindConfig must be the outermost Metro config wrapper.
  7. NEVER wrap react-native or react-native-reanimated components with withUniwind — View, Text, Pressable, Image, TextInput, ScrollView, FlatList, Switch, Modal, Animated.View, Animated.Text, etc. already have full className support built in. Wrapping them with withUniwind will break behavior. Only use withUniwind for third-party components (e.g., expo-image, expo-blur, moti).
  8. Font families: single font only — React Native doesn't support fallbacks. Use --font-sans: 'Roboto-Regular' not 'Roboto', sans-serif.
  9. All theme variants must define the same set of CSS variables — If light defines --color-primary, then dark and every custom theme must too. Mismatched variables cause runtime errors.
  10. accent- prefix is REQUIRED for non-style color props — This is crucial. Props like color (Button, ActivityIndicator), tintColor (Image), thumbColor (Switch), placeholderTextColor (TextInput) are NOT part of the style object. You MUST use the corresponding {propName}ClassName prop with accent- prefixed classes. Example: <ActivityIndicator colorClassName="accent-blue-500" /> NOT <ActivityIndicator className="text-blue-500" />. Regular Tailwind color classes (like text-blue-500) only work on className (which maps to style). For non-style color props, always use accent-.
  11. rem default is 16px — NativeWind used 14px. Set polyfills: { rem: 14 } in metro config if migrating.
  12. cssEntryFile must be a relative path string — Use './global.css' not path.resolve(__dirname, 'global.css').
  13. Deduplicate with cn() when mixing custom CSS classes and Tailwind — Uniwind does NOT auto-deduplicate. If a custom CSS class (.card { padding: 16px }) and a Tailwind utility (p-6) set the same property, both apply with unpredictable results. Always wrap with cn('card', 'p-6') when there's overlap.
  14. Important utilities are supported — Tailwind important modifier works in classNames with ! at the end: bg-red-500!, active:bg-red-500!, ios:pt-12!. Leading !bg-red-500 syntax is deprecated. Important utilities override non-important utilities for the same style property, but inline style still overrides className.

Setup

Installation
bash
# or other package manager
bun install uniwind tailwindcss

Requires Tailwind CSS v4+.

global.css

Create a CSS entry file:

css
@import 'tailwindcss';
@import 'uniwind';

Import in your App component (e.g., App.tsx or app/_layout.tsx), NOT in index.ts/index.js — importing there breaks hot reload:

tsx
// app/_layout.tsx or App.tsx
import './global.css';

The directory containing global.css is the app root — Tailwind scans for classNames starting from this directory.

Metro Configuration
js
const { getDefaultConfig } = require('expo/metro-config');
// Bare RN: const { getDefaultConfig } = require('@react-native/metro-config');
const { withUniwindConfig } = require('uniwind/metro');

const config = getDefaultConfig(__dirname);

// withUniwindConfig MUST be the OUTERMOST wrapper
module.exports = withUniwindConfig(config, {
  cssEntryFile: './global.css',           // Required — relative path from project root
  polyfills: { rem: 16 },                // Optional — base rem value (default 16)
  extraThemes: ['ocean', 'sunset'],       // Optional — custom themes beyond light/dark
  dtsFile: './uniwind-types.d.ts',        // Optional — TypeScript types output path
  debug: true,                            // Optional — log unsupported CSS in dev
  isTV: false,                            // Optional — enable TV platform support
});

For most flows, keep defaults, only provide cssEntryFile.

Wrapper order — Uniwind must wrap everything else:

js
// CORRECT
module.exports = withUniwindConfig(withOtherConfig(config, opts), { cssEntryFile: './global.css' });

// WRONG — Uniwind is NOT outermost
module.exports = withOtherConfig(withUniwindConfig(config, { cssEntryFile: './global.css' }), opts);
Vite Configuration (v1.2.0+)

If user has storybook setup, add extra vite config:

ts
import tailwindcss from '@tailwindcss/vite';
import { uniwind } from 'uniwind/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    tailwindcss(),
    uniwind({
      cssEntryFile: './src/global.css',
      dtsFile: './src/uniwind-types.d.ts',
    }),
  ],
});
TypeScript

Uniwind auto-generates a .d.ts file (default: ./uniwind-types.d.ts) after running Metro. Place it in src/ or app/ for auto-inclusion, or add to tsconfig.json:

json
{ "include": ["./uniwind-types.d.ts"] }

If user has some typescript errors related to classNames, just run metro server to build the d.ts file.

Expo Router Placement
text
project/
├── app/_layout.tsx    ← import '../global.css' here
├── components/
├── global.css         ← project root (best location)
└── metro.config.js    ← cssEntryFile: './global.css'

If global.css is in app/ dir, add @source for sibling directories:

css
@import 'tailwindcss';
@import 'uniwind';
@source '../components';
Tailwind IntelliSense (VS Code / Cursor / Windsurf)
json
{
  "tailwindCSS.classAttributes": [
    "class", "className", "headerClassName",
    "contentContainerClassName", "columnWrapperClassName",
    "endFillColorClassName", "imageClassName", "tintColorClassName",
    "ios_backgroundColorClassName", "thumbColorClassName",
    "trackColorOnClassName", "trackColorOffClassName",
    "selectionColorClassName", "cursorColorClassName",
    "underlineColorAndroidClassName", "placeholderTextColorClassName",
    "selectionHandleColorClassName", "colorsClassName",
    "progressBackgroundColorClassName", "titleColorClassName",
    "underlayColorClassName", "colorClassName",
    "backdropColorClassName", "backgroundColorClassName",
    "statusBarBackgroundColorClassName", "drawerBackgroundColorClassName",
    "ListFooterComponentClassName", "ListHeaderComponentClassName"
  ],
  "tailwindCSS.classFunctions": ["useResolveClassNames"]
}
Monorepo Support

Add @source directives in global.css for packages outside the CSS entry file's directory:

css
@import 'tailwindcss';
@import 'uniwind';
@source "../../packages/ui/src";
@source "../../packages/shared/src";

Also needed for node_modules packages that contain Uniwind classes (e.g., shared UI libraries).

Component Bindings

All core React Native components support className out of the box. Some have additional className props for sub-styles (like contentContainerClassName) and non-style color props (requiring accent- prefix).

Complete Reference

Legend: Props marked with ⚡ require the accent- prefix. Props in parentheses are platform-specific.

View
PropMaps toPrefix
classNamestyle—
Text
PropMaps toPrefix
classNamestyle—
selectionColorClassNameselectionColor⚡ accent-
Pressable
PropMaps toPrefix
classNamestyle—

Supports active:, disabled:, focus: state selectors.

Image
PropMaps toPrefix
classNamestyle—
tintColorClassNametintColor⚡ accent-
TextInput
PropMaps toPrefix
classNamestyle—
cursorColorClassNamecursorColor⚡ accent-
selectionColorClassNameselectionColor⚡ accent-
placeholderTextColorClassNameplaceholderTextColor⚡ accent-
selectionHandleColorClassNameselectionHandleColor⚡ accent-
underlineColorAndroidClassNameunderlineColorAndroid (Android)⚡ accent-

Supports focus:, active:, disabled: state selectors.

ScrollView
PropMaps toPrefix
classNamestyle—
contentContainerClassNamecontentContainerStyle—
endFillColorClassNameendFillColor⚡ accent-
FlatList
PropMaps toPrefix
classNamestyle—
contentContainerClassNamecontentContainerStyle—
columnWrapperClassNamecolumnWrapperStyle—
ListHeaderComponentClassNameListHeaderComponentStyle—
ListFooterComponentClassNameListFooterComponentStyle—
endFillColorClassNameendFillColor⚡ accent-
SectionList
PropMaps toPrefix
classNamestyle—
contentContainerClassNamecontentContainerStyle—
ListHeaderComponentClassNameListHeaderComponentStyle—
ListFooterComponentClassNameListFooterComponentStyle—
endFillColorClassNameendFillColor⚡ accent-
VirtualizedList
PropMaps toPrefix
classNamestyle—
contentContainerClassNamecontentContainerStyle—
ListHeaderComponentClassNameListHeaderComponentStyle—
ListFooterComponentClassNameListFooterComponentStyle—
endFillColorClassNameendFillColor⚡ accent-
Switch
PropMaps toPrefix
thumbColorClassNamethumbColor⚡ accent-
trackColorOnClassNametrackColor.true (on)⚡ accent-
trackColorOffClassNametrackColor.false (off)⚡ accent-
ios_backgroundColorClassNameios_backgroundColor (iOS)⚡ accent-

Note: Switch does NOT support className (className?: never in types). Use only the color-specific className props above. Supports disabled: state selector.

ActivityIndicator
PropMaps toPrefix
classNamestyle—
colorClassNamecolor⚡ accent-
Button
PropMaps toPrefix
colorClassNamecolor⚡ accent-

Note: Button does not support className (no style prop on RN Button).

Modal
PropMaps toPrefix
classNamestyle—
backdropColorClassNamebackdropColor⚡ accent-
RefreshControl
PropMaps toPrefix
classNamestyle—
colorsClassNamecolors (Android)⚡ accent-
tintColorClassNametintColor (iOS)⚡ accent-
titleColorClassNametitleColor (iOS)⚡ accent-
progressBackgroundColorClassNameprogressBackgroundColor (Android)⚡ accent-
ImageBackground
PropMaps toPrefix
classNamestyle—
imageClassNameimageStyle—
tintColorClassNametintColor⚡ accent-
SafeAreaView
PropMaps toPrefix
classNamestyle—
KeyboardAvoidingView
PropMaps toPrefix
classNamestyle—
contentContainerClassNamecontentContainerStyle—
InputAccessoryView
PropMaps toPrefix
classNamestyle—
backgroundColorClassNamebackgroundColor⚡ accent-
TouchableHighlight
PropMaps toPrefix
classNamestyle—
underlayColorClassNameunderlayColor⚡ accent-

Supports active:, disabled: state selectors.

TouchableOpacity
PropMaps toPrefix
classNamestyle—

Supports active:, disabled: state selectors.

TouchableNativeFeedback
PropMaps toPrefix
classNamestyle—

Supports active:, disabled: state selectors.

TouchableWithoutFeedback
PropMaps toPrefix
classNamestyle—

Supports active:, disabled: state selectors.

Usage Examples
tsx
import { View, Text, Pressable, TextInput, ScrollView, FlatList, Switch, Image, ActivityIndicator, Modal, RefreshControl, Button } from 'react-native';

// View — basic layout
<View className="flex-1 bg-background p-4">
  <Text className="text-foreground text-lg font-bold">Title</Text>
</View>

// Pressable — with press/focus states
<Pressable className="bg-primary px-6 py-3 rounded-lg active:opacity-80 active:bg-primary/90 focus:ring-2">
  <Text className="text-white text-center font-semibold">Press Me</Text>
</Pressable>

// TextInput — with focus state and accent- color props
<TextInput
  className="border border-border rounded-lg px-4 py-2 text-base text-foreground focus:border-primary"
  placeholderTextColorClassName="accent-muted"
  selectionColorClassName="accent-primary"
  cursorColorClassName="accent-primary"
  selectionHandleColorClassName="accent-primary"
  underlineColorAndroidClassName="accent-transparent"
  placeholder="Enter text..."
/>

// ScrollView — with content container
<ScrollView className="flex-1" contentContainerClassName="p-4 gap-4">
  {/* content */}
</ScrollView>

// FlatList — with all sub-style props
<FlatList
  className="flex-1"
  contentContainerClassName="p-4 gap-3"
  columnWrapperClassName="gap-3"
  ListHeaderComponentClassName="pb-4"
  ListFooterComponentClassName="pt-4"
  endFillColorClassName="accent-gray-100"
  numColumns={2}
  data={items}
  renderItem={({ item }) => <ItemCard item={item} />}
/>

// Switch — no className support, use color-specific props only
<Switch
  thumbColorClassName="accent-white"
  trackColorOnClassName="accent-primary"
  trackColorOffClassName="accent-gray-300 dark:accent-gray-700"
  ios_backgroundColorClassName="accent-gray-200"
/>

// Image — tint color
<Image className="w-6 h-6" tintColorClassName="accent-primary" source={icon} />

// ActivityIndicator
<ActivityIndicator className="m-4" colorClassName="accent-primary" size="large" />

// Button — only colorClassName (no className)
<Button colorClassName="accent-primary" title="Submit" onPress={handleSubmit} />

// Modal — backdrop color
<Modal className="flex-1" backdropColorClassName="accent-black/50">
  {/* content */}
</Modal>

// RefreshControl — platform-specific color props
<RefreshControl
  className="p-4"
  tintColorClassName="accent-primary"
  titleColorClassName="accent-gray-500"
  colorsClassName="accent-primary"
  progressBackgroundColorClassName="accent-white dark:accent-gray-800"
/>

// ImageBackground — separate image styling
<ImageBackground
  className="flex-1 justify-center items-center"
  imageClassName="opacity-50"
  tintColorClassName="accent-blue-500"
  source={bgImage}
>
  <Text className="text-white text-2xl font-bold">Overlay</Text>
</ImageBackground>

// KeyboardAvoidingView
<KeyboardAvoidingView
  behavior="padding"
  className="flex-1 bg-white"
  contentContainerClassName="p-4 justify-end"
>
  <TextInput className="border border-gray-300 rounded-lg p-3" placeholder="Type..." />
</KeyboardAvoidingView>

// InputAccessoryView
<InputAccessoryView
  className="p-4 border-t border-gray-300"
  backgroundColorClassName="accent-white dark:accent-gray-800"
>
  <Button title="Done" onPress={dismissKeyboard} />
</InputAccessoryView>

// TouchableHighlight — underlay color
<TouchableHighlight
  className="bg-blue-500 px-6 py-3 rounded-lg"
  underlayColorClassName="accent-blue-600 dark:accent-blue-700"
  onPress={handlePress}
>
  <Text className="text-white font-semibold">Press Me</Text>
</TouchableHighlight>

The accent- Prefix Pattern

React Native components have props like color, tintColor, thumbColor that are NOT part of the style object. To set these via Tailwind classes, use the accent- prefix with the corresponding {propName}ClassName prop:

tsx
// color prop → colorClassName with accent- prefix
<ActivityIndicator colorClassName="accent-blue-500 dark:accent-blue-400" />
<Button colorClassName="accent-primary" title="Submit" />

// tintColor prop → tintColorClassName
<Image className="w-6 h-6" tintColorClassName="accent-red-500" source={icon} />

// thumbColor → thumbColorClassName
<Switch thumbColorClassName="accent-white" trackColorOnClassName="accent-primary" />

// placeholderTextColor → placeholderTextColorClassName
<TextInput placeholderTextColorClassName="accent-gray-400 dark:accent-gray-600" />

CRITICAL Rule: className maps to the style prop — it handles layout, typography, backgrounds, borders, etc. But React Native has many color props that live OUTSIDE of style (like color, tintColor, thumbColor, placeholderTextColor). These require a separate {propName}ClassName prop with the accent- prefix. Without accent-, the class resolves to a style object — but these props expect a plain color string.

tsx
// WRONG — className sets style, but ActivityIndicator's color is NOT a style prop
<ActivityIndicator className="text-blue-500" />  // color will NOT be set

// CORRECT — use the dedicated colorClassName prop with accent- prefix
<ActivityIndicator colorClassName="accent-blue-500" />  // color IS set to #3b82f6

// WRONG — tintColor is not a style prop on Image
<Image className="tint-blue-500" source={icon} />  // won't work

// CORRECT
<Image tintColorClassName="accent-blue-500" source={icon} />

Styling Third-Party Components

Wrap once at module level, use with className everywhere:

tsx
import { withUniwind } from 'uniwind';
import { Image as ExpoImage } from 'expo-image';
import { BlurView as RNBlurView } from 'expo-blur';
import { LinearGradient as RNLinearGradient } from 'expo-linear-gradient';

// Module-level wrapping (NEVER inside render functions)
export const Image = withUniwind(ExpoImage);
export const BlurView = withUniwind(RNBlurView);
export const LinearGradient = withUniwind(RNLinearGradient);

withUniwind automatically maps:

  • style → className
  • {name}Style → {name}ClassName
  • {name}Color → {name}ColorClassName (with accent- prefix)

For custom prop mappings:

tsx
const StyledProgressBar = withUniwind(ProgressBar, {
  width: {
    fromClassName: 'widthClassName',
    styleProperty: 'width',
  },
});

Usage patterns:

  • Used in one file only — define the wrapped component in that same file
  • Used across multiple files — wrap once in a shared module (e.g., components/styled.ts) and re-export
tsx
// components/styled.ts
import { withUniwind } from 'uniwind';
import { Image as ExpoImage } from 'expo-image';
export const Image = withUniwind(ExpoImage);

// Then import everywhere:
import { Image } from '@/components/styled';

NEVER call withUniwind on the same component in multiple files.

CRITICAL: Do NOT use withUniwind on components from react-native or react-native-reanimated. These already have built-in className support:

tsx
// WRONG — View already supports className natively
const StyledView = withUniwind(View);        // DO NOT DO THIS
const StyledText = withUniwind(Text);        // DO NOT DO THIS
const StyledAnimatedView = withUniwind(Animated.View); // DO NOT DO THIS

// CORRECT — only wrap third-party components
const StyledExpoImage = withUniwind(ExpoImage);     // expo-image
const StyledBlurView = withUniwind(BlurView);        // expo-blur
const StyledMotiView = withUniwind(MotiView);        // moti
useResolveClassNames

Converts Tailwind class strings to React Native style objects. Use for one-off cases or components that only accept style:

tsx
import { useResolveClassNames } from 'uniwind';

const headerStyle = useResolveClassNames('bg-primary p-4');
const cardStyle = useResolveClassNames('bg-card dark:bg-card rounded-lg shadow-sm');

// React Navigation screen options
<Stack.Navigator screenOptions={{ headerStyle, cardStyle }} />
Comparison
FeaturewithUniwinduseResolveClassNames
SetupOnce per componentPer usage
PerformanceOptimizedSlightly slower
Best forReusable componentsOne-off, navigation config
SyntaxclassName="..."style={...}

Dynamic ClassNames

NEVER do this (Tailwind scans at build time)
tsx
// BROKEN — template literal with variable
<View className={`bg-${color}-500`} />
<Text className={`text-${size}`} />
Correct patterns
tsx
// Ternary with complete class names
<View className={isActive ? 'bg-primary' : 'bg-muted'} />

// Mapping object
const colorMap = {
  primary: 'bg-blue-500 text-white',
  danger: 'bg-red-500 text-white',
  ghost: 'bg-transparent text-foreground',
};
<Pressable className={colorMap[variant]} />

// Array join for multiple conditions
<View className={[
  'p-4 rounded-lg',
  isActive && 'bg-primary',
  isDisabled && 'opacity-50',
].filter(Boolean).join(' ')} />

tailwind-variants (tv)

For complex component styling with variants and compound variants:

tsx
import { tv } from 'tailwind-variants';

const button = tv({
  base: 'font-semibold rounded-lg px-4 py-2 items-center justify-center',
  variants: {
    color: {
      primary: 'bg-blue-500 text-white',
      secondary: 'bg-gray-500 text-white',
      danger: 'bg-red-500 text-white',
      ghost: 'bg-transparent text-foreground border border-border',
    },
    size: {
      sm: 'text-sm px-3 py-1.5',
      md: 'text-base px-4 py-2',
      lg: 'text-lg px-6 py-3',
    },
    disabled: {
      true: 'opacity-50',
    },
  },
  compoundVariants: [
    { color: 'primary', size: 'lg', class: 'bg-blue-600' },
  ],
  defaultVariants: { color: 'primary', size: 'md' },
});

<Pressable className={button({ color: 'primary', size: 'lg' })}>
  <Text className="text-white font-semibold">Click</Text>
</Pressable>

cn Utility — Class Deduplication

Uniwind does NOT auto-deduplicate conflicting classNames. This means if the same property appears in multiple classes, both will be applied and the result is unpredictable. This is especially critical when mixing custom CSS classes with Tailwind utilities.

Setup
bash
npm install tailwind-merge clsx
ts
// lib/cn.ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}
When cn Is Required
  1. Merging className props — component accepts external className that may conflict:
tsx
import { cn } from '@/lib/cn';

<View className={cn('p-4 bg-white', props.className)} />
<Text className={cn('text-base', isActive && 'text-primary', disabled && 'opacity-50')} />
  1. CRITICAL: Mixing custom CSS classes with Tailwind utilities — if your custom CSS class sets a property that a Tailwind utility also sets, you MUST use cn() to deduplicate:
css
/* global.css */
.card {
  background-color: white;
  border-radius: 12px;
  padding: 16px;
}
tsx
// WRONG — both .card (padding: 16px) and p-6 (padding: 24px) apply, result is unpredictable
<View className="card p-6" />

// CORRECT — cn deduplicates, p-6 wins over .card's padding
<View className={cn('card', 'p-6')} />
  1. tv() output combined with extra classes — tv already handles its own variants, but if you add more classes on top:
tsx
<Pressable className={cn(button({ color: 'primary' }), props.className)} />
When cn Is NOT Needed
  • Static className with no conflicts: <View className="flex-1 p-4 bg-white" />
  • Single custom CSS class with no overlapping Tailwind: <View className="card-shadow mt-4" /> (if card-shadow only sets box-shadow which no Tailwind class also sets)

Important Utilities and Style Specificity

Uniwind supports Tailwind's important modifier (!) for utilities that must override another utility for the same style property.

tsx
import { View, Pressable } from 'react-native';

// bg-red-500! has higher priority than bg-blue-500
<View className="bg-blue-500 bg-red-500!" />;

// Important utilities work with state and platform variants
<Pressable className="bg-blue-500 active:bg-red-500!" />;
<View className="pt-4 ios:pt-12! android:pt-8!" />;

Priority rules:

  • Important utility (bg-red-500!) overrides non-important utility (bg-blue-500) for the same property.
  • Important variants work normally: active:bg-red-500!, ios:pt-12!, dark:text-white!.
  • Inline style always wins, even over important className utilities: <View className="bg-red-500!" style={{ backgroundColor: 'blue' }} /> renders blue.
  • Use ! sparingly. For reusable components and consumer overrides, prefer cn() with tailwind-merge.

Theming

Quick Setup (dark: prefix)

Works immediately — no configuration needed:

tsx
<View className="bg-white dark:bg-gray-900">
  <Text className="text-black dark:text-white">Themed</Text>
</View>

Best for small apps and prototyping. Does not scale to custom themes.

Scalable Setup (CSS Variables)

Define in global.css, use everywhere without dark: prefix:

css
@layer theme {
  :root {
    @variant light {
      --color-background: #ffffff;
      --color-foreground: #111827;
      --color-foreground-secondary: #6b7280;
      --color-card: #ffffff;
      --color-border: #e5e7eb;
      --color-muted: #9ca3af;
      --color-primary: #3b82f6;
      --color-danger: #ef4444;
      --color-success: #10b981;
    }
    @variant dark {
      --color-background: #000000;
      --color-foreground: #ffffff;
      --color-foreground-secondary: #9ca3af;
      --color-card: #1f2937;
      --color-border: #374151;
      --color-muted: #6b7280;
      --color-primary: #3b82f6;
      --color-danger: #ef4444;
      --color-success: #10b981;
    }
  }
}
tsx
// Auto-adapts to current theme — no dark: prefix needed
<View className="bg-card border border-border p-4 rounded-lg">
  <Text className="text-foreground text-lg font-bold">Title</Text>
  <Text className="text-muted mt-2">Subtitle</Text>
</View>

Variable naming: --color-background → bg-background, text-background.

Prefer CSS variables over explicit dark: variants — they're cleaner, maintain easier, and work with custom themes automatically.

Custom Themes

Step 1 — Define in global.css:

css
@layer theme {
  :root {
    @variant light { /* ... */ }
    @variant dark { /* ... */ }
    @variant ocean {
      --color-background: #0c4a6e;
      --color-foreground: #e0f2fe;
      --color-primary: #06b6d4;
      --color-card: #0e7490;
      --color-border: #155e75;
      /* Must define ALL the same variables as light/dark */
    }
  }
}

Step 2 — Register in metro.config.js (exclude light/dark — they're automatic):

js
module.exports = withUniwindConfig(config, {
  cssEntryFile: './global.css',
  extraThemes: ['ocean'],
});

Restart Metro after adding themes.

Step 3 — Use:

tsx
Uniwind.setTheme('ocean');
Theme API
tsx
import { Uniwind, useUniwind } from 'uniwind';

// Imperative (no re-render)
Uniwind.setTheme('dark');          // Force dark
Uniwind.setTheme('light');         // Force light
Uniwind.setTheme('system');        // Follow device (re-enables adaptive themes)
Uniwind.setTheme('ocean');         // Custom theme (must be in extraThemes)
Uniwind.currentTheme;              // Current theme name
Uniwind.hasAdaptiveThemes;         // true if following system

// Reactive hook (re-renders on change)
const { theme, hasAdaptiveThemes } = useUniwind();

Uniwind.setTheme('light') / setTheme('dark') also calls Appearance.setColorScheme to sync native components (Alert, Modal, system dialogs).

By default Uniwind uses "system" theme - follows device color scheme. If user wants to override it, just call Uniwind.setTheme with desired theme. It can be done above the React component to avoid theme switching at runtime.

Theme Switcher Example
tsx
import { View, Pressable, Text, ScrollView } from 'react-native';
import { Uniwind, useUniwind } from 'uniwind';

export const ThemeSwitcher = () => {
  const { theme, hasAdaptiveThemes } = useUniwind();
  const activeTheme = hasAdaptiveThemes ? 'system' : theme;

  const themes = [
    { name: 'light', label: 'Light' },
    { name: 'dark', label: 'Dark' },
    { name: 'system', label: 'System' },
  ];

  return (
    <ScrollView horizontal showsHorizontalScrollIndicator={false}>
      <View className="flex-row gap-2 p-4">
        {themes.map((t) => (
          <Pressable
            key={t.name}
            onPress={() => Uniwind.setTheme(t.name)}
            className={`px-4 py-3 rounded-lg items-center ${
              activeTheme === t.name ? 'bg-primary' : 'bg-card border border-border'
            }`}
          >
            <Text className={`text-sm ${
              activeTheme === t.name ? 'text-white' : 'text-foreground'
            }`}>
              {t.label}
            </Text>
          </Pressable>
        ))}
      </View>
    </ScrollView>
  );
};
ScopedTheme

Apply a different theme to a subtree without changing the global theme:

tsx
import { ScopedTheme } from 'uniwind';

<View className="gap-3">
  <PreviewCard />

  <ScopedTheme theme="dark">
    <PreviewCard />  {/* Renders with dark theme */}
  </ScopedTheme>

  <ScopedTheme theme="ocean">
    <PreviewCard />  {/* Renders with ocean theme */}
  </ScopedTheme>
</View>
  • Nearest ScopedTheme wins (nested scopes supported)
  • Hooks (useUniwind, useResolveClassNames, useCSSVariable) resolve against the nearest scoped theme
  • withUniwind-wrapped components inside the scope also resolve scoped theme values
  • Custom themes require registration in extraThemes
useCSSVariable

Access CSS variable values in JavaScript:

tsx
import { useCSSVariable } from 'uniwind';

const primaryColor = useCSSVariable('--color-primary');
const spacing = useCSSVariable('--spacing-4');

// Multiple variables at once
const [bg, fg] = useCSSVariable(['--color-background', '--color-foreground']) as [string, string]

Use for: animations, chart libraries, third-party component configs, calculations with design tokens.

It's required to cast the result of useCSSVariable as it can return: string | number | undefined. Uniwind doesn't know if given variable exist and what type it is, so it returns union type.

getCSSVariable

Read CSS variable values outside of React (event handlers, async callbacks, utility modules, worklets). Available in Uniwind 1.6.4+.

ts
import { Uniwind } from 'uniwind';

const primary = Uniwind.getCSSVariable('--color-primary');
const [bg, fg] = Uniwind.getCSSVariable(['--color-background', '--color-foreground']) as [string, string];

Same value rules as useCSSVariable (variable must be used in a className or declared in @theme static). Same return type: string | number | undefined. Cast as needed.

Not reactive — value is read once. For reactive values inside components use useCSSVariable. Use getCSSVariable for one-shot reads (onPress handlers, utility functions, native module configs).

Runtime CSS Variable Updates

Update theme variables at runtime (e.g., user-selected brand colors or API-driven themes):

tsx
Uniwind.updateCSSVariables('light', {
  '--color-primary': '#ff6600',
  '--color-background': '#fafafa',
});

Updates are theme-specific and take effect immediately.

@theme static

For JS-only values not used in classNames:

css
@theme static {
  --chart-line-width: 2;
  --chart-dot-radius: 4;
  --animation-duration: 300;
}

Access via useCSSVariable('--chart-line-width'). Use for: chart configs, animation durations, native module values.

OKLCH Colors support

Perceptually uniform color format — wider gamut, consistent lightness:

css
@layer theme {
  :root {
    @variant light {
      --color-primary: oklch(0.5 0.2 240);
      --color-background: oklch(1 0 0);
    }
    @variant dark {
      --color-primary: oklch(0.6 0.2 240);
      --color-background: oklch(0.13 0.004 17.69);
    }
  }
}
Display P3 Colors support

Wide-gamut color format for devices that support the P3 color space (most modern iPhones and Macs). Uniwind parses color(display-p3 ...) values and converts them for native use:

css
@layer theme {
  :root {
    @variant light {
      --color-primary: color(display-p3 0.2 0.4 1);
      --color-accent: color(display-p3 1 0.3 0.3);
    }
    @variant dark {
      --color-primary: color(display-p3 0.3 0.5 1);
      --color-accent: color(display-p3 1 0.4 0.4);
    }
  }
}

Platform Selectors

Apply platform-specific styles directly in className:

tsx
// Individual platforms
<View className="ios:bg-red-500 android:bg-blue-500 web:bg-green-500" />

// native: shorthand (iOS + Android)
<View className="native:bg-blue-500 web:bg-gray-500" />

// TV platforms
<View className="tv:p-8 android-tv:bg-black apple-tv:bg-gray-900" />

// Combine with other utilities
<View className="p-4 ios:pt-12 android:pt-6 web:pt-4" />

Platform variants in @layer theme for global values (use @variant, not @media):

css
@layer theme {
  :root {
    @variant ios { --font-sans: 'SF Pro Text'; }
    @variant android { --font-sans: 'Roboto-Regular'; }
    @variant web { --font-sans: 'Inter'; }
  }
}

Prefer platform selectors over Platform.select() — cleaner syntax, no imports needed.

Data Selectors

Style based on prop values using data-[prop=value]:utility:

tsx
// Boolean props
<Pressable
  data-selected={isSelected}
  className="border rounded px-3 py-2 data-[selected=true]:ring-2 data-[selected=true]:ring-primary"
/>

// String props
<View
  data-state={isOpen ? 'open' : 'closed'}
  className="p-4 data-[state=open]:bg-muted/50 data-[state=closed]:bg-transparent"
/>

// Tabs pattern
<Pressable
  data-selected={route.key === current}
  className="px-4 py-2 rounded-md text-foreground/60
    data-[selected=true]:bg-primary data-[selected=true]:text-white"
>
  <Text>{route.title}</Text>
</Pressable>

// Toggle pattern
<Pressable
  data-checked={enabled}
  className="h-6 w-10 rounded-full bg-muted data-[checked=true]:bg-primary"
>
  <View className="h-5 w-5 rounded-full bg-background translate-x-0 data-[checked=true]:translate-x-4" />
</Pressable>

Rules:

  • Only equality selectors supported (data-[prop=value])
  • No presence-only selectors (data-[prop] — not supported)
  • No has-data-* parent selectors (not supported in React Native)
  • Booleans match both boolean and string forms

Interactive States

tsx
// active: — when pressed
<Pressable className="bg-primary active:bg-primary/80 active:opacity-90 active:scale-95">
  <Text className="text-white">Press me</Text>
</Pressable>

// disabled: — when disabled prop is true
<Pressable
  disabled={isLoading}
  className="bg-primary disabled:bg-gray-300 disabled:opacity-50"
>
  <Text className="text-white disabled:text-gray-500">Submit</Text>
</Pressable>

// focus: — keyboard/accessibility focus
<TextInput
  className="border border-border rounded-lg px-4 py-2 focus:border-primary focus:ring-2 focus:ring-primary/20"
/>

<Pressable className="bg-card rounded-lg p-4 focus:ring-2 focus:ring-primary">
  <Text className="text-foreground">Focusable</Text>
</Pressable>

Components with state support:

  • Pressable: active:, disabled:, focus:
  • TextInput: active:, disabled:, focus:
  • Switch: disabled:
  • Text: active:, disabled:
  • TouchableOpacity / TouchableHighlight / TouchableNativeFeedback / TouchableWithoutFeedback: active:, disabled:

Responsive Breakpoints

Mobile-first — unprefixed styles apply to all sizes, prefixed styles apply at that breakpoint and above:

PrefixMin WidthTypical Device
(none)0pxAll (mobile)
sm:640pxLarge phones
md:768pxTablets
lg:1024pxLandscape tablets
xl:1280pxDesktops
2xl:1536pxLarge desktops
tsx
// Responsive padding and typography
<View className="p-4 sm:p-6 lg:p-8">
  <Text className="text-base sm:text-lg lg:text-xl font-bold">Responsive</Text>
</View>

// Responsive grid (1 col → 2 col → 3 col)
<View className="flex-row flex-wrap">
  <View className="w-full sm:w-1/2 lg:w-1/3 p-2">
    <View className="bg-card p-4 rounded"><Text>Item</Text></View>
  </View>
</View>

// Responsive visibility
<View className="hidden sm:flex flex-row gap-4">
  <Text>Visible on tablet+</Text>
</View>
<View className="flex sm:hidden">
  <Text>Mobile only</Text>
</View>

Custom breakpoints:

css
@theme {
  --breakpoint-xs: 480px;
  --breakpoint-tablet: 820px;
  --breakpoint-3xl: 1920px;
}

Usage: xs:p-2 tablet:p-4 3xl:p-8

Design mobile-first — start with base styles (no prefix), enhance with breakpoints:

tsx
// CORRECT — mobile-first
<View className="w-full sm:w-3/4 md:w-1/2 lg:w-1/3" />

// WRONG — desktop-first (reversed order is confusing and fragile)
<View className="w-full lg:w-1/2 md:w-3/4 sm:w-full" />

Safe Area Utilities

Padding
ClassDescription
p-safeAll sides
pt-safe / pb-safe / pl-safe / pr-safeIndividual sides
px-safe / py-safeHorizontal / vertical
Margin
ClassDescription
m-safeAll sides
mt-safe / mb-safe / ml-safe / mr-safeIndividual sides
mx-safe / my-safeHorizontal / vertical
Positioning
ClassDescription
inset-safeAll sides
top-safe / bottom-safe / left-safe / right-safeIndividual sides
x-safe / y-safeHorizontal / vertical inset
Compound Variants
PatternBehaviorExample
{prop}-safe-or-{value}Math.max(inset, value) — ensures minimum spacingpt-safe-or-4
{prop}-safe-offset-{value}inset + value — adds extra spacing on top of insetpb-safe-offset-4
Setup

Uniwind Free (default) — requires react-native-safe-area-context to update insets. Wrap your App component in SafeAreaProvider and SafeAreaListener and call Uniwind.updateInsets(insets) in the onChange callback:

tsx
import { SafeAreaProvider, SafeAreaListener } from 'react-native-safe-area-context';
import { Uniwind } from 'uniwind';

export default function App() {
  return (
    <SafeAreaProvider>
      <SafeAreaListener
        onChange={({ insets }) => {
          Uniwind.updateInsets(insets);
        }}
      >
        <View className="pt-safe px-safe">{/* content */}</View>
      </SafeAreaListener>
    </SafeAreaProvider>
  );
}

Uniwind Pro — automatic, no setup needed. Insets injected from native layer.

CSS Functions

Uniwind provides CSS functions for device-aware and theme-aware styling. These can be used everywhere (custom CSS classes, @utility, etc.) — but NOT inside @theme {} (which only accepts static values). Use @utility to create reusable Tailwind-style utility classes:

hairlineWidth()

Returns the thinnest line width displayable on the device. Use for subtle borders and dividers.

css
@utility h-hairline { height: hairlineWidth(); }
@utility border-hairline { border-width: hairlineWidth(); }
@utility w-hairline { width: calc(hairlineWidth() * 10); }
tsx
<View className="h-hairline bg-gray-300" />
<View className="border-hairline border-gray-200 rounded-lg p-4" />
fontScale(multiplier?)

Multiplies a base value by the device's font scale accessibility setting. Ensures text respects user preferences for larger or smaller text.

  • fontScale() — uses multiplier 1 (device font scale × 1)
  • fontScale(0.9) — smaller scale
  • fontScale(1.2) — larger scale
css
@utility text-sm-scaled { font-size: fontScale(0.9); }
@utility text-base-scaled { font-size: fontScale(); }
@utility text-lg-scaled { font-size: fontScale(1.2); }
tsx
<Text className="text-sm-scaled text-gray-600">Small accessible text</Text>
<Text className="text-base-scaled">Regular accessible text</Text>
pixelRatio(multiplier?)

Multiplies a value by the device's pixel ratio. Creates pixel-perfect designs that scale across screen densities.

  • pixelRatio() — uses multiplier 1 (device pixel ratio × 1)
  • pixelRatio(2) — double the pixel ratio
css
@utility w-icon { width: pixelRatio(); }
@utility w-avatar { width: pixelRatio(2); }
tsx
<Image source={{ uri: 'avatar.png' }} className="w-avatar rounded-full" />
light-dark(lightValue, darkValue)

Returns different values based on the current theme mode. Automatically adapts when the theme changes — no manual switching logic needed.

  • First parameter: value for light theme
  • Second parameter: value for dark theme
css
@utility bg-adaptive { background-color: light-dark(#ffffff, #1f2937); }
@utility text-adaptive { color: light-dark(#111827, #f9fafb); }
@utility border-adaptive { border-color: light-dark(#e5e7eb, #374151); }
tsx
<View className="bg-adaptive border-adaptive border rounded-lg p-4">
  <Text className="text-adaptive">Adapts to light/dark theme</Text>
</View>

Also works in custom CSS classes (not just @utility):

css
.adaptive-card {
  background-color: light-dark(#ffffff, #1f2937);
  color: light-dark(#111827, #f9fafb);
}

Custom CSS & Utilities

Custom CSS Classes

Uniwind supports custom CSS class names defined in global.css. They are compiled at build time — no runtime overhead. Use them when you need styles that are hard to express as Tailwind utilities (e.g., complex box-shadow, multi-property bundles).

css
/* global.css */
.card-shadow {
  background-color: white;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

.adaptive-surface {
  background-color: light-dark(#ffffff, #1f2937);
  color: light-dark(#111827, #f9fafb);
}

.container {
  flex: 1;
  width: 100%;
  max-width: 1200px;
}

Apply via className just like any Tailwind class:

tsx
<View className="card-shadow" />
Mixing Custom CSS with Tailwind

You can combine custom CSS classes with Tailwind utilities in a single className:

tsx
<View className="card-shadow p-4 m-2">
  <Text className="adaptive-surface mb-2">{title}</Text>
  <View className="container flex-row">{children}</View>
</View>

WARNING: If a custom CSS class and a Tailwind utility set the same property, you MUST use cn() to deduplicate. Without cn(), both values apply and the result is unpredictable:

tsx
// WRONG — .container sets flex:1, and flex-1 also sets flex:1 (harmless but wasteful)
// WRONG — .container sets width:100%, and w-full also sets width:100% (redundant)
// DANGEROUS — .card-shadow sets border-radius:12px, and rounded-2xl sets border-radius:16px — CONFLICT!
<View className="card-shadow rounded-2xl" />

// CORRECT — cn ensures rounded-2xl wins
import { cn } from '@/lib/cn';
<View className={cn('card-shadow', 'rounded-2xl')} />

Rule of thumb: If your custom CSS class sets properties that might overlap with Tailwind utilities you'll also use, always wrap with cn(). See cn Utility section for full setup.

Guidelines for Custom CSS
  • Keep selectors flat — no deep nesting or child selectors
  • Prefer Tailwind utilities for simple, single-property styles
  • Use custom classes for complex or multi-property bundles that would be verbose in className
  • Use light-dark() for theme-aware custom classes
  • Custom classes are great for shared design tokens that don't fit Tailwind's naming (e.g., .card, .chip, .badge-dot)
Custom Utilities (@utility)

The @utility directive creates utility classes that work exactly like built-in Tailwind classes. Three main use cases:

1. Variable-driven utilities (runtime-injected values)

Create a utility whose value comes from a CSS variable injected at runtime via updateCSSVariables. Use @theme static to declare the variable so Uniwind tracks it even before it is updated:

css
/* global.css */
@theme static {
  --header-height: 0px;
}

@utility p-safe-header {
  padding-top: var(--header-height);
}

Inject the real value at runtime (e.g., from react-navigation's layout event):

tsx
import { Uniwind } from 'uniwind'

// e.g., inside a navigation layout listener
Uniwind.updateCSSVariables(Uniwind.currentTheme, {
  '--header-height': headerHeight,
})
tsx
<View className="p-safe-header flex-1" />
2. Brand-new utilities (no Tailwind equivalent)

For styles that have no built-in Tailwind class:

css
@utility h-hairline { height: hairlineWidth(); }
@utility text-scaled { font-size: fontScale(); }
@utility card-shadow {
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}

Usage like any Tailwind class: <View className="h-hairline card-shadow" />

3. Overriding existing Tailwind utilities

Use @utility to completely replace what a built-in class does. Example: make border always use --color-primary:

css
@utility border {
  border-width: 1px;
  border-style: solid;
  border-color: var(--color-primary);
}

@theme Directive

Customize Tailwind design tokens in global.css:

css
@theme {
  /* Colors */
  --color-primary: #3b82f6;
  --color-brand-500: #3b82f6;
  --color-brand-900: #1e3a8a;

  /* Typography */
  --font-sans: 'Roboto-Regular';
  --font-sans-medium: 'Roboto-Medium';
  --font-sans-bold: 'Roboto-Bold';
  --font-mono: 'FiraCode-Regular';

  /* Spacing & sizing */
  --text-base: 15px;
  --spacing-4: 16px;
  --radius-lg: 12px;

  /* Breakpoints */
  --breakpoint-tablet: 820px;
}

Usage: bg-brand-500, text-brand-900, font-sans, font-mono, rounded-lg.

Fonts

React Native requires a single font per family — no fallbacks:

css
@theme {
  --font-sans: 'Roboto-Regular';
  --font-sans-bold: 'Roboto-Bold';
  --font-mono: 'FiraCode-Regular';
}

Font name must exactly match the font file name (without extension).

Expo: Configure fonts in app.json with the expo-font plugin, then reference in CSS.

Bare RN: Use react-native-asset to link fonts, same CSS config.

Platform-specific fonts (use @variant, not @media):

css
@layer theme {
  :root {
    @variant ios { --font-sans: 'SF Pro Text'; }
    @variant android { --font-sans: 'Roboto-Regular'; }
    @variant web { --font-sans: 'system-ui'; }
  }
}

Gradients

Built-in support — no extra dependencies:

tsx
<View className="bg-gradient-to-r from-red-500 via-yellow-500 to-green-500 p-4 rounded-lg">
  <Text className="text-white font-bold">Gradient</Text>
</View>

For expo-linear-gradient, you can wrap it with withUniwind for className-based layout and styling (padding, border-radius, flex, etc.), but the colors prop is an array that cannot be resolved via className — it must be provided explicitly. Use useCSSVariable to get theme-aware colors:

tsx
import { useCSSVariable } from 'uniwind';
import { withUniwind } from 'uniwind';
import { LinearGradient as RNLinearGradient } from 'expo-linear-gradient';

const LinearGradient = withUniwind(RNLinearGradient);

function GradientCard() {
  // useCSSVariable returns string | number | undefined
  const primary = useCSSVariable('--color-primary');
  const secondary = useCSSVariable('--color-secondary');

  // Guard against undefined — LinearGradient.colors requires valid ColorValues
  if (!primary || !secondary) {
    return null;
  }

  return (
    <LinearGradient
      className="flex-1 rounded-2xl p-6"
      colors={[primary as string, secondary as string]}
    >
      <Text className="text-white font-bold">Themed gradient</Text>
    </LinearGradient>
  );
}

Alternatively, export a wrapped component from a shared module for reuse:

tsx
// components/styled.ts
import { withUniwind } from 'uniwind';
import { LinearGradient as RNLinearGradient } from 'expo-linear-gradient';

export const LinearGradient = withUniwind(RNLinearGradient);
tsx
// usage — className handles layout, colors still passed manually
import { LinearGradient } from '@/components/styled';

<LinearGradient className="rounded-xl p-4" colors={['#ff6b6b', '#4ecdc4']}>
  <Text className="text-white">Static gradient</Text>
</LinearGradient>

React Navigation Integration

Use useResolveClassNames for screen options that only accept style objects:

tsx
import { useResolveClassNames } from 'uniwind';

function Layout() {
  const headerStyle = useResolveClassNames('bg-background');
  const headerTitleStyle = useResolveClassNames('text-foreground font-bold');

  return (
    <Stack.Navigator
      screenOptions={{
        headerStyle,
        headerTitleStyle,
      }}
    />
  );
}

Keep React Navigation's <ThemeProvider> if already in use — it manages navigation-specific theming.

UI Kit Compatibility

  • HeroUI Native: Works with Uniwind. Uses tailwind-variants (tv) internally. Apply className directly on HeroUI components. Bun users: Bun uses symlinks for node_modules, which can cause Tailwind's Oxide scanner to miss library classes in production builds. Fix: use the resolved path in @source and hoist the package:
    css
    @source "../../node_modules/heroui-native/lib";
    ini
    # .npmrc
    public-hoist-pattern[]=heroui-native
  • react-native-reusables: Compatible.
  • Gluestack v4.1+: Compatible.
  • Lucide React Native: Use withUniwind(LucideIcon) with colorClassName="accent-blue-500" for icon color. Works for all Lucide icons.
  • @shopify/flash-list: Use withUniwind(FlashList) for className and contentContainerClassName support. Note: withUniwind loses generic type params on ref — cast manually if needed.

Use semantic color tokens (bg-primary, text-foreground) for theme consistency across UI kits.

Supported vs Unsupported Classes

React Native uses the Yoga layout engine. Key differences from web CSS:

  • No CSS cascade/inheritance — styles don't inherit from parents
  • Flexbox by default — all views use flexbox with flexDirection: 'column'
  • Limited CSS properties — no floats, grid, pseudo-elements
Built-in Extra Utilities

Uniwind provides additional utility classes for React Native features not covered by standard Tailwind:

ClassEffect
border-continuousSets borderCurve: 'continuous' — smooth, superellipse corners (iOS)
border-circularSets borderCurve: 'circular' — standard circular corners (iOS default)
tsx
// Smooth iOS-style rounded corners (like SwiftUI's .continuous)
<View className="rounded-2xl border-continuous bg-card p-4">
  <Text className="text-foreground">Smooth corners</Text>
</View>
Supported (all standard Tailwind)

Layout, spacing, sizing, typography, colors, borders, effects, flexbox, positioning, transforms, interactive states.

Unsupported (web-specific, silently ignored)
  • hover: visited: — use Pressable active: instead
  • before: after: placeholder: — pseudo-elements
  • float-* clear-* columns-*
  • print: screen:
  • break-before-* break-after-* break-inside-*

Uniwind Pro

Paid upgrade with 100% API compatibility. Built on a 2nd-generation C++ engine for apps that demand the best performance. Graduated pricing (billed annually): $99/seat (1-3), $49 (4-6), $29 (7-15), $1 (16+). Pricing and licensing: https://uniwind.dev/pricing

Pricing & Licensing
  • Graduated per-seat pricing (billed annually, VAT excluded unless applicable): $99 for seats 1-3, $49 for 4-6, $29 for 7-15, $1 for 16+
  • Individual License: Personal Pro license per engineer
  • Team License: Single key management — add or remove members instantly
  • CI/CD License: Full support for automated and headless build environments
  • Enterprise: Custom plans available (contact support@uniwind.dev)
  • Priority Support: Critical issues resolved with priority response times
Overview
  • C++ style engine: Forged on the 2nd-gen Unistyles C++ engine. Injects styles directly into the ShadowTree without triggering React re-renders — a direct, optimized highway between classNames and the native layer
  • Performance: Benchmarked at ~55ms (vs StyleSheet 49ms, traditional Uniwind 81ms, NativeWind 197ms) — near-native speed
  • 55+ className props update without re-renders across 20 components (all component bindings listed above)
  • Reanimated animations: animate-* and transition-* via className (Reanimated v4)
  • Native insets & runtime values: Automatic safe area injection, device rotation, and font size updates — no SafeAreaListener setup needed
  • Theme transitions: Native animated transitions when switching themes (fade, slide, circle mask)
  • Group variants: Tailwind group-active:* / group-focus:* propagate parent interaction state through the C++ shadow tree with zero re-renders
  • Default styles: Experimental 1.2.0+ feature for styling default React Native components from CSS selectors like View { ... } and Text { ... }
  • Priority support: Don't let technical hurdles slow your team down

Package: "uniwind": "npm:uniwind-pro@latest" in package.json.

Installation
  1. Set dependency alias in package.json:

    json
    { "dependencies": { "uniwind": "npm:uniwind-pro@latest" } }
  2. Install peer dependencies:

    bash
    npm install react-native-nitro-modules react-native-reanimated react-native-worklets
  3. Authenticate: npx uniwind-pro (interactive — select "Login with GitHub")

  4. Add Babel plugin:

    js
    // babel.config.js
    module.exports = {
      presets: ['module:metro-react-native-babel-preset'],
      plugins: ['react-native-worklets/plugin'],
    };
  5. Whitelist postinstall if needed:

    • bun: Add "trustedDependencies": ["uniwind"] to package.json
    • yarn v2+: Configure in .yarnrc.yml
    • pnpm: pnpm config set enable-pre-post-scripts true
  6. Rebuild native app:

    bash
    npx expo prebuild --clean && npx expo run:ios

Pro does NOT work with Expo Go. Requires native rebuild.

Verify installation: Check for native modules (.cpp, .mm files) in node_modules/uniwind.

Show full SKILL.md (2,207 more words)Show less
Reanimated Animations (Requires Reanimated v4.0.0+)
tsx
<View className="size-32 bg-primary rounded animate-spin" />
<View className="size-32 bg-primary rounded animate-bounce" />
<View className="size-32 bg-primary rounded animate-pulse" />
<View className="size-32 bg-primary rounded animate-ping" />

// Loading spinner
<View className="size-8 border-4 border-gray-200 border-t-blue-500 rounded-full animate-spin" />

Custom keyframe animations beyond Tailwind defaults:

ClassDescription
animate-wiggleRotational wiggle
animate-shakeHorizontal shake
animate-flashOpacity flash on/off
animate-rubber-bandElastic scale stretch
animate-swingPendulum swing
animate-tadaScale + rotate attention seeker
animate-heartbeatDouble-pulse heartbeat
animate-jelloRotational jello wobble
animate-floatGentle vertical float
animate-breatheSubtle breathing scale
animate-tiltAlternating tilt rotation
animate-glitchRapid horizontal jitter

Components auto-swap to Animated versions when animation classes detected:

ComponentAnimated Version
ViewAnimated.View
TextAnimated.Text
ImageAnimated.Image
ImageBackgroundAnimated.ImageBackground
ScrollViewAnimated.ScrollView
FlatListAnimated.FlatList
TextInputAnimated.TextInput
PressableAnimated.Pressable
Entering & Exiting Animations

Drive Reanimated's entering/exiting animations via className — no Reanimated imports needed. Components auto-upgrade when uw-* classes are detected.

tsx
// Bounce in, bounce out
{visible && <View className="size-20 bg-primary rounded-xl uw-entering-bounce-in uw-exiting-bounce-out" />}

// Fade in slowly (1000ms)
{visible && <View className="size-20 bg-primary rounded-xl uw-entering-fade-in uw-entering-duration-1000 uw-exiting-fade-out" />}

Entering presets: uw-entering-fade-in uw-entering-fade-in-right uw-entering-fade-in-left uw-entering-fade-in-up uw-entering-fade-in-down uw-entering-slide-in-right uw-entering-slide-in-left uw-entering-slide-in-up uw-entering-slide-in-down uw-entering-zoom-in uw-entering-zoom-in-rotate uw-entering-zoom-in-left uw-entering-zoom-in-right uw-entering-zoom-in-up uw-entering-zoom-in-down uw-entering-zoom-in-easy-up uw-entering-zoom-in-easy-down uw-entering-bounce-in uw-entering-bounce-in-down uw-entering-bounce-in-up uw-entering-bounce-in-left uw-entering-bounce-in-right uw-entering-flip-in-x-up uw-entering-flip-in-x-down uw-entering-flip-in-y-left uw-entering-flip-in-y-right uw-entering-flip-in-easy-x uw-entering-flip-in-easy-y uw-entering-stretch-in-x uw-entering-stretch-in-y uw-entering-rotate-in-down-left uw-entering-rotate-in-down-right uw-entering-rotate-in-up-left uw-entering-rotate-in-up-right uw-entering-roll-in-left uw-entering-roll-in-right uw-entering-pinwheel-in uw-entering-light-speed-in-right uw-entering-light-speed-in-left

Exiting presets: uw-exiting-fade-out uw-exiting-fade-out-right uw-exiting-fade-out-left uw-exiting-fade-out-up uw-exiting-fade-out-down uw-exiting-slide-out-right uw-exiting-slide-out-left uw-exiting-slide-out-up uw-exiting-slide-out-down uw-exiting-zoom-out uw-exiting-zoom-out-rotate uw-exiting-zoom-out-left uw-exiting-zoom-out-right uw-exiting-zoom-out-up uw-exiting-zoom-out-down uw-exiting-zoom-out-easy-up uw-exiting-zoom-out-easy-down uw-exiting-bounce-out uw-exiting-bounce-out-down uw-exiting-bounce-out-up uw-exiting-bounce-out-left uw-exiting-bounce-out-right uw-exiting-flip-out-x-up uw-exiting-flip-out-x-down uw-exiting-flip-out-y-left uw-exiting-flip-out-y-right uw-exiting-flip-out-easy-x uw-exiting-flip-out-easy-y uw-exiting-stretch-out-x uw-exiting-stretch-out-y uw-exiting-rotate-out-down-left uw-exiting-rotate-out-down-right uw-exiting-rotate-out-up-left uw-exiting-rotate-out-up-right uw-exiting-roll-out-left uw-exiting-roll-out-right uw-exiting-pinwheel-out uw-exiting-light-speed-out-right uw-exiting-light-speed-out-left

Animation modifiers (pattern: uw-{entering|exiting|layout}-{modifier}):

  • Duration: uw-{type}-duration-75 uw-{type}-duration-100 ... uw-{type}-duration-1000 or arbitrary uw-{type}-duration-{ms}
  • Delay: uw-{type}-delay-75 ... uw-{type}-delay-1000 or arbitrary uw-{type}-delay-{ms}
  • Easing: uw-{type}-ease-linear uw-{type}-ease-in uw-{type}-ease-out uw-{type}-ease-in-out uw-{type}-ease-bounce
  • Spring: uw-{type}-springify uw-{type}-damping-{value} uw-{type}-stiffness-{value} uw-{type}-mass-{value}
Layout Transitions

Animate position/size changes when siblings are added or removed:

tsx
<View className="w-full gap-2">
  {items.map(item => (
    <View key={item.id} className={`h-14 ${item.color} rounded-xl uw-entering-fade-in uw-exiting-fade-out uw-layout-linear-transition`} />
  ))}
</View>
ClassDescription
uw-layout-linear-transitionSmooth linear repositioning
uw-layout-fading-transitionFade during repositioning
uw-layout-jumping-transitionBouncy jump to new position
uw-layout-curved-transitionCurved path repositioning
uw-layout-sequenced-transitionSequenced repositioning
uw-layout-entry-exit-transitionCombined entry/exit during layout
Transitions

Smooth property changes when className or state changes:

tsx
// Color transition on press
<Pressable className="bg-primary active:bg-red-500 transition-colors duration-300" />

// Opacity transition
<View className={`transition-opacity duration-500 ${visible ? 'opacity-100' : 'opacity-0'}`} />

// Transform transition
<Pressable className="active:scale-95 transition-transform duration-150" />

// All properties
<Pressable className="bg-primary px-6 py-3 rounded-lg active:scale-95 active:bg-primary/80 transition-all duration-150">
  <Text className="text-white font-semibold">Animated Button</Text>
</Pressable>
ClassProperties
transition-noneNo transition
transition-allAll properties
transition-colorsBackground, border, text colors
transition-opacityOpacity
transition-shadowBox shadow
transition-transformScale, rotate, translate

Duration: duration-75 duration-100 duration-150 duration-200 duration-300 duration-500 duration-700 duration-1000

Easing: ease-linear ease-in ease-out ease-in-out

Delay: delay-75 delay-100 delay-150 delay-200 delay-300 delay-500 delay-700 delay-1000

Using Reanimated Directly

Still works with Uniwind classes:

tsx
import Animated, { FadeIn, FlipInXUp, LinearTransition } from 'react-native-reanimated';

<Animated.Text entering={FadeIn.delay(500)} className="text-foreground text-lg">
  Fading in
</Animated.Text>

<Animated.FlatList
  data={data}
  className="flex-none"
  contentContainerClassName="px-2"
  layout={LinearTransition}
  renderItem={({ item }) => (
    <Animated.Text entering={FlipInXUp} className="text-foreground py-2">
      {item}
    </Animated.Text>
  )}
/>
Shadow Tree Updates

No code changes needed — props connect directly to C++ engine, eliminating re-renders automatically.

Group Variants

Tailwind group variants propagate parent interaction state to descendants through the C++ shadow tree. No re-renders, no context providers.

tsx
// Basic group — descendants react to parent press
<Pressable className="group p-4 bg-base rounded-xl">
  <Text className="text-default group-active:text-primary">Press the card</Text>
  <View className="size-8 bg-blue-500 rounded group-active:bg-red-500" />
</Pressable>

// Named groups — descendants pick which ancestor to follow
<Pressable className="group/card p-4 bg-base rounded-xl">
  <Pressable className="group/button px-3 py-1 bg-primary rounded">
    <Text className="text-white group-active/card:opacity-50 group-active/button:font-bold">
      Nested groups
    </Text>
  </Pressable>
</Pressable>

Supported variants: group-active:* (press), group-focus:* (focus). Named variants: group-active/{name}:*, group-focus/{name}:*.

Supported group parents: Pressable (press + focus), Text (press — requires onPress, even empty). TouchableOpacity, TouchableHighlight, TouchableWithoutFeedback, and TextInput do not act as group parents — wrap in a Pressable marked group.

Not supported: group-hover:* (no pointer hover on native), group-disabled:* (parsed but no shadow tree trigger), arbitrary group-[.selector]:* variants, implicit in-* variants.

Default Styles (Pro 1.2.0+, Experimental)

Default styles let Pro users define baseline styles for built-in React Native components directly in CSS. They are disabled by default and require an experimental flag.

js
// metro.config.js
module.exports = withUniwindConfig(config, {
  cssEntryFile: './global.css',
  experimental: {
    defaultStyles: true,
  },
});
css
/* global.css */
View {
  border-color: var(--color-primary);
}

Text {
  font-family: Inter;
  font-size: 16px;
}

Effect: every View gets border-color: var(--color-primary), every Text gets font-family: Inter and font-size: 16px, unless more specific styles override them.

Rules:

  • Available only in Uniwind Pro 1.2.0+
  • Disabled by default; enable experimental.defaultStyles: true
  • Experimental; may not work for every use case and may change in future releases
  • Use React Native component names as selectors, not HTML tags
  • Treat as baseline styles; direct className styles can override them

Supported component selectors: ActivityIndicator, FlatList, Image, ImageBackground, InputAccessoryView, KeyboardAvoidingView, Modal, Pressable, RefreshControl, SafeAreaView, ScrollView, SectionList, Switch, Text, TextInput, TouchableHighlight, TouchableNativeFeedback, TouchableOpacity, TouchableWithoutFeedback, View, VirtualizedList.

Suspense Support

Components inside React Suspense boundaries are handled correctly. While a subtree is suspended, Uniwind keeps the C++ shadow entries alive so theme updates and runtime changes (dark mode, orientation, etc.) still reach suspended nodes. When the tree unsuspends, styles are already up to date — no flash of stale theme.

Native Insets

Remove SafeAreaListener setup — insets injected from native layer:

tsx
// With Pro — just use safe area classes directly
<View className="pt-safe pb-safe">{/* content */}</View>
Theme Transitions (Pro)

Native animated transitions when switching themes. Supported on iOS, Android, and Web.

tsx
import { Uniwind, ThemeTransitionPreset } from 'uniwind';

// Fade transition
Uniwind.setTheme('dark', { preset: ThemeTransitionPreset.Fade });

// Slide transitions
Uniwind.setTheme('dark', { preset: ThemeTransitionPreset.SlideRightToLeft });
Uniwind.setTheme('light', { preset: ThemeTransitionPreset.SlideLeftToRight });

// Circle mask transitions (expand from a corner or center)
Uniwind.setTheme('ocean', { preset: ThemeTransitionPreset.CircleCenter });

// Blur transitions
Uniwind.setTheme('dark', { preset: ThemeTransitionPreset.Blur });
Uniwind.setTheme('dark', { preset: ThemeTransitionPreset.BlurRightToLeft });

// No animation
Uniwind.setTheme('light');

Available presets:

PresetEffect
ThemeTransitionPreset.NoneInstant switch, no animation
ThemeTransitionPreset.FadeCrossfade between themes
ThemeTransitionPreset.SlideRightToLeftSlide new theme in from right
ThemeTransitionPreset.SlideLeftToRightSlide new theme in from left
ThemeTransitionPreset.CircleTopRightCircle mask expanding from top-right
ThemeTransitionPreset.CircleTopLeftCircle mask expanding from top-left
ThemeTransitionPreset.CircleBottomRightCircle mask expanding from bottom-right
ThemeTransitionPreset.CircleBottomLeftCircle mask expanding from bottom-left
ThemeTransitionPreset.CircleCenterCircle mask expanding from center
ThemeTransitionPreset.BlurBlur out animation
ThemeTransitionPreset.BlurRightToLeftDirectional blur from right to left
ThemeTransitionPreset.BlurLeftToRightDirectional blur from left to right

Setup Diagnostics

When styles aren't working, check in this order:

1. package.json
  • "uniwind" (or "uniwind-pro") in dependencies
  • "tailwindcss" at v4+ (^4.0.0)
  • For Pro: react-native-nitro-modules, react-native-reanimated, react-native-worklets
2. metro.config.js
  • withUniwindConfig imported from 'uniwind/metro'
  • withUniwindConfig is the outermost wrapper
  • cssEntryFile is a relative path string (e.g., './global.css')
  • No path.resolve() or absolute paths
  • For Pro default styles: experimental.defaultStyles: true is set
3. global.css
  • Contains @import 'tailwindcss'; AND @import 'uniwind';
  • Imported in App.tsx or root layout, NOT in index.ts/index.js
  • Location determines app root for Tailwind scanning
4. babel.config.js (Pro only)
  • 'react-native-worklets/plugin' in plugins array
5. TypeScript
  • uniwind-types.d.ts exists (generated after running Metro)
  • Included in tsconfig.json or placed in src//app/ dir
6. Build
  • Metro server restarted after config changes
  • Metro cache cleared (npx expo start --clear or npx react-native start --reset-cache)
  • Native rebuild done (if Pro or after dependency changes)

Troubleshooting

SymptomCauseFix
Styles not applyingMissing imports in global.cssAdd @import 'tailwindcss'; @import 'uniwind';
Styles not applyingglobal.css imported in index.jsMove import to App.tsx or _layout.tsx
Classes not detectedglobal.css in nested dir, components elsewhereAdd @source '../components' in global.css
TypeScript errors on classNameMissing types fileRun Metro to generate uniwind-types.d.ts
withUniwindConfig is not a functionWrong importUse require('uniwind/metro') not require('uniwind')
Hot reload full-reloadsglobal.css imported in wrong fileMove to App.tsx or root layout
cssEntryFile error / Metro crashAbsolute path usedUse relative: './global.css'
withUniwindConfig not outermostAnother wrapper wraps UniwindSwap order so Uniwind is outermost
Dark theme not workingMissing @variant darkDefine dark variant in @layer theme
Custom theme not appearingNot registered in metro configAdd to extraThemes array, restart Metro
Fonts not loadingFont name mismatchCSS font name must match file name exactly (no extension)
rem values too large/smallWrong base remSet polyfills: { rem: 14 } for NativeWind compat
Unsupported CSS warningWeb-specific CSS usedEnable debug: true to identify; remove unsupported properties
Failed to serialize javascript objectComplex CSS, circular refs, or stale cacheClear caches: watchman watch-del-all; rm -rf node_modules/.cache; npx expo start --clear. Also check if docs/markdown files containing CSS classes are in the scan path (see below)
Failed to serialize javascript object from llms-full.txt or docsDocs/markdown files with CSS classes in project dir get scanned by TailwindMove .md files with CSS examples outside the project root, or add to .gitignore so Tailwind's scanner skips them
unstable_enablePackageExports conflictApp disables package exportsUse selective resolver for Uniwind and culori
Classes from monorepo package missingNot included in Tailwind scanAdd @source '../../packages/ui' in global.css
Classes from node_modules library missing in production (bun)Bun uses symlinks; Tailwind's Oxide scanner can't follow themUse resolved path: @source "../../node_modules/heroui-native/lib" and add public-hoist-pattern[]=heroui-native to .npmrc
active: not working with withUniwindwithUniwind does NOT support interactive state selectorsOnly core RN Pressable/TextInput/Switch support active:/focus:/disabled:. Third-party pressables wrapped with withUniwind won't get states
withUniwind custom mapping overrides className+style mergingWhen manual mapping is provided, style prop is not mergedUse auto mapping (no second arg) for className+style merge. For manual mapping + className, double-wrap: withUniwind(withUniwind(Comp), { mapping })
withUniwind loses generic types on ref (e.g., FlashList<T>)TypeScript limitation with HOCsCast the ref manually: ref={scrollRef as any}
Platform-specific fonts: @theme block error@media ios/android inside @theme {}Use @layer theme { :root { @variant ios { ... } } } instead — @theme only accepts custom properties, and platform selection uses @variant not @media
Uniwind.setTheme('system') crash on Android (RN 0.82+)RN 0.82 changed Appearance APIUpdate to latest Uniwind (fixed). Avoid setTheme('system') on older Uniwind + RN 0.82+
Styles flash/disappear on initial load (Android)SafeAreaListener fires before component listeners mountFixed in recent versions. If persists, ensure Uniwind is latest
useTVEventHandler is undefinedUniwind module replacement interferes with tvOS exportsFixed in v1.2.1+. Update Uniwind
@layer theme variables not rendering on webBug with RNW + Expo SDK 55Fixed in v1.4.1+. Update Uniwind
updateCSSVariables wrong theme at app startCalling for multiple themes back-to-back; last call wins on first renderCall updateCSSVariables for the current theme last. After initial load, order doesn't matter
Pro: animations not workingMissing Babel pluginAdd react-native-worklets/plugin to babel.config.js
Pro: module not foundNo native rebuildRun npx expo prebuild --clean then npx expo run:ios
Pro: postinstall failedPackage manager blocks scriptsAdd to trustedDependencies (bun) or configure yarn/pnpm
Pro: auth expiredLogin session expired (180-day lifetime)Run npx uniwind-pro, re-login
Pro: download limit reachedMonthly download limit hitCheck Pro dashboard, limits reset monthly
Pro: Uniwind.updateInsets called unnecessarilyPro injects insets nativelyUniwind.updateInsets is a no-op in Pro. Remove SafeAreaListener setup when using Pro
Pro: theme transition crashMissing ThemeTransitionPreset import or calling before app is readyImport from 'uniwind'. Ensure the app has fully mounted before calling setTheme with a transition
Pro: default component styles not applyingFeature disabled or unsupported selectorUse Uniwind Pro 1.2.0+, enable experimental.defaultStyles: true, restart Metro, and use supported RN component selectors like View or Text
unstable_enablePackageExports Selective Resolver

If your app disables unstable_enablePackageExports (common in crypto apps), use a selective resolver:

js
config.resolver.unstable_enablePackageExports = false;
config.resolver.resolveRequest = (context, moduleName, platform) => {
  if (['uniwind', 'culori'].some((prefix) => moduleName.startsWith(prefix))) {
    return context.resolveRequest(
      { ...context, unstable_enablePackageExports: true },
      moduleName,
      platform
    );
  }
  return context.resolveRequest(context, moduleName, platform);
};

FAQ

Where to put global.css in Expo Router? Project root. Import in app/_layout.tsx. If placed in app/, add @source for sibling dirs.

Does Uniwind work with Expo Go? Free: Yes. Pro: No — requires native rebuild (development builds).

Can I use tailwind.config.js? No. Uniwind uses Tailwind v4 — all config via @theme in global.css.

How to access CSS variables in JS? Inside components: useCSSVariable('--color-primary') (reactive). Outside React: Uniwind.getCSSVariable('--color-primary') (one-shot, 1.6.4+). For variables not used in classNames, define with @theme static.

Can I use Platform.select()? Yes, but prefer platform selectors (ios:pt-12 android:pt-6) — cleaner, no imports.

Next.js support? Not officially supported. Community plugin: uniwind-plugin-next. For Next.js, use standard Tailwind CSS.

Vite support? Yes, since v1.2.0. Use uniwind/vite plugin alongside @tailwindcss/vite.

Full app reloads on CSS changes? Metro can't hot-reload files with many providers. Move global.css import deeper in the component tree.

Style specificity? Important utilities like bg-red-500! override non-important utilities for the same property and work with variants (active:bg-red-500!, ios:pt-12!). Inline style always overrides className, even important utilities. Use className for static styles, inline only for truly dynamic values. Use cn() from tailwind-merge for component libraries where classNames may conflict.

How do I include custom fonts? Load font files (Expo: expo-font plugin in app.json; Bare RN: react-native-asset), then map in CSS: @theme { --font-sans: 'Roboto-Regular'; }. Font name must exactly match the file name. See the Fonts section above.

How can I style based on prop values? Use data selectors: data-[selected=true]:ring-2. Only equality checks supported. See the Data Selectors section above.

How can I use gradients? Built-in: bg-gradient-to-r from-red-500 to-green-500. Also supports angle-based (bg-linear-90) and arbitrary values (bg-linear-[45deg,#f00_0%,#00f_100%]). See the Gradients section above.

How does className deduplication work? Uniwind does NOT auto-deduplicate conflicting classNames. Use tailwind-merge with a cn() utility. See the cn Utility section above.

How to debug 'Failed to serialize javascript object'? Clear caches: watchman watch-del-all; rm -rf node_modules/.cache; npx expo start --clear. Enable debug: true in metro config to identify the problematic CSS pattern. See the Troubleshooting table above.

How do I enable safe area classNames? Free: Install react-native-safe-area-context, wrap root with SafeAreaListener, call Uniwind.updateInsets(insets). Pro: Automatic — no setup. Then use pt-safe, pb-safe, etc. See the Safe Area Utilities section above.

What UI kits work well with Uniwind? React Native Reusables (shadcn philosophy, copy-paste components) and HeroUI Native (complete library, optimized for Uniwind). Any library works via withUniwind wrapper. See the UI Kit Compatibility section above.

Can I scope a theme to a single component? Yes, use ScopedTheme: <ScopedTheme theme="dark"><Card /></ScopedTheme>. It forces a theme for the subtree without changing the global theme. See the Theming section.

Does active: work with react-native-gesture-handler Pressable? No. withUniwind does NOT support interactive state selectors (active:, focus:, disabled:). Only core RN Pressable, TextInput, and Switch support them. For RNGH components, use onPressIn/onPressOut with state.

Can I customize the default border color? Yes — use @utility border to override the class entirely:

css
@utility border {
  border-width: 1px;
  border-style: solid;
  border-color: var(--color-primary);
}

This completely replaces the built-in border behavior, so re-declare any properties you still need. Alternatively, use border border-gray-300 explicitly or define --color-border in @theme and use border-border.

Can I use platform-specific fonts in @theme {}? No. @theme {} only accepts custom properties. Use @layer theme { :root { @variant ios { --font-sans: '...'; } } } instead. Note: use @variant (not @media) for platform selection in CSS.

Does Uniwind work with ESM metro config (metro.config.mjs or metro.config.ts)? Yes since v1.3.0+. If you hit Class extends value is not a constructor, update Uniwind or use CommonJS metro.config.js.

Classes from node_modules work in dev but not production (bun)? Bun symlinks packages. Tailwind's Oxide scanner can't follow them. Use resolved @source paths and hoist the package. See UI Kit Compatibility for details.

How to clear all caches when debugging? watchman watch-del-all 2>/dev/null; rm -rf node_modules/.cache && npx expo start --clear. This clears Watchman, Babel/bundler caches, and Expo internal cache.

MCP Server

Uniwind provides an official MCP server for AI-assisted development. If the user's environment supports MCP, recommend setting it up: https://docs.uniwind.dev/mcp

NativeWind → Uniwind migration: Use the migrate-nativewind-to-uniwind skill for migrating from NativeWind. It handles package removal, config migration, Tailwind v4 upgrade, cssInterop removal, theme conversion, and all breaking changes. It is tested and production-ready.

IMPORTANT: Do NOT guess Uniwind APIs. If you are unsure about any Uniwind API, hook, component, or configuration option, fetch and verify against the official docs: https://docs.uniwind.dev/llms-full.txt

© CherryHQ, AGPL-3.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in .agents/skills/uniwind of CherryHQ/cherry-studio-app.

Open the folder on GitHubat commit fcfd953

Compare with similar skills

Uniwind next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Uniwind compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Uniwind this skillCherryHQ/cherry-studio-app4k—~20kAutomated safety check: WarnAGPL-3.0
Weapp Tailwindcss Setupweapp-tailwindcss/weapp-tailwindcss1.9k—~406Automated safety check: PassMIT
UniwindOhh-889/skyroc795—~1.4kAutomated safety check: PassMIT
Weapp Tailwindcss Lynxweapp-tailwindcss/weapp-tailwindcss1.9k—~374Automated safety check: PassMIT
Weapp Tailwindcss React Nativeweapp-tailwindcss/weapp-tailwindcss1.9k—~403Automated safety check: PassMIT
Migrate Nativewind To UniwindOhh-889/skyroc795—~6.1kAutomated safety check: PassMIT

Similar skills

  • Weapp Tailwindcss Setup

    weapp-tailwindcss/weapp-tailwindcss

    为新项目配置当前 weapp-tailwindcss v5 与 Tailwind CSS 4,覆盖 uni-app、uni-app x、Taro、Mpx、原生小程序、weapp-vite、Vite、Webpack 5、Rspack、Gulp、独立 CLI 及小程序/H5/Web/App 多端。Use when setting up, installing, integrating…

    1.9k GitHub stars~406 tokensUpdated today
    MobileAuto-check passed
  • Uniwind

    Ohh-889/skyroc

    Uniwind — Tailwind CSS v4 styling for React Native. An agent skill from Ohh-889/skyroc.

    795 GitHub stars~1.4k tokensUpdated 1 mo ago
    Frontend & DesignAuto-check passed
  • Weapp Tailwindcss Lynx

    weapp-tailwindcss/weapp-tailwindcss

    为 ReactLynx + Rspeedy 配置 @weapp-tailwindcss/lynx 0.3.2 与 Tailwind CSS 4,覆盖 Rspeedy plugin、Lynx 原生 CSS、静态 className、theme 变量静态化、任意值、encoder 警告和 iOS/Android 运行端验证。Use for ReactLynx, Rspeedy, Lynx…

    1.9k GitHub stars~374 tokensUpdated today
    MobileAuto-check passed
  • Weapp Tailwindcss React Native

    weapp-tailwindcss/weapp-tailwindcss

    为 Expo/React Native 配置 @weapp-tailwindcss/react-native 0.2.5 的 Tailwind CSS 4 编译器、Metro virtual module、Babel JSX transform、style manifest、tw runtime、dark/ios/android/native variants。Use for Expo SDK…

    1.9k GitHub stars~403 tokensUpdated today
    MobileAuto-check passed
  • Migrate a React Native project from NativeWind to Uniwind. An agent skill from Ohh-889/skyroc.

    795 GitHub stars~6.1k tokensUpdated 1 mo ago
    Frontend & DesignAuto-check passed
  • Design System

    WrongStack/WrongStack

    Use this skill BEFORE writing or restyling ANY user-facing interface.

    370 GitHub stars~3.9k tokensUpdated yesterday
    Frontend & DesignAuto-check passed

More from CherryHQ/cherry-studio-app

All 13 skills in this repo
  • Expo Cicd Workflows

    CherryHQ/cherry-studio-app

    Helps understand and write EAS workflow YAML files for Expo projects.

    4k GitHub starsUsed in 6 repos~870 tokens
    Auto-check passed
  • Native Data Fetching

    CherryHQ/cherry-studio-app

    A skill your agent uses when implementing or debugging ANY network request, API call, or data fetching.

    4k GitHub starsUsed in 6 repos~2.9k tokens
    Auto-check: notes
  • Building Native UI

    CherryHQ/cherry-studio-app

    Complete guide for building beautiful apps with Expo Router.

    4k GitHub starsUsed in 8 repos~2.6k tokens
    Auto-check passed
  • Expo Tailwind Setup

    CherryHQ/cherry-studio-app

    Set up Tailwind CSS v4 in Expo with react-native-css and NativeWind v5 for universal styling

    4k GitHub starsUsed in 8 repos~3k tokens
    Auto-check passed
  • Diagnose

    CherryHQ/cherry-studio-app

    Opt-in evidence-first causal diagnosis for bugs, browser or app/device failures, flaky behavior, and performance regressions.

    4k GitHub stars~1.8k tokensUpdated today
    Auto-check passed
  • Improve React

    CherryHQ/cherry-studio-app

    Survey a whole React codebase as a senior React engineer, using React Doctor's scan as evidence, then produce a prioritized audit and self-contained implementation plans for other agents (or cheaper…

    4k GitHub starsUsed in 3 repos~3k tokens
    Auto-check: warnings

Questions about Uniwind

What does Uniwind do?

Uniwind — Tailwind CSS v4 styling for React Native. An agent skill from CherryHQ/cherry-studio-app. Uniwind is an agent skill from CherryHQ/cherry-studio-app. Uniwind — Tailwind CSS v4 styling for React Native.

When should I use Uniwind?

Uniwind fits situations like: styling components in a React Native project that uses Tailwind with className; tasks that involve Cross-platform mobile apps; tasks that involve CSS and styling.

How do I install Uniwind in Claude Code?

Run `npx skills add CherryHQ/cherry-studio-app --skill uniwind -a claude-code`. Or copy the skill folder (.agents/skills/uniwind in CherryHQ/cherry-studio-app) into .claude/skills/uniwind in your project. Claude Code loads it when a task matches its description.

How do I install Uniwind in Codex?

Run `npx skills add CherryHQ/cherry-studio-app --skill uniwind -a codex`. Or copy the skill folder (.agents/skills/uniwind in CherryHQ/cherry-studio-app) into .agents/skills/uniwind in your project. Codex loads it when a task matches its description.

Can I use Uniwind in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add CherryHQ/cherry-studio-app --skill uniwind -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/uniwind, .gemini/skills/uniwind, .github/skills/uniwind and .opencode/skills/uniwind in your project.

What does Uniwind need to run?

Going by SKILL.md and its folder, Uniwind needs the command-line tools its instructions call (npx, npm, bun and pnpm).

Does Uniwind access the network?

SKILL.md names 2 domains. As links in the text: docs.uniwind.dev and uniwind.dev. This is read from the text; nothing was executed.

Is Uniwind safe to install?

Our automated static check of SKILL.md flagged 2 warning(s): mentions a credentials file (ssh keys, cloud or package-manager tokens). Read the flagged lines before installing; the check is not a guarantee either way.

What licence does Uniwind use?

Uniwind is published under the AGPL-3.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Uniwind use?

About 20k tokens (SKILL.md is roughly 81k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Uniwind?

Skills that share tags, products or a category with Uniwind: Weapp Tailwindcss Setup (weapp-tailwindcss/weapp-tailwindcss, 1.9k stars), Uniwind (Ohh-889/skyroc, 795 stars), Weapp Tailwindcss Lynx (weapp-tailwindcss/weapp-tailwindcss, 1.9k stars) and Weapp Tailwindcss React Native (weapp-tailwindcss/weapp-tailwindcss, 1.9k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Uniwind?

CherryHQ (a GitHub organization) maintains it in CherryHQ/cherry-studio-app, which has 3,974 GitHub stars. The repository holds 13 skills in this directory. The repository was last updated on October 8, 2026.

Source: CherryHQ/cherry-studio-app on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.