mcpField
U
uniwind

uniwind

github.com/hairyfデザイン・UI2641 回インストール2 週間前に更新
リポジトリ

uniwind について

Uniwindは、React NativeプロジェクトにTailwind CSS v4のスタイリングを導入するライブラリです。React NativeやExpoでアプリを開発する人向けで、ビルド時にスタイルをコンパイルし、実行時の処理を追加せずにコアコンポーネントで`className`を使えます。

主な機能

  1. 01React Native向けTailwind CSS v4ユーティリティ
  2. 02ビルド時のスタイルコンパイル
  3. 03コアコンポーネントでの`className`対応
  4. 04CSS変数によるテーマ設定
  5. 05MetroとViteの設定に対応

活用シーン

  1. 01ユーティリティクラスでReact Nativeコンポーネントをスタイリング
  2. 02アプリのコンポーネント間でTailwindスタイルを共有
  3. 03React Nativeインターフェースのテーマ設定

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/hairyf/skills --skill uniwind

インストール後、Claude Code で/uniwindを入力して読み込まれたか確認します。

同梱ファイルLICENSE.mdSYNC.md

詳細

Uniwind — 完全リファレンス

Uniwind 1.7.0+ / Uniwind Pro 1.2.1+ / Tailwind CSS v4 / React Native 0.81+ / Expo SDK 54+

ユーザーのバージョンがこれより古い場合は、最適な体験のために 1.7.0+(無料版)/1.2.1+(Pro)への更新をおすすめしてください。

Uniwind は Tailwind CSS v4 を React Native に導入します。すべての React Native のコアコンポーネントは、標準で className プロパティをサポートしています。スタイルはビルド時にコンパイルされるため、実行時のオーバーヘッドはありません。

重要なルール

  1. Tailwind v4 のみ — @tailwind base ではなく @import 'tailwindcss' を使用してください。Tailwind v3 はサポートされていません。
  2. className を動的に構築しないでください — Tailwind はビルド時にスキャンします。bg-${color}-500 は機能しません。完全な文字列リテラル、マッピングオブジェクト、または三項演算子を使用してください。
  3. cssInterop や remapProps は使用しないでください — これらは NativeWind の API です。Uniwind はグローバルコンポーネントをオーバーライドしません。
  4. tailwind.config.js は不要です — 設定はすべて @theme と @layer theme を使って global.css に記述します。
  5. ThemeProvider は不要です — Uniwind.setTheme() を直接使用してください。
  6. withUniwindConfig は Metro 設定の最も外側のラッパーにする必要があります。
  7. react-native または react-native-reanimated のコンポーネントを withUniwind でラップしないでください — View、Text、Pressable、Image、TextInput、ScrollView、FlatList、Switch、Modal、Animated.View、Animated.Text などは、すでに標準で完全な className サポートを備えています。これらを withUniwind でラップすると、動作に支障が出ます。withUniwind はサードパーティ製コンポーネント(例:expo-image、expo-blur、moti)にのみ使用してください。
  8. フォントファミリーは単一のフォントのみ — React Native はフォールバックに対応していません。'Roboto', sans-serif ではなく --font-sans: 'Roboto-Regular' を使用してください。
  9. すべてのテーマバリアントで、同じ CSS 変数を定義する必要があります — light で --color-primary を定義する場合、dark とすべてのカスタムテーマでも定義してください。変数が一致しないと、実行時エラーが発生します。
  10. スタイル以外の色プロパティには、accent- プレフィックスが必須です — これは非常に重要です。color(Button、ActivityIndicator)、tintColor(Image)、thumbColor(Switch)、placeholderTextColor(TextInput)などのプロパティは、style オブジェクトの一部ではありません。accent- を付けたクラスを使い、対応する {propName}ClassName プロパティを必ず指定してください。例:<ActivityIndicator colorClassName="accent-blue-500" />。<ActivityIndicator className="text-blue-500" /> は使用しないでください。通常の Tailwind の色クラス(text-blue-500 など)が機能するのは、style にマッピングされる className のみです。スタイル以外の色プロパティには、必ず accent- を使用してください。
  11. rem のデフォルト値は 16px です — NativeWind では 14px でした。移行する場合は、Metro 設定で polyfills: { rem: 14 } を設定してください。
  12. cssEntryFile は相対パス文字列にする必要があります — path.resolve(__dirname, 'global.css') ではなく './global.css' を使用してください。
  13. カスタム CSS クラスと Tailwind を組み合わせる場合は、cn() で重複を解消してください — Uniwind は重複を自動で解消しません。カスタム CSS クラス(.card { padding: 16px })と Tailwind ユーティリティ(p-6)が同じプロパティを設定すると、両方が適用され、結果が予測できなくなります。設定が重複する場合は、必ず cn('card', 'p-6') でラップしてください。
  14. important ユーティリティはサポートされています — Tailwind の important 修飾子は、末尾に ! を付けて className で使用できます:bg-red-500!、active:bg-red-500!、ios:pt-12!。先頭に ! を付ける !bg-red-500 形式は非推奨です。同じスタイルプロパティに対して、important ユーティリティは important でないユーティリティより優先されますが、インラインの style は引き続き className より優先されます。

セットアップ

インストール
# or other package manager
bun install uniwind tailwindcss

Tailwind CSS v4+ が必要です。

global.css

CSS エントリファイルを作成します。

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

App コンポーネント(例:App.tsx または app/_layout.tsx)でインポートしてください。index.ts/index.js ではインポートしないでください。そこにインポートすると、ホットリロードが機能しなくなります。

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

global.css を含むディレクトリがアプリのルートになります。Tailwind はこのディレクトリを起点に className をスキャンします。

Metro の設定
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
});

ほとんどのフローではデフォルト設定を維持し、cssEntryFile のみ指定してください。

ラッパーの順序 — Uniwind はほかのすべてをラップする必要があります。

// 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 の設定(v1.2.0+)

ストーリーブックのセットアップがある場合は、追加の Vite 設定を追加してください。

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

Metro の実行後、Uniwind は .d.ts ファイル(デフォルト:./uniwind-types.d.ts)を自動生成します。自動インクルードするには src/ または app/ に配置するか、tsconfig.json に追加します。

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

classNames に関連する TypeScript エラーが発生した場合は、Metro サーバーを実行して .d.ts ファイルを生成してください。

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

global.css が app/ ディレクトリにある場合は、兄弟ディレクトリ用に @source を追加します。

@import 'tailwindcss';
@import 'uniwind';
@source '../components';
Tailwind IntelliSense(VS Code / Cursor / Windsurf)
{
  "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"]
}
モノレポのサポート

CSS エントリーファイルのディレクトリ外にあるパッケージについて、global.css に @source ディレクティブを追加します。

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

Uniwind クラスを含む node_modules パッケージ(共有 UI ライブラリなど)にも必要です。

コンポーネントのバインディング

すべてのコア React Native コンポーネントは、追加設定なしで className をサポートします。一部のコンポーネントには、サブスタイル用の追加の className プロパティ(contentContainerClassName など)や、スタイル以外の色プロパティ(accent- プレフィックスが必要)があります。

完全なリファレンス

凡例:⚡ が付いたプロパティには accent- プレフィックスが必要です。括弧内のプロパティはプラットフォーム固有です。

View
プロパティ対応先プレフィックス
classNamestyle—
Text
プロパティ対応先プレフィックス
classNamestyle—
selectionColorClassNameselectionColor⚡ accent-
Pressable
プロパティ対応先プレフィックス
classNamestyle—

