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 端的通用样式。它采用 CSS-first 配置方式,无需 babel.config.js,通过 PostCSS 插件和 Metro 转换器运行。指南详细介绍了安装步骤、配置文件的创建(包括 metro.config.js、postcss.config.mjs 和 global.css),以及如何使用 `useCssElement` 封装 React Native 核心组件(如 View、Text、Image 等)以支持 className 属性。此外,还涵盖了平台特定样式(通过 @media ios/android 媒体查询)、Apple 系统色彩变量的整合(使用 `platformColor()` 和 `light-dark()`)、自定义主题变量(通过 @theme)以及在 JavaScript 中使用 CSS 变量的方法。该设置适用于所有 Expo 平台,能够简化跨平台开发流程,降低样式代码的维护成本。

兼容平台~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 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

使用 react-native-css 在 Expo 中配置 Tailwind CSS

本指南介绍如何在 Expo 中使用 react-native-css 和 NativeWind v5 配置 Tailwind CSS v4,实现 iOS、Android 和 Web 端的通用样式。

概述

本配置使用以下工具:

  • Tailwind CSS v4 - 现代化的 CSS-first 配置
  • 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"
  }
}
  • 在 Expo 中无需 autoprefixer,因为使用了 lightningcss
  • Expo 默认包含了 postcss

配置文件

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, {
  // inline variables 会破坏 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 时,不需要 babel.config.js 来支持 Tailwind。如果存在,请删除任何 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 Variable hook
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)";

Image 组件(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>
  );
}

自定义主题变量

在 global.css 中使用 @theme 添加自定义主题变量:

@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 比例 */
    --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 hook:

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-first 配置
  2. PostCSS 插件 - 使用 @tailwindcss/postcss 替代 tailwindcss
  3. CSS 导入 - 使用 @import "tailwindcss/..." 替代 @tailwind 指令
  4. 主题配置 - 在 CSS 中使用 @theme 替代 tailwind.config.js
  5. 组件包装器 - 必须使用 useCssElement 包装组件以支持 className
  6. Metro 配置 - 使用带有不同选项(inlineVariables: false)的 withNativewind

故障排除

样式未生效

  1. 确保已在应用入口导入 CSS 文件
  2. 检查组件是否已使用 useCssElement 包装
  3. 验证 Metro 配置是否正确应用了 withNativewind

平台颜色不工作

  1. 在 @media ios 代码块中使用 platformColor()
  2. 在 web/Android 上回退使用 light-dark()

TypeScript 错误

在组件 props 中添加 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".

相关技能