Communitygithub.com

Geruyang/agent-skills-library

Expo で react-native-css と NativeWind v5 を使って Tailwind CSS v4 をセットアップし、ユニバーサルなスタイリングを実現します。

agent-skills-library とは?

このスキルは、Expo プロジェクトに Tailwind CSS v4 を導入し、react-native-css と NativeWind v5 を組み合わせることで、iOS、Android、Web のすべてのプラットフォームで統一されたスタイリングを提供します。従来の Babel プラグイン方式から脱却し、PostCSS と CSS ファーストの設定を採用することで、よりモダンで高速な開発ワークフローを実現します。具体的には、CSS ファイルに `@import` で Tailwind のレイヤーを読み込み、`@theme` ディレクティブでカスタムテーマを定義する方法を採用します。さらに、`react-native-css` の `useCssElement` フックを使って React Native の基本コンポーネントをラップし、className プロパティで Tailwind のユーティリティクラスを直接使用できるようにします。このスキルは、Expo SDK 52+、Tailwind CSS v4、NativeWind v5 を対象としており、`metro.config.js`、`postcss.config.mjs`、およびグローバル CSS ファイルの設定から、実際のコンポーネントでの使用方法、カスタムテーマ、プラットフォーム固有のスタイル、Apple システムカラーの統合までをカバーしています。また、NativeWind v4 / Tailwind v3 からの主な変更点やトラブルシューティングのヒントも提供しています。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Geruyang/agent-skills-library/tree/HEAD/original-skills/x-openai-expo-tailwind-setup-db649d8

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Tailwind CSS Setup for Expo with react-native-css

このガイドでは、react-native-css と NativeWind v5 を使用して Expo に Tailwind CSS v4 をセットアップし、iOS、Android、Web 全体でユニバーサルなスタイリングを実現する方法について説明します。

概要

このセットアップでは以下を使用します:

  • Tailwind CSS v4 - モダンな CSS ファースト構成
  • react-native-css - React Native 向け CSS ランタイム
  • NativeWind v5 - React Native で Tailwind を使用するための Metro トランスフォーマー
  • @tailwindcss/postcss - Tailwind v4 用 PostCSS プラグイン

インストール

# 依存関係のインストール
npx expo install tailwindcss@^4 [email protected] [email protected] @tailwindcss/postcss tailwind-merge clsx

lightningcss の互換性のために resolutions を追加します:

// package.json
{
  "resolutions": {
    "lightningcss": "1.30.1"
  }
}
  • autoprefixer は Expo では lightningcss が使用されるため不要です
  • postcss は Expo にデフォルトで含まれています

設定ファイル

Metro 設定

metro.config.js を作成または更新します:

// metro.config.js
const { getDefaultConfig } = require("expo/metro-config");
const { withNativewind } = require("nativewind/metro");

/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname);

module.exports = withNativewind(config, {
  // インラインブレーク変数は CSS 変数の PlatformColor を壊します
  inlineVariables: false,
  // className サポートは手動で追加します
  globalClassNamePolyfill: false,
});

PostCSS 設定

postcss.config.mjs を作成します:

// postcss.config.mjs
export default {
  plugins: {
    "@tailwindcss/postcss": {},
  },
};

グローバル CSS

src/global.css を作成します:

@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/preflight.css" layer(base);
@import "tailwindcss/utilities.css";

/* プラットフォーム固有のフォントファミリー */
@media android {
  :root {
    --font-mono: monospace;
    --font-rounded: normal;
    --font-serif: serif;
    --font-sans: normal;
  }
}

@media ios {
  :root {
    --font-mono: ui-monospace;
    --font-serif: ui-serif;
    --font-sans: system-ui;
    --font-rounded: ui-rounded;
  }
}

重要: Babel 設定は不要

Tailwind v4 と NativeWind v5 では、Tailwind 用の babel.config.js は必要ありません。既存の NativeWind Babel プリセットがあれば削除してください:

// babel.config.js が NativeWind 設定のみを含む場合は削除
// 以下はもはや必要ありません:
// module.exports = function (api) {
//   api.cache(true);
//   return {
//     presets: [
//       ["babel-preset-expo", { jsxImportSource: "nativewind" }],
//       "nativewind/babel",
//     ],
//   };
// };

CSS コンポーネントラッパー

react-native-css は明示的な CSS 要素ラッピングを必要とするため、再利用可能なコンポーネントを作成します:

メインコンポーネント (src/tw/index.tsx)