active:、disabled:、focus: の状態セレクターをサポートします。

Image
プロパティ対応先プレフィックス
classNamestyle—
tintColorClassNametintColor⚡ accent-
TextInput
プロパティ対応先プレフィックス
classNamestyle—
cursorColorClassNamecursorColor⚡ accent-
selectionColorClassNameselectionColor⚡ accent-
placeholderTextColorClassNameplaceholderTextColor⚡ accent-
selectionHandleColorClassNameselectionHandleColor⚡ accent-
underlineColorAndroidClassNameunderlineColorAndroid(Android)⚡ accent-

focus:、active:、disabled: の状態セレクターをサポートします。

ScrollView
プロパティ対応先プレフィックス
classNamestyle—
contentContainerClassNamecontentContainerStyle—
endFillColorClassNameendFillColor⚡ accent-
FlatList
プロパティ対応先プレフィックス
classNamestyle—
contentContainerClassNamecontentContainerStyle—
columnWrapperClassNamecolumnWrapperStyle—
ListHeaderComponentClassNameListHeaderComponentStyle—
ListFooterComponentClassNameListFooterComponentStyle—
endFillColorClassNameendFillColor⚡ accent-
SectionList
プロパティ対応先プレフィックス
classNamestyle—
contentContainerClassNamecontentContainerStyle—
ListHeaderComponentClassNameListHeaderComponentStyle—
ListFooterComponentClassNameListFooterComponentStyle—
endFillColorClassNameendFillColor⚡ accent-
VirtualizedList
プロパティ対応先プレフィックス
classNamestyle—
contentContainerClassNamecontentContainerStyle—
ListHeaderComponentClassNameListHeaderComponentStyle—
ListFooterComponentClassNameListFooterComponentStyle—
endFillColorClassNameendFillColor⚡ accent-
Switch
プロパティ対応先プレフィックス
thumbColorClassNamethumbColor⚡ accent-
trackColorOnClassNametrackColor.true(オン)⚡ accent-
trackColorOffClassNametrackColor.false(オフ)⚡ accent-
ios_backgroundColorClassNameios_backgroundColor(iOS)⚡ accent-

注:Switch は className をサポートしていません(型では className?: never)。上記の色専用の className プロパティのみを使用してください。disabled: 状態セレクターをサポートしています。

ActivityIndicator
プロパティ対応先プレフィックス
classNamestyle—
colorClassNamecolor⚡ accent-
Button
プロパティ対応先プレフィックス
colorClassNamecolor⚡ accent-

注:Button は className をサポートしていません(React Native の Button には style プロパティがありません)。

Modal
プロパティ対応先プレフィックス
classNamestyle—
backdropColorClassNamebackdropColor⚡ accent-
RefreshControl
プロパティ対応先プレフィックス
classNamestyle—
colorsClassNamecolors(Android)⚡ accent-
tintColorClassNametintColor(iOS)⚡ accent-
titleColorClassNametitleColor(iOS)⚡ accent-
progressBackgroundColorClassNameprogressBackgroundColor(Android)⚡ accent-
ImageBackground
プロパティ対応先プレフィックス
classNamestyle—
imageClassNameimageStyle—
tintColorClassNametintColor⚡ accent-
SafeAreaView
プロパティ対応先プレフィックス
classNamestyle—
KeyboardAvoidingView
プロパティ対応先プレフィックス
classNamestyle—
contentContainerClassNamecontentContainerStyle—
InputAccessoryView
プロパティ対応先プレフィックス
classNamestyle—
backgroundColorClassNamebackgroundColor⚡ accent-
TouchableHighlight
プロパティ対応先プレフィックス
classNamestyle—
underlayColorClassNameunderlayColor⚡ accent-

active:、disabled: 状態セレクターをサポートしています。

TouchableOpacity
プロパティ対応先プレフィックス
classNamestyle—

active:、disabled: 状態セレクターをサポートしています。

TouchableNativeFeedback
プロパティ対応先プレフィックス
classNamestyle—

active:、disabled: 状態セレクターをサポートしています。

TouchableWithoutFeedback
プロパティ対応先プレフィックス
classNamestyle—

active:、disabled: 状態セレクターをサポートしています。

使用例
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>

accent- プレフィックスのパターン

React Native のコンポーネントには、style オブジェクトの一部ではない color、tintColor、thumbColor などのプロパティがあります。これらを Tailwind クラスで設定するには、対応する {propName}ClassName プロパティで accent- プレフィックスを使用します。

// 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" />

重要なルール:className は style プロパティに対応し、レイアウト、タイポグラフィ、背景、境界線などを処理します。一方、React Native には style の外部にある色関連プロパティ(color、tintColor、thumbColor、placeholderTextColor など)が多数あります。これらには、accent- プレフィックスを付けた別の {propName}ClassName プロパティが必要です。accent- を付けない場合、クラスは style オブジェクトに解決されますが、これらのプロパティが受け取るのは通常の色文字列です。

// 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} />

サードパーティ製コンポーネントのスタイリング

withUniwind(推奨)

モジュールレベルで一度だけラップすれば、どこでも className を使用できます。

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 は次のように自動でマッピングします。

  • style → className
  • {name}Style → {name}ClassName
  • {name}Color → {name}ColorClassName(accent- プレフィックス付き)

カスタムのプロパティマッピングを行う場合:

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

使用パターン:

  • 1つのファイルでのみ使用する場合 — 同じファイル内でラップしたコンポーネントを定義します
  • 複数のファイルで使用する場合 — 共有モジュール(例:components/styled.ts)で一度だけラップし、再エクスポートします
// 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';

同じコンポーネントに対して複数のファイルで withUniwind を呼び出すことは絶対にしないでください。

重要: react-native または react-native-reanimated のコンポーネントに withUniwind を使用しないでください。これらはすでに className を標準でサポートしています。

// 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

Tailwind のクラス文字列を React Native のスタイルオブジェクトに変換します。単発のケースや、style のみを受け取るコンポーネントで使用します。

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 }} />
比較
機能withUniwinduseResolveClassNames
セットアップコンポーネントごとに一度使用するたび
パフォーマンス最適化済みやや低速
最適な用途再利用可能なコンポーネント単発の用途、ナビゲーション設定
構文className="..."style={...}

動的な ClassNames

絶対にしないでください(Tailwind はビルド時にスキャンします)
// BROKEN — template literal with variable
<View className={`bg-${color}-500`} />
<Text className={`text-${size}`} />
正しいパターン
// 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)

バリアントや複合バリアントを使った複雑なコンポーネントのスタイリングに使用します。

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 ユーティリティ — クラスの重複排除

Uniwind は競合する className を自動で重複排除しません。つまり、同じプロパティが複数のクラスに含まれる場合、両方が適用され、結果は予測できません。カスタム CSS クラスと Tailwind ユーティリティを組み合わせる場合は、特に注意が必要です。

セットアップ
npm install tailwind-merge clsx
// lib/cn.ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}
cn が必要な場合
  1. className プロパティのマージ — コンポーネントが、競合する可能性のある外部 className を受け取る場合:
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. 重要:カスタム CSS クラスと Tailwind ユーティリティの併用 — カスタム CSS クラスが設定するプロパティを Tailwind ユーティリティも設定する場合、重複排除のために cn() を必ず使用してください。
/* global.css */
.card {
  background-color: white;
  border-radius: 12px;
  padding: 16px;
}
// 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() の出力と追加クラスの組み合わせ — tv は独自のバリアントを処理しますが、さらにクラスを追加する場合:
<Pressable className={cn(button({ color: 'primary' }), props.className)} />
cn が不要な場合
  • 競合のない静的な className:<View className="flex-1 p-4 bg-white" />
  • Tailwind と重複しない単一のカスタム CSS クラス:<View className="card-shadow mt-4" />(card-shadow が box-shadow のみを設定し、それを設定する Tailwind クラスがない場合)

重要ユーティリティとスタイルの詳細度

Uniwind は、同じスタイルプロパティに対して別のユーティリティを上書きする必要がある場合に、Tailwind の important 修飾子(!)をサポートします。

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!" />;

優先順位のルール:

  • 重要ユーティリティ(bg-red-500!)は、同じプロパティに対して重要ではないユーティリティ(bg-blue-500)を上書きします。
  • 重要バリアントは通常どおり機能します:active:bg-red-500!、ios:pt-12!、dark:text-white!。
  • インライン style は、重要な className ユーティリティよりも常に優先されます:<View className="bg-red-500!" style={{ backgroundColor: 'blue' }} /> は青で描画されます。
  • ! は控えめに使用してください。再利用可能なコンポーネントや利用側での上書きには、tailwind-merge を使った cn() を推奨します。

テーマ設定

クイックセットアップ(dark: プレフィックス)

設定不要ですぐに動作します。

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

小規模なアプリやプロトタイピングに最適です。カスタムテーマには対応しきれません。

拡張可能なセットアップ(CSS 変数)

global.css で定義すれば、dark: プレフィックスなしでどこでも使用できます。

@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;
    }
  }
}
// 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>

変数の命名:--color-background → bg-background、text-background。

明示的な dark: バリアントより CSS 変数を優先してください。より簡潔で、保守しやすく、カスタムテーマにも自動で対応できます。

カスタムテーマ

ステップ 1 — global.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 */
    }
  }
}

ステップ 2 — metro.config.js に登録します(light/dark は自動のため除外します)。

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

テーマを追加した後は Metro を再起動してください。

ステップ 3 — 使用します。

Uniwind.setTheme('ocean');
テーマ API
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') は、ネイティブコンポーネント(Alert、Modal、システムダイアログ)を同期するために、Appearance.setColorScheme も呼び出します。

デフォルトでは、Uniwind は「system」テーマを使用し、デバイスのカラースキームに従います。テーマを上書きするには、希望するテーマを指定して Uniwind.setTheme を呼び出します。実行時のテーマ切り替えを避けるため、React コンポーネントの外部で呼び出すこともできます。

テーマ切り替えの例
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

グローバルテーマを変更せずに、サブツリーに別のテーマを適用します。

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>
  • 最も近い ScopedTheme が優先されます(スコープのネストに対応しています)
  • フック(useUniwind、useResolveClassNames、useCSSVariable)は、最も近いスコープのテーマを基準に解決します
  • スコープ内の withUniwind でラップされたコンポーネントも、スコープのテーマ値を解決します
  • カスタムテーマは extraThemes に登録する必要があります
useCSSVariable

JavaScript で CSS 変数の値にアクセスします。

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]

アニメーション、チャートライブラリ、サードパーティ製コンポーネントの設定、デザイントークンを使った計算に使用します。

useCSSVariable の結果は string | number | undefined を返す可能性があるため、型をキャストする必要があります。 Uniwind は指定された変数が存在するかどうかや、その型を把握していないため、ユニオン型を返します。

getCSSVariable

React の外部(イベントハンドラー、非同期コールバック、ユーティリティモジュール、ワークレット)で CSS 変数の値を読み取ります。Uniwind 1.6.4+ で利用できます。

import { Uniwind } from 'uniwind';

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

値に関する規則は useCSSVariable と同じです(変数は className で使用するか、@theme static で宣言する必要があります)。戻り値の型も同じく string | number | undefined です。必要に応じてキャストしてください。

リアクティブではなく、値は一度だけ読み取られます。コンポーネント内でリアクティブな値を使う場合は useCSSVariable を使用します。getCSSVariable は、単発の読み取り(onPress ハンドラー、ユーティリティ関数、ネイティブモジュールの設定)に使用してください。

実行時の CSS 変数の更新

実行時にテーマ変数を更新します(例:ユーザーが選択したブランドカラーや、API によって決まるテーマ)。

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

更新はテーマごとに適用され、すぐに反映されます。

@theme static

classNames では使用しない、JavaScript 専用の値に使用します。

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

useCSSVariable('--chart-line-width') でアクセスします。チャートの設定、アニメーションの時間、ネイティブモジュールの値に使用します。

OKLCH カラーのサポート

知覚的に均一なカラー形式です。より広い色域と、一貫した明度を実現します。

@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 カラーのサポート

P3 色空間に対応したデバイス(最新の iPhone や Mac のほとんど)向けの広色域カラー形式です。Uniwind は color(display-p3 ...) の値を解析し、ネイティブで使用できるように変換します。

@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);
    }
  }
}

プラットフォームセレクター

className でプラットフォーム固有のスタイルを直接適用します。

// 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" />

グローバル値には、@layer theme 内でプラットフォームバリアントを使用します(@media ではなく @variant を使用してください)。

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

Platform.select() よりプラットフォームセレクターを優先してください。構文がすっきりし、インポートも不要です。

データセレクター

data-[prop=value]:utility を使って、プロパティの値に基づいてスタイルを設定します。

// 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>

ルール:

  • 等価セレクター(data-[prop=value])のみサポートされます
  • 存在のみを確認するセレクター(data-[prop])はサポートされていません
  • has-data-* 親セレクターは React Native ではサポートされていません
  • ブール値は、ブール値形式と文字列形式の両方に一致します

インタラクティブ状態

// 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>

状態に対応するコンポーネント:

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

レスポンシブブレークポイント

モバイルファーストです。プレフィックスなしのスタイルはすべてのサイズに適用され、プレフィックス付きのスタイルはそのブレークポイント以上で適用されます。

PrefixMin WidthTypical Device
(none)0pxすべて(モバイル)
sm:640px大型スマートフォン
md:768pxタブレット
lg:1024px横向きのタブレット
xl:1280pxデスクトップ
2xl:1536px大型デスクトップ
// 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>

カスタムブレークポイント:

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

使用例:xs:p-2 tablet:p-4 3xl:p-8

モバイルファーストで設計します — ベーススタイル(プレフィックスなし)から始め、ブレークポイントで拡張します。

// 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" />

セーフエリアユーティリティ

パディング
ClassDescription
p-safe全辺
pt-safe / pb-safe / pl-safe / pr-safe個別の辺
px-safe / py-safe水平方向/垂直方向
マージン
ClassDescription
m-safe全辺
mt-safe / mb-safe / ml-safe / mr-safe個別の辺
mx-safe / my-safe水平方向/垂直方向
位置指定
ClassDescription
inset-safe全辺
top-safe / bottom-safe / left-safe / right-safe個別の辺
x-safe / y-safe水平方向/垂直方向のインセット
複合バリアント
PatternBehaviorExample
{prop}-safe-or-{value}Math.max(inset, value) — 最小間隔を確保しますpt-safe-or-4
{prop}-safe-offset-{value}inset + value — インセットに間隔を追加しますpb-safe-offset-4
セットアップ