import {
  useCssElement,
  useNativeVariable as useFunctionalVariable,
} from "react-native-css";

import { Link as RouterLink } from "expo-router";
import Animated from "react-native-reanimated";
import React from "react";
import {
  View as RNView,
  Text as RNText,
  Pressable as RNPressable,
  ScrollView as RNScrollView,
  TouchableHighlight as RNTouchableHighlight,
  TextInput as RNTextInput,
  StyleSheet,
} from "react-native";

// CSS 対応 Link
export const Link = (
  props: React.ComponentProps<typeof RouterLink> & { className?: string }
) => {
  return useCssElement(RouterLink, props, { className: "style" });
};

Link.Trigger = RouterLink.Trigger;
Link.Menu = RouterLink.Menu;
Link.MenuAction = RouterLink.MenuAction;
Link.Preview = RouterLink.Preview;

// CSS 変数フック
export const useCSSVariable =
  process.env.EXPO_OS !== "web"
    ? useFunctionalVariable
    : (variable: string) => `var(${variable})`;

// View
export type ViewProps = React.ComponentProps<typeof RNView> & {
  className?: string;
};

export const View = (props: ViewProps) => {
  return useCssElement(RNView, props, { className: "style" });
};
View.displayName = "CSS(View)";

// Text
export const Text = (
  props: React.ComponentProps<typeof RNText> & { className?: string }
) => {
  return useCssElement(RNText, props, { className: "style" });
};
Text.displayName = "CSS(Text)";

// ScrollView
export const ScrollView = (
  props: React.ComponentProps<typeof RNScrollView> & {
    className?: string;
    contentContainerClassName?: string;
  }
) => {
  return useCssElement(RNScrollView, props, {
    className: "style",
    contentContainerClassName: "contentContainerStyle",
  });
};
ScrollView.displayName = "CSS(ScrollView)";

// Pressable
export const Pressable = (
  props: React.ComponentProps<typeof RNPressable> & { className?: string }
) => {
  return useCssElement(RNPressable, props, { className: "style" });
};
Pressable.displayName = "CSS(Pressable)";

// TextInput
export const TextInput = (
  props: React.ComponentProps<typeof RNTextInput> & { className?: string }
) => {
  return useCssElement(RNTextInput, props, { className: "style" });
};
TextInput.displayName = "CSS(TextInput)";

// AnimatedScrollView
export const AnimatedScrollView = (
  props: React.ComponentProps<typeof Animated.ScrollView> & {
    className?: string;
    contentClassName?: string;
    contentContainerClassName?: string;
  }
) => {
  return useCssElement(Animated.ScrollView, props, {
    className: "style",
    contentClassName: "contentContainerStyle",
    contentContainerClassName: "contentContainerStyle",
  });
};

// TouchableHighlight(underlayColor 抽出付き)
function XXTouchableHighlight(
  props: React.ComponentProps<typeof RNTouchableHighlight>
) {
  const { underlayColor, ...style } = StyleSheet.flatten(props.style) || {};
  return (
    <RNTouchableHighlight
      underlayColor={underlayColor}
      {...props}
      style={style}
    />
  );
}

export const TouchableHighlight = (
  props: React.ComponentProps<typeof RNTouchableHighlight>
) => {
  return useCssElement(XXTouchableHighlight, props, { className: "style" });
};
TouchableHighlight.displayName = "CSS(TouchableHighlight)";

画像コンポーネント (src/tw/image.tsx)

import { useCssElement } from "react-native-css";
import React from "react";
import { StyleSheet } from "react-native";
import Animated from "react-native-reanimated";
import { Image as RNImage } from "expo-image";

const AnimatedExpoImage = Animated.createAnimatedComponent(RNImage);

export type ImageProps = React.ComponentProps<typeof Image>;

function CSSImage(props: React.ComponentProps<typeof AnimatedExpoImage>) {
  // @ts-expect-error: objectFit スタイルを contentFit プロパティに再マッピング
  const { objectFit, objectPosition, ...style } =
    StyleSheet.flatten(props.style) || {};

  return (
    <AnimatedExpoImage
      contentFit={objectFit}
      contentPosition={objectPosition}
      {...props}
      source={
        typeof props.source === "string" ? { uri: props.source } : props.source
      }
      // @ts-expect-error: スタイルは上で再マッピング済み
      style={style}
    />
  );
}

export const Image = (
  props: React.ComponentProps<typeof CSSImage> & { className?: string }
) => {
  return useCssElement(CSSImage, props, { className: "style" });
};