Uniwind Free(デフォルト) — インセットを更新するには、react-native-safe-area-context が必要です。 App コンポーネントを SafeAreaProvider と SafeAreaListener でラップし、onChange コールバックで Uniwind.updateInsets(insets) を呼び出します。

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 — 自動で適用されるため、セットアップは不要です。インセットはネイティブレイヤーから注入されます。

CSS 関数

Uniwind は、デバイスやテーマに応じたスタイル設定のための CSS 関数を提供します。これらはどこでも(カスタム CSS クラス、@utility など)使用できます。ただし、@theme {} 内では使用できません(静的な値のみ受け付けます)。再利用可能な Tailwind スタイルのユーティリティクラスを作成するには、@utility を使用します。

hairlineWidth()

デバイスで表示可能な最も細い線幅を返します。控えめなボーダーや区切り線に使用します。

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

デバイスのフォントスケールのアクセシビリティ設定に基づいて、基準値に倍率を掛けます。ユーザーが設定した大きめ、または小さめのテキストサイズを反映します。

  • fontScale() — 倍率 1 を使用します(デバイスのフォントスケール × 1)
  • fontScale(0.9) — 小さめのスケール
  • fontScale(1.2) — 大きめのスケール
@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); }
<Text className="text-sm-scaled text-gray-600">Small accessible text</Text>
<Text className="text-base-scaled">Regular accessible text</Text>
pixelRatio(multiplier?)

デバイスのピクセル比に基づいて、値に倍率を掛けます。画面密度に応じて拡大縮小する、ピクセル単位で正確なデザインを作成できます。

  • pixelRatio() — 倍率 1 を使用します(デバイスのピクセル比 × 1)
  • pixelRatio(2) — ピクセル比を 2 倍にします
@utility w-icon { width: pixelRatio(); }
@utility w-avatar { width: pixelRatio(2); }
<Image source={{ uri: 'avatar.png' }} className="w-avatar rounded-full" />
light-dark(lightValue, darkValue)

現在のテーマモードに応じて異なる値を返します。テーマが変わると自動的に切り替わるため、手動で切り替えるロジックは不要です。

  • 第 1 引数:ライトテーマ用の値
  • 第 2 引数:ダークテーマ用の値
@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); }
<View className="bg-adaptive border-adaptive border rounded-lg p-4">
  <Text className="text-adaptive">Adapts to light/dark theme</Text>
</View>

カスタム CSS クラスでも動作します(@utility 内だけではありません)。

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

カスタム CSS とユーティリティ

カスタム CSS クラス

Uniwind は、global.css で定義されたカスタム CSS クラス名をサポートします。これらはビルド時にコンパイルされるため、実行時のオーバーヘッドはありません。Tailwind ユーティリティでは表現しにくいスタイル(複雑なボックスシャドウや、複数のプロパティをまとめたスタイルなど)に使用します。

/* 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;
}

他の Tailwind クラスと同様に、className で適用します。

<View className="card-shadow" />
カスタム CSS と Tailwind の併用

1 つの className で、カスタム CSS クラスと Tailwind ユーティリティを組み合わせられます。

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

警告:カスタム CSS クラスと Tailwind ユーティリティが同じプロパティを設定する場合は、重複を解消するために必ず cn() を使用してください。cn() を使用しないと、両方の値が適用され、結果が予測できなくなります。

// 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')} />

経験則:カスタム CSS クラスが、併用する Tailwind ユーティリティと重複する可能性のあるプロパティを設定する場合は、必ず cn() で囲んでください。設定方法の詳細は、cn ユーティリティのセクションを参照してください。

カスタム CSS のガイドライン
  • セレクターはフラットに保ち、深いネストや子セレクターは使用しないでください
  • 単一プロパティのシンプルなスタイルには、Tailwind ユーティリティを優先してください
  • className で冗長になる複雑なスタイルや複数プロパティのまとまりには、カスタムクラスを使用してください
  • テーマに対応するカスタムクラスには、light-dark() を使用してください
  • カスタムクラスは、Tailwind の命名規則に合わない共通のデザイントークン(例:.card、.chip、.badge-dot)に最適です
カスタムユーティリティ(@utility)

@utility ディレクティブを使うと、組み込みの Tailwind クラスとまったく同じように機能するユーティリティクラスを作成できます。主な用途は 3 つあります。

1. 変数駆動型ユーティリティ(実行時に値を注入)

updateCSSVariables を使って実行時に注入する CSS 変数から値を取得するユーティリティを作成します。変数が更新される前から Uniwind が追跡できるように、@theme static を使って変数を宣言します。

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

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

実際の値は実行時に注入します(例:react-navigation のレイアウトイベントから)。

import { Uniwind } from 'uniwind'

// e.g., inside a navigation layout listener
Uniwind.updateCSSVariables(Uniwind.currentTheme, {
  '--header-height': headerHeight,
})
<View className="p-safe-header flex-1" />
2. 新しいユーティリティ(Tailwind に相当するものがない場合)

組み込みの Tailwind クラスがないスタイルに使用します。

@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);
}

ほかの Tailwind クラスと同様に使用できます:<View className="h-hairline card-shadow" />

3. 既存の Tailwind ユーティリティを上書き

@utility を使って、組み込みクラスの動作を完全に置き換えられます。例:border が常に --color-primary を使用するようにします。

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

@theme ディレクティブ

global.css で Tailwind のデザイントークンをカスタマイズします。

@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;
}

使用例:bg-brand-500、text-brand-900、font-sans、font-mono、rounded-lg。

フォント

React Native では、ファミリーごとに単一のフォントが必要です。フォールバックは使用できません。

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

フォント名は、拡張子を除いたフォントファイル名と完全に一致している必要があります。

Expo:expo-font プラグインを使って app.json でフォントを設定し、CSS で参照します。

Bare RN:react-native-asset を使ってフォントをリンクし、同じ CSS 設定を使用します。

プラットフォーム固有のフォント(@media ではなく @variant を使用):

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

グラデーション

組み込みでサポートされているため、追加の依存関係は不要です。

<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>

expo-linear-gradient の場合は、withUniwind でラップすると、className ベースのレイアウトやスタイリング(パディング、border-radius、flex など)を適用できます。ただし、colors プロパティは配列であり、className では解決できないため、明示的に指定する必要があります。テーマに対応する色を取得するには、useCSSVariable を使用してください。

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>
  );
}

または、再利用できるように、共有モジュールからラップ済みコンポーネントをエクスポートします。

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

export const LinearGradient = withUniwind(RNLinearGradient);
// 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 との統合

style オブジェクトのみを受け付ける画面オプションには、useResolveClassNames を使用します。

import { useResolveClassNames } from 'uniwind';

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

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

すでに React Navigation の <ThemeProvider> を使用している場合は、そのまま使用してください。ナビゲーション固有のテーマ設定を管理します。

UI キットとの互換性

  • HeroUI Native:Uniwind と連携します。内部では tailwind-variants(tv)を使用しています。HeroUI コンポーネントに className を直接適用してください。Bun ユーザー向け:Bun は node_modules にシンボリックリンクを使用するため、本番ビルドで Tailwind の Oxide スキャナーがライブラリのクラスを検出できないことがあります。対処法:解決済みのパスを @source で使用し、パッケージをホイストします。
    @source "../../node_modules/heroui-native/lib";
    
    # .npmrc
    public-hoist-pattern[]=heroui-native
    
  • react-native-reusables:互換性があります。
  • Gluestack v4.1+:互換性があります。
  • Lucide React Native:アイコンの色には、colorClassName="accent-blue-500" を指定して withUniwind(LucideIcon) を使用します。すべての Lucide アイコンで機能します。
  • @shopify/flash-list:className と contentContainerClassName をサポートするには、withUniwind(FlashList) を使用します。注:withUniwind は ref のジェネリック型パラメーターを引き継ぎません。必要に応じて、手動でキャストしてください。

UI キット間で一貫したテーマを保つには、セマンティックなカラートークン(bg-primary、text-foreground)を使用してください。

サポート対象クラスと非サポートクラス

React Native は Yoga レイアウトエンジンを使用します。Web CSS との主な違いは次のとおりです。

  • CSS のカスケード/継承はありません — スタイルは親から継承されません
  • デフォルトで Flexbox — すべてのビューで flexDirection: 'column' の Flexbox が使用されます
  • CSS プロパティは限定的です — float、grid、疑似要素は使用できません
組み込みの追加ユーティリティ

Uniwind は、標準の Tailwind では対応していない React Native の機能向けに、追加のユーティリティクラスを提供します。

クラス効果
border-continuousborderCurve: 'continuous' を設定します — 滑らかなスーパー楕円形の角(iOS)
border-circularborderCurve: 'circular' を設定します — 標準的な円形の角(iOS のデフォルト)
// 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>
サポート対象(標準の Tailwind 全般)

レイアウト、余白、サイズ、タイポグラフィ、色、ボーダー、エフェクト、Flexbox、配置、変形、インタラクティブな状態。

非サポート(Web 固有のため、無視されます)
  • hover: visited: — 代わりに Pressable の active: を使用します
  • before: after: placeholder: — 疑似要素
  • float-* clear-* columns-*
  • print: screen:
  • break-before-* break-after-* break-inside-*

Uniwind Pro

API 互換性 100% の有料アップグレードです。最高のパフォーマンスを求めるアプリ向けに、第 2 世代の C++ エンジンを基盤としています。段階制料金(年払い):$99/シート(1~3)、$49(4~6)、$29(7~15)、$1(16 以上)。料金とライセンス:https://uniwind.dev/pricing

料金とライセンス
  • 段階制のシート単価(年払い。該当する場合を除き VAT は別途):シート 1~3 は $99、4~6 は $49、7~15 は $29、16 以上は $1
  • 個人ライセンス:エンジニアごとの個人用 Pro ライセンス
  • チームライセンス:一元的なキー管理 — メンバーをすぐに追加または削除できます
  • CI/CD ライセンス:自動ビルド環境とヘッドレスビルド環境を全面的にサポートします
  • エンタープライズ:カスタムプランをご利用いただけます([email protected] までお問い合わせください)
  • 優先サポート:重大な問題には優先対応します
概要
  • C++ スタイルエンジン:第 2 世代 Unistyles C++ エンジンを基盤としています。React の再レンダリングを発生させずにスタイルを ShadowTree に直接注入し、classNames とネイティブ層を最適化された直通経路で結びます
  • パフォーマンス:ベンチマーク結果は約 55ms(StyleSheet は 49ms、従来の Uniwind は 81ms、NativeWind は 197ms)で、ネイティブに近い速度です
  • 20 個のコンポーネントで、55 個以上の className props を再レンダリングなしで更新できます(上記に挙げたすべてのコンポーネントバインディング)
  • Reanimated アニメーション:className を介して animate-* と transition-* を使用できます(Reanimated v4)
  • ネイティブのインセットと実行時の値:セーフエリア、デバイスの回転、フォントサイズの更新を自動で反映します — SafeAreaListener の設定は不要です
  • テーマの遷移:テーマ切り替え時にネイティブのアニメーション付き遷移を行います(フェード、スライド、円形マスク)
  • グループバリアント:Tailwind の group-active:*/group-focus:* は、再レンダリングを発生させずに、親のインタラクション状態を C++ のシャドウツリー経由で伝播します
  • デフォルトスタイル:CSS セレクター View { ... } や Text { ... } からデフォルトの React Native コンポーネントをスタイリングできる、実験的な 1.2.0+ の機能です
  • 優先サポート:技術的な課題によってチームの進行が妨げられないようにします