Image.displayName = "CSS(Image)";

アニメーションコンポーネント (src/tw/animated.tsx)

import * as TW from "./index";
import RNAnimated from "react-native-reanimated";

export const Animated = {
  ...RNAnimated,
  View: RNAnimated.createAnimatedComponent(TW.View),
};

使用方法

tw ディレクトリから CSS ラップされたコンポーネントをインポートします:

import { View, Text, ScrollView, Image } from "@/tw";

export default function MyScreen() {
  return (
    <ScrollView className="flex-1 bg-white">
      <View className="p-4 gap-4">
        <Text className="text-xl font-bold text-gray-900">Hello Tailwind!</Text>
        <Image
          className="w-full h-48 rounded-lg object-cover"
          source={{ uri: "https://example.com/image.jpg" }}
        />
      </View>
    </ScrollView>
  );
}

カスタムテーマ変数

@theme を使用して、global.css にカスタムテーマ変数を追加します:

@layer theme {
  @theme {
    /* カスタムフォント */
    --font-rounded: "SF Pro Rounded", sans-serif;

    /* カスタム行高 */
    --text-xs--line-height: calc(1em / 0.75);
    --text-sm--line-height: calc(1.25em / 0.875);
    --text-base--line-height: calc(1.5em / 1);

    /* カスタム行間スケール */
    --leading-tight: 1.25em;
    --leading-snug: 1.375em;
    --leading-normal: 1.5em;
  }
}

プラットフォーム固有のスタイル

プラットフォーム固有のスタイルにはメディアクエリを使用します:

@media ios {
  :root {
    --font-sans: system-ui;
    --font-rounded: ui-rounded;
  }
}

@media android {
  :root {
    --font-sans: normal;
    --font-rounded: normal;
  }
}

CSS 変数による Apple システムカラー

Apple のセマンティックカラー用の CSS ファイルを作成します:

/* src/css/sf.css */
@layer base {
  html {
    color-scheme: light;
  }
}

:root {
  /* ライト/ダークモード対応のアクセントカラー */
  --sf-blue: light-dark(rgb(0 122 255), rgb(10 132 255));
  --sf-green: light-dark(rgb(52 199 89), rgb(48 209 89));
  --sf-red: light-dark(rgb(255 59 48), rgb(255 69 58));

  /* グレースケール */
  --sf-gray: light-dark(rgb(142 142 147), rgb(142 142 147));
  --sf-gray-2: light-dark(rgb(174 174 178), rgb(99 99 102));

  /* テキストカラー */
  --sf-text: light-dark(rgb(0 0 0), rgb(255 255 255));
  --sf-text-2: light-dark(rgb(60 60 67 / 0.6), rgb(235 235 245 / 0.6));

  /* 背景色 */
  --sf-bg: light-dark(rgb(255 255 255), rgb(0 0 0));
  --sf-bg-2: light-dark(rgb(242 242 247), rgb(28 28 30));
}

/* platformColor による iOS ネイティブカラー */
@media ios {
  :root {
    --sf-blue: platformColor(systemBlue);
    --sf-green: platformColor(systemGreen);
    --sf-red: platformColor(systemRed);
    --sf-gray: platformColor(systemGray);
    --sf-text: platformColor(label);
    --sf-text-2: platformColor(secondaryLabel);
    --sf-bg: platformColor(systemBackground);
    --sf-bg-2: platformColor(secondarySystemBackground);
  }
}

/* Tailwind テーマカラーとして登録 */
@layer theme {
  @theme {
    --color-sf-blue: var(--sf-blue);
    --color-sf-green: var(--sf-green);
    --color-sf-red: var(--sf-red);
    --color-sf-gray: var(--sf-gray);
    --color-sf-text: var(--sf-text);
    --color-sf-text-2: var(--sf-text-2);
    --color-sf-bg: var(--sf-bg);
    --color-sf-bg-2: var(--sf-bg-2);
  }
}

そしてコンポーネントで使用します:

<Text className="text-sf-text">プライマリテキスト</Text>
<Text className="text-sf-text-2">セカンダリテキスト</Text>
<View className="bg-sf-bg">...</View>

JavaScript での CSS 変数の使用

useCSSVariable フックを使用します:

import { useCSSVariable } from "@/tw";

function MyComponent() {
  const blue = useCSSVariable("--sf-blue");

  return <View style={{ borderColor: blue }} />;
}