package.json のパッケージ指定:"uniwind": "npm:uniwind-pro@latest"。

インストール
  1. package.jsonで依存関係のエイリアスを設定します:

    { "dependencies": { "uniwind": "npm:uniwind-pro@latest" } }
    
  2. ピア依存関係をインストールします:

    npm install react-native-nitro-modules react-native-reanimated react-native-worklets
    
  3. 認証します:npx uniwind-pro(対話形式で「Login with GitHub」を選択します)

  4. Babel プラグインを追加します — 既存のplugins配列にreact-native-worklets/pluginを追加してください。プリセットは置き換えないでください(Expo ではbabel-preset-expoを、bare-RN では既存のプリセットを維持します):

    // babel.config.js
    module.exports = {
      // your existing presets and config — leave untouched
      plugins: [
        // ...other plugins
        'react-native-worklets/plugin',
      ],
    };
    

    すでに Reanimated を使用している場合は、worklets プラグインが設定済みの可能性があります。

  5. 必要に応じて postinstall を許可リストに追加します:

    • bun:package.jsonに"trustedDependencies": ["uniwind"]を追加します
    • yarn v2+:.yarnrc.ymlで設定します
    • pnpm:pnpm config set enable-pre-post-scripts true
  6. ネイティブアプリを再ビルドします:

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

Pro は Expo Go では動作しません。ネイティブの再ビルドが必要です。

インストールを確認する:node_modules/uniwind内のネイティブモジュール(.cpp、.mmファイル)を確認します。

Reanimated アニメーション(Reanimated v4.0.0 以降が必要)
<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" />

Tailwind のデフォルトを超えるカスタムキーフレームアニメーション:

クラス説明
animate-wiggle回転する小刻みな揺れ
animate-shake水平方向の揺れ
animate-flash不透明度が点滅
animate-rubber-band弾むように伸縮
animate-swing振り子のような揺れ
animate-tada注目を引く拡大と回転
animate-heartbeat2回の脈動を繰り返す鼓動
animate-jello回転しながらゼリーのように揺れる
animate-floatゆるやかに上下に浮遊
animate-breatheさりげない呼吸のような拡大縮小
animate-tilt左右に交互に傾く回転
animate-glitch素早い水平方向の揺れ

アニメーションクラスが検出されると、コンポーネントは自動的に Animated バージョンに切り替わります:

コンポーネントAnimated バージョン
ViewAnimated.View
TextAnimated.Text
ImageAnimated.Image
ImageBackgroundAnimated.ImageBackground
ScrollViewAnimated.ScrollView
FlatListAnimated.FlatList
TextInputAnimated.TextInput
PressableAnimated.Pressable
Entering と Exiting のアニメーション

className を使って Reanimated の Entering/Exiting アニメーションを適用できます。Reanimated のインポートは不要です。uw-* クラスが検出されると、コンポーネントは自動的にアップグレードされます。

// 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 プリセット: 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 プリセット: 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