NativeWind v4 / Tailwind v3 との主な違い

  1. babel.config.js が不要 - 設定は CSS ファーストになりました
  2. PostCSS プラグイン - tailwindcss の代わりに @tailwindcss/postcss を使用
  3. CSS インポート - @tailwind ディレクティブの代わりに @import "tailwindcss/..." を使用
  4. テーマ設定 - tailwind.config.js の代わりに CSS で @theme を使用
  5. コンポーネントラッパー - className サポートのためにコンポーネントを useCssElement でラップする必要があります
  6. Metro 設定 - 異なるオプション(inlineVariables: false)で withNativewind を使用

トラブルシューティング

スタイルが適用されない

  1. アプリのエントリポイントで CSS ファイルがインポートされていることを確認
  2. コンポーネントが useCssElement でラップされていることを確認
  3. Metro 設定に withNativewind が適用されていることを確認

プラットフォームカラーが動作しない

  1. @media ios ブロック内で platformColor() を使用
  2. ウェブ/Android 用に light-dark() にフォールバック

TypeScript エラー

コンポーネントのプロパティに className を追加します:

type Props = React.ComponentProps<typeof RNView> & { className?: string };

Individual skills in this repo

This repo contains 12 individual skills — each has its own dedicated page.

Geruyang/agent-skills-library

Guidance for distinctive, intentional visual design when building new UI or reshaping an existing one. Helps with aesthetic direction, typography, and making choices that don't read as templated defaults.

Geruyang/agent-skills-library

Use for new frontend applications, dashboards, games, creative websites, hero sections, and visually driven UI from scratch, or when the user explicitly asks for a redesign/restyle/modernization. Builds from clean, airy, high-taste, readable image-generated concept design with section-specific references, faithful implementation, and browser testing.

Geruyang/agent-skills-library

Use when testing, debugging, or making targeted improvements to rendered frontend apps through the Build Web Apps or web dev plugin: local dev servers, UI regressions, interaction bugs, console errors, responsive layout, and visual QA. Check whether the Browser plugin is available and use it first when it is; otherwise use regular Playwright with the recorded reason.

Geruyang/agent-skills-library

Design UI surfaces for browser games. Use when the user asks for HUDs, menus, overlays, responsive layouts, or visual direction that must protect the playfield.

Geruyang/agent-skills-library

GSAP animation reference for HyperFrames. Covers gsap.to(), from(), fromTo(), easing, stagger, defaults, timelines (gsap.timeline(), position parameter, labels, nesting, playback), and performance (transforms, will-change, quickTo). Use when writing GSAP animations in HyperFrames compositions.

Geruyang/agent-skills-library

Guide for building, configuring, and deploying microfrontends on Vercel. Use this skill when the user mentions microfrontends, multi-zones, splitting an app across teams, independent deployments, cross-app routing, incremental migration, composing multiple frontends under one domain, microfrontends.json, @vercel/microfrontends, the microfrontends local proxy, or path-based routing between Vercel projects. Also use when the user asks about shared layouts across projects, navigation between microfrontends, fallback environments, asset prefixes, or feature flag controlled routing.

Geruyang/agent-skills-library

Next.js App Router expert guidance. Use when building, debugging, or architecting Next.js applications — routing, Server Components, Server Actions, Cache Components, layouts, middleware/proxy, data fetching, rendering strategies, and deployment on Vercel.

Geruyang/agent-skills-library

Use when sizing Public Equity Investing positions, finding equity hedges, or building an integrated position-and-hedge risk plan from a listed-equity thesis. Do not use for thesis construction, standalone event underwriting, trade execution, personal investment advice, or credit-instrument risk.

Geruyang/agent-skills-library

Integrate data visualizations into React and Next.js applications. Use when the user needs chart components, UML-like or architecture diagram components, React integration patterns, Next.js client or server boundaries, hydration-safe rendering, lazy loading, framework-aware performance, scroll-driven visual stories, or export guidance.

Geruyang/agent-skills-library

shadcn/ui expert guidance — CLI, component installation, composition patterns, custom registries, theming, Tailwind CSS integration, and high-quality interface design. Use when initializing shadcn, adding components, composing product UI, building custom registries, configuring themes, or troubleshooting component issues.

Geruyang/agent-skills-library

v0 by Vercel expert guidance. Use when discussing AI code generation, generating UI components from prompts, v0 CLI usage, v0 SDK/API integration, or integrating v0 into development workflows with GitHub and Vercel deployment.

Geruyang/agent-skills-library

Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn init", "create an app with --preset", or "switch to --preset".

関連スキル