アニメーション修飾子(パターン:uw-{entering|exiting|layout}-{modifier}):

  • Duration:uw-{type}-duration-75 uw-{type}-duration-100 ... uw-{type}-duration-1000 または任意の uw-{type}-duration-{ms}
  • Delay:uw-{type}-delay-75 ... uw-{type}-delay-1000 または任意の 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}
レイアウトトランジション

兄弟要素の追加または削除時に、位置やサイズの変更をアニメーション化します。

<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>
クラス説明
uw-layout-linear-transition滑らかな線形の位置変更
uw-layout-fading-transition位置変更中にフェード
uw-layout-jumping-transition新しい位置へ弾むように移動
uw-layout-curved-transition曲線に沿った位置変更
uw-layout-sequenced-transition順序に沿った位置変更
uw-layout-entry-exit-transitionレイアウト変更時の入場/退場を組み合わせる
トランジション

className または state が変化したときに、プロパティを滑らかに変更します。

// 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>
クラスプロパティ
transition-noneトランジションなし
transition-allすべてのプロパティ
transition-colors背景色、ボーダー色、テキスト色
transition-opacity不透明度
transition-shadowボックスシャドウ
transition-transform拡大縮小、回転、移動

期間:duration-75 duration-100 duration-150 duration-200 duration-300 duration-500 duration-700 duration-1000

イージング:ease-linear ease-in ease-out ease-in-out

遅延:delay-75 delay-100 delay-150 delay-200 delay-300 delay-500 delay-700 delay-1000

Reanimated を直接使用する

Uniwind クラスと併用しても動作します。

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>
  )}
/>
シャドウツリーの更新

コードを変更する必要はありません。props が C++ エンジンに直接接続されるため、再レンダリングが自動的に排除されます。

シャドウツリーの診断

C++ エンジンの動作を観察するための開発専用 API です。コンポーネントがシャドウツリーに登録/登録解除されるタイミングや、スタイルがどのように反映されるかを確認できます。uniwind/diagnostics からインポートします。

import { enableDiagnostics } from 'uniwind/diagnostics';

enableDiagnostics({
  reportMounts: true,    // log when components register with the shadow tree
  reportUnmounts: true,  // log when components unregister
  reportUpdates: true,   // log style updates with property-level detail
});

3 つのオプションはすべてデフォルトで false です。必要なものだけを有効にしてください(複雑な画面ですべてをログに記録すると、ログが大量になります)。更新ログは 🔥 C++ updates(シャドウツリーを介して適用)と ✨ Native updates(ネイティブビューに直接設定される props)にグループ化されます。どちらも React の再レンダリングなしで発生します。

用途:テーマ切り替えのデバッグ(reportUpdates)、メモリリークの検出(画面遷移後にマウント/アンマウント数がゼロに戻ることを確認)、再レンダリングがゼロであることの検証。

プラットフォーム対応状況:iOS と Android ではすべての診断機能を利用できます。Web では enableDiagnostics は何も出力しないスタブです。

グループバリアント

Tailwind の group バリアントは、C++ シャドウツリーを通じて、親のインタラクション状態を子孫に伝播します。再レンダリングもコンテキストプロバイダーも必要ありません。

// 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>

対応バリアント:group-active:*(押下)、group-focus:*(フォーカス)。名前付きバリアント:group-active/{name}:*、group-focus/{name}:*。

対応するグループ親:Pressable(押下+フォーカス)、Text(押下 — onPress が必要です。空の関数でも構いません)。TouchableOpacity、TouchableHighlight、TouchableWithoutFeedback、TextInput はグループ親として機能しません。group を付けた Pressable で囲んでください。

非対応:group-hover:*(ネイティブではポインターホバーがないため)、group-disabled:*(解析されますが、シャドウツリーのトリガーがありません)、任意の group-[.selector]:* バリアント、暗黙的な in-* バリアント。

デフォルトスタイル(Pro 1.2.0 以降、実験的)

デフォルトスタイルを使用すると、Pro ユーザーは組み込みの React Native コンポーネントのベースラインスタイルを CSS で直接定義できます。デフォルトでは無効になっており、実験的機能フラグが必要です。

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

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

効果:より具体的なスタイルで上書きされない限り、すべての View に border-color: var(--color-primary) が、すべての Text に font-family: Inter と font-size: 16px が設定されます。

ルール:

  • Uniwind Pro 1.2.0+ でのみ利用可能です
  • デフォルトでは無効です。有効にするには experimental.defaultStyles: true を設定します
  • 実験的機能です。すべてのユースケースで動作するとは限らず、今後のリリースで変更される可能性があります
  • セレクターには HTML タグではなく、React Native のコンポーネント名を使用してください
  • ベースラインスタイルとして扱ってください。直接指定した className のスタイルで上書きできます

対応するコンポーネントセレクター:ActivityIndicator、FlatList、Image、ImageBackground、InputAccessoryView、KeyboardAvoidingView、Modal、Pressable、RefreshControl、SafeAreaView、ScrollView、SectionList、Switch、Text、TextInput、TouchableHighlight、TouchableNativeFeedback、TouchableOpacity、TouchableWithoutFeedback、View、VirtualizedList。

Suspense のサポート

React の Suspense 境界内のコンポーネントは正しく処理されます。サブツリーが一時停止している間、Uniwind は C++ シャドウエントリーを維持するため、テーマの更新やランタイムの変更(ダークモード、画面の向きなど)が一時停止中のノードにも届きます。ツリーが再開したときにはスタイルがすでに最新の状態になっているため、古いテーマが一瞬表示されることはありません。

ネイティブのインセット

SafeAreaListener の設定を削除します。インセットはネイティブレイヤーから注入されます。

// With Pro — just use safe area classes directly
<View className="pt-safe pb-safe">{/* content */}</View>
テーマのトランジション(Pro)

テーマ切り替え時にネイティブのアニメーション付きトランジションを適用します。iOS、Android、Web でサポートされています。

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');

利用可能なプリセット:

プリセット効果
ThemeTransitionPreset.Noneアニメーションなしで即座に切り替え
ThemeTransitionPreset.Fadeテーマ間をクロスフェード
ThemeTransitionPreset.SlideRightToLeft新しいテーマが右からスライドイン
ThemeTransitionPreset.SlideLeftToRight新しいテーマが左からスライドイン
ThemeTransitionPreset.CircleTopRight右上から円形マスクが拡大
ThemeTransitionPreset.CircleTopLeft左上から円形マスクが拡大
ThemeTransitionPreset.CircleBottomRight右下から円形マスクが拡大
ThemeTransitionPreset.CircleBottomLeft左下から円形マスクが拡大
ThemeTransitionPreset.CircleCenter中央から円形マスクが拡大
ThemeTransitionPreset.Blurぼかしながら消えるアニメーション
ThemeTransitionPreset.BlurRightToLeft右から左への方向性のあるぼかし
ThemeTransitionPreset.BlurLeftToRight左から右への方向性のあるぼかし

セットアップの診断

スタイルが機能しない場合は、次の順に確認してください。

1. package.json
  • 依存関係に "uniwind"(または "uniwind-pro")がある
  • "tailwindcss" が v4 以上(^4.0.0)
  • Pro の場合:react-native-nitro-modules、react-native-reanimated、react-native-worklets
2. metro.config.js
  • 'uniwind/metro' から withUniwindConfig をインポートしている
  • withUniwindConfig が最も外側のラッパーになっている
  • cssEntryFile が相対パスの文字列(例:'./global.css')になっている
  • path.resolve() や絶対パスを使用していない
  • Pro のデフォルトスタイルを使う場合:experimental.defaultStyles: true が設定されている
3. global.css
  • @import 'tailwindcss'; と @import 'uniwind'; の両方が含まれている
  • App.tsx またはルートレイアウトでインポートされている。index.ts/index.js ではインポートしない
  • 配置場所によって、Tailwind がスキャンするアプリのルートが決まる
4. babel.config.js(Pro のみ)
  • プラグイン配列に 'react-native-worklets/plugin' がある
5. TypeScript
  • uniwind-types.d.ts が存在する(Metro の実行後に生成)
  • tsconfig.json に含まれている、または src//app/ ディレクトリに配置されている
6. ビルド
  • 設定変更後に Metro サーバーを再起動している
  • Metro のキャッシュをクリアしている(npx expo start --clear または npx react-native start --reset-cache)
  • ネイティブの再ビルドを実施している(Pro の場合、または依存関係の変更後)

トラブルシューティング

症状原因対処方法
スタイルが適用されないglobal.css にインポートがない@import 'tailwindcss'; @import 'uniwind'; を追加します
スタイルが適用されないglobal.css を index.js でインポートしているインポートを App.tsx または _layout.tsx に移動します
クラスが検出されないglobal.css がネストされたディレクトリにあり、コンポーネントが別の場所にあるglobal.css に @source '../components' を追加します
className で TypeScript エラーが発生する型定義ファイルがないMetro を実行して uniwind-types.d.ts を生成します
withUniwindConfig is not a functionインポートが正しくないrequire('uniwind') ではなく require('uniwind/metro') を使用します
ホットリロードでアプリ全体が再読み込みされるglobal.css を間違ったファイルでインポートしているApp.tsx またはルートレイアウトに移動します
cssEntryFile エラー/Metro がクラッシュする絶対パスを使用している相対パス './global.css' を使用します
withUniwindConfig が最も外側にない別のラッパーが Uniwind をラップしている順序を入れ替えて、Uniwind を最も外側にします
ダークテーマが機能しない@variant dark がない@layer theme でダークバリアントを定義します
カスタムテーマが表示されないmetro config に登録されていないextraThemes 配列に追加して、Metro を再起動します
フォントが読み込まれないフォント名が一致していないCSS のフォント名はファイル名と完全に一致させます(拡張子なし)
rem の値が大きすぎる/小さすぎる基準となる rem が正しくないNativeWind との互換性のために polyfills: { rem: 14 } を設定します
未対応 CSS の警告が表示されるWeb 固有の CSS を使用しているdebug: true を有効にして特定し、未対応のプロパティを削除します
Failed to serialize javascript object複雑な CSS、循環参照、または古いキャッシュキャッシュをクリアします:watchman watch-del-all; rm -rf node_modules/.cache; npx expo start --clear。また、CSS クラスを含む docs/markdown ファイルがスキャン対象のパスにないか確認してください(下記参照)
llms-full.txt または docs で Failed to serialize javascript object が発生するプロジェクトディレクトリ内の CSS クラスを含む docs/markdown ファイルが Tailwind によってスキャンされているCSS の例を含む .md ファイルをプロジェクトルートの外に移動するか、.gitignore に追加して Tailwind のスキャナーがスキップするようにします
unstable_enablePackageExports の競合アプリで package exports が無効になっているUniwind と culori に選択的リゾルバーを使用します
モノレポのパッケージのクラスが見つからないTailwind のスキャン対象に含まれていないglobal.css に @source '../../packages/ui' を追加します
本番環境で node_modules のライブラリのクラスが見つからない(bun)Bun はシンボリックリンクを使用するため、Tailwind の Oxide スキャナーがたどれない解決済みパス @source "../../node_modules/heroui-native/lib" を使用し、.npmrc に public-hoist-pattern[]=heroui-native を追加します
withUniwind で active: が機能しないwithUniwind はインタラクティブな状態セレクターをサポートしていませんRN コアの Pressable/TextInput/Switch のみが active:/focus:/disabled: をサポートしています。withUniwind でラップしたサードパーティ製のプレス可能コンポーネントでは状態が適用されません
withUniwind のカスタムマッピングで className と style のマージが上書きされる手動マッピングを指定すると、style プロパティがマージされないclassName と style をマージするには、自動マッピング(第 2 引数なし)を使用します。手動マッピングと className を併用するには、二重にラップします:withUniwind(withUniwind(Comp), { mapping })
withUniwind が ref のジェネリック型(例:FlashList<T>)を失うHOC における TypeScript の制限ref を手動でキャストします:ref={scrollRef as any}
プラットフォーム固有のフォント:@theme ブロックエラー@theme {} 内に @media ios/android がある代わりに @layer theme { :root { @variant ios { ... } } } を使用します — @theme が受け付けるのはカスタムプロパティのみで、プラットフォームの選択には @media ではなく @variant を使用します
Android(RN 0.82 以降)で Uniwind.setTheme('system') がクラッシュするRN 0.82 で Appearance API が変更された最新の Uniwind に更新します(修正済み)。古い Uniwind と RN 0.82 以降の組み合わせでは setTheme('system') を使用しないでください
初回読み込み時にスタイルが一瞬表示された後、消える(Android)コンポーネントのリスナーがマウントされる前に SafeAreaListener が呼び出される最近のバージョンで修正済みです。問題が続く場合は、Uniwind が最新であることを確認してください
useTVEventHandler が未定義Uniwind のモジュール置換が tvOS のエクスポートに干渉しているv1.2.1 以降で修正済みです。Uniwind を更新してください
Web で @layer theme の変数がレンダリングされないRNW と Expo SDK 55 の不具合v1.4.1 以降で修正済みです。Uniwind を更新してください
アプリ起動時に updateCSSVariables が間違ったテーマを適用する複数のテーマに対して連続して呼び出しており、初回レンダリングでは最後の呼び出しが適用される現在のテーマに対する updateCSSVariables の呼び出しを最後にします。初回読み込み後は順序は関係ありません
Pro:アニメーションが機能しないBabel プラグインがないbabel.config.js に react-native-worklets/plugin を追加します
Pro:モジュールが見つからないネイティブの再ビルドをしていないnpx expo prebuild --clean の後に npx expo run:ios を実行します
Pro:postinstall に失敗したパッケージマネージャーがスクリプトをブロックしているtrustedDependencies(bun)に追加するか、yarn/pnpm を設定します
Pro:認証の有効期限が切れたログインセッションの有効期限が切れた(有効期間は 180 日)npx uniwind-pro を実行して、再ログインします
Pro:ダウンロード制限に達した月間ダウンロード制限に達したPro ダッシュボードを確認してください。制限は毎月リセットされます
Pro:Uniwind.updateInsets が不要に呼び出されるPro はインセットをネイティブに挿入するPro では Uniwind.updateInsets は何もしません。Pro を使用する場合は SafeAreaListener の設定を削除してください
Pro:テーマの切り替え時にクラッシュするThemeTransitionPreset のインポートがないか、アプリの準備完了前に呼び出している'uniwind' からインポートします。トランジション付きで setTheme を呼び出す前に、アプリが完全にマウントされていることを確認してください
Pro:コンポーネントのデフォルトスタイルが適用されない機能が無効か、セレクターがサポートされていないUniwind Pro 1.2.0 以降を使用し、experimental.defaultStyles: true を有効にして Metro を再起動し、View や Text などのサポート対象の RN コンポーネントセレクターを使用します
unstable_enablePackageExports 選択的リゾルバー

アプリで unstable_enablePackageExports を無効にしている場合(暗号資産アプリでよくあります)、選択的リゾルバーを使用してください。

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

Expo Router では global.css をどこに配置すればよいですか? プロジェクトのルートです。app/_layout.tsx でインポートしてください。app/ 内に配置する場合は、兄弟ディレクトリ用の @source を追加してください。

Uniwind は Expo Go で動作しますか? Free:はい。Pro:いいえ。ネイティブの再ビルド(開発ビルド)が必要です。

tailwind.config.js を使用できますか? いいえ。Uniwind は Tailwind v4 を使用しており、設定はすべて global.css 内の @theme で行います。

JavaScript から CSS 変数にアクセスするにはどうすればよいですか? コンポーネント内では useCSSVariable('--color-primary')(リアクティブ)を使用します。React の外では Uniwind.getCSSVariable('--color-primary')(一度だけ取得、1.6.4+)を使用します。クラス名で使用しない変数は、@theme static で定義してください。

Platform.select() を使用できますか? はい。ただし、プラットフォームセレクター(ios:pt-12 android:pt-6)を推奨します。よりシンプルで、インポートも不要です。

Next.js はサポートされていますか? 公式にはサポートされていません。コミュニティプラグイン:uniwind-plugin-next。Next.js では、標準の Tailwind CSS を使用してください。

Vite はサポートされていますか? はい。v1.2.0 以降でサポートされています。@tailwindcss/vite と併せて uniwind/vite プラグインを使用してください。

CSS の変更時にアプリ全体がリロードされるのはなぜですか? Metro は、多数のプロバイダーを含むファイルをホットリロードできません。コンポーネントツリーのより深い位置に global.css のインポートを移動してください。

スタイルの優先順位はどうなっていますか? bg-red-500! のような重要ユーティリティは、同じプロパティに対する重要でないユーティリティを上書きし、バリアント(active:bg-red-500!、ios:pt-12!)でも使用できます。インラインの style は、重要ユーティリティであっても常に className を上書きします。静的なスタイルには className を使用し、インラインスタイルは本当に動的な値にのみ使用してください。クラス名が競合する可能性のあるコンポーネントライブラリでは、tailwind-merge の cn() を使用してください。

カスタムフォントを読み込むにはどうすればよいですか? フォントファイルを読み込み(Expo:app.json の expo-font プラグイン、Bare RN:react-native-asset)、CSS でマッピングします:@theme { --font-sans: 'Roboto-Regular'; }。フォント名はファイル名と完全に一致している必要があります。上記の Fonts セクションを参照してください。

プロパティの値に基づいてスタイルを設定するにはどうすればよいですか? データセレクターを使用します:data-[selected=true]:ring-2。等価チェックのみサポートされています。上記の Data Selectors セクションを参照してください。

グラデーションを使用するにはどうすればよいですか? 組み込み機能:bg-gradient-to-r from-red-500 to-green-500。角度指定(bg-linear-90)と任意の値(bg-linear-[45deg,#f00_0%,#00f_100%])にも対応しています。上記の Gradients セクションを参照してください。

className の重複排除はどのように機能しますか? Uniwind は、競合する className を自動では重複排除しません。cn() ユーティリティと tailwind-merge を使用してください。上記の cn Utility セクションを参照してください。

「Failed to serialize javascript object」のデバッグ方法を教えてください。 キャッシュをクリアします:watchman watch-del-all; rm -rf node_modules/.cache; npx expo start --clear。Metro の設定で debug: true を有効にし、問題のある CSS パターンを特定してください。上記の Troubleshooting テーブルを参照してください。

セーフエリアの className を有効にするにはどうすればよいですか? Free:react-native-safe-area-context をインストールし、ルートを SafeAreaListener でラップして、Uniwind.updateInsets(insets) を呼び出します。Pro:自動で有効になるため、設定は不要です。その後、pt-safe、pb-safe などを使用してください。上記の Safe Area Utilities セクションを参照してください。

Uniwind と相性のよい UI キットはどれですか? React Native Reusables(shadcn の考え方に基づく、コピー&ペースト可能なコンポーネント)と HeroUI Native(Uniwind 向けに最適化された完全なライブラリ)です。どのライブラリも withUniwind ラッパーを使って動作します。上記の UI Kit Compatibility セクションを参照してください。

単一のコンポーネントにテーマを適用できますか? はい。ScopedTheme を使用します:<ScopedTheme theme="dark"><Card /></ScopedTheme>。グローバルテーマを変更せずに、サブツリー内でテーマを強制的に適用します。Theming セクションを参照してください。

active: は react-native-gesture-handler の Pressable で動作しますか? いいえ。withUniwind はインタラクティブな状態セレクター(active:、focus:、disabled:)をサポートしていません。これらをサポートしているのは、コア RN の Pressable、TextInput、Switch のみです。RNGH コンポーネントでは、状態と onPressIn/onPressOut を使用してください。 デフォルトの border の色はカスタマイズできますか? はい。@utility border を使ってクラス全体を上書きします。

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

これにより組み込みの border の動作が完全に置き換えられるため、引き続き必要なプロパティは再宣言してください。または、border border-gray-300 を明示的に使うか、@theme で --color-border を定義して border-border を使います。

@theme {} でプラットフォーム固有のフォントを使えますか? いいえ。@theme {} が受け付けるのはカスタムプロパティのみです。代わりに @layer theme { :root { @variant ios { --font-sans: '...'; } } } を使います。注:CSS でプラットフォームを選択するには、@media ではなく @variant を使ってください。

Uniwind は ESM の metro 設定(metro.config.mjs または metro.config.ts)で動作しますか? はい。v1.3.0 以降で動作します。Class extends value is not a constructor が発生した場合は、Uniwind を更新するか、CommonJS の metro.config.js を使ってください。

node_modules のクラスが開発環境では動作するのに、本番環境(bun)では動作しないのはなぜですか? Bun はパッケージをシンボリックリンクします。Tailwind の Oxide スキャナーはそれをたどれません。解決済みの @source パスを使い、パッケージをホイストしてください。詳細は UI Kit Compatibility を参照してください。

デバッグ時にすべてのキャッシュをクリアするにはどうすればよいですか? watchman watch-del-all 2>/dev/null; rm -rf node_modules/.cache && npx expo start --clear を実行します。これにより、Watchman、Babel/バンドラーのキャッシュ、Expo 内部のキャッシュがクリアされます。

MCP サーバー

Uniwind は、AI を活用した開発向けの公式 MCP サーバーを提供しています。ユーザーの環境が MCP に対応している場合は、セットアップをおすすめしてください:https://docs.uniwind.dev/mcp

関連スキル

NativeWind → Uniwind の移行:NativeWind から移行するには、migrate-nativewind-to-uniwind スキルを使います。パッケージの削除、設定の移行、Tailwind v4 へのアップグレード、cssInterop の削除、テーマの変換、すべての破壊的変更に対応しています。テスト済みで、本番環境で使用できます。

重要:Uniwind の API を推測しないでください。Uniwind の API、フック、コンポーネント、設定オプションに不確かな点がある場合は、公式ドキュメントを取得して確認してください:https://docs.uniwind.dev/llms-full.txt

コメント