Communitygithub.com

Samueladewole/claude-skills

Tailwind CSS v4 in Expo mit react-native-css und NativeWind v5 für universelles Styling einrichten.

Was ist claude-skills?

Diese Skill-Anleitung beschreibt die Einrichtung von Tailwind CSS v4 in einer Expo-Umgebung unter Verwendung von react-native-css und NativeWind v5. Sie ermöglicht universelles Styling für iOS, Android und das Web mit einem modernen, CSS-zentrierten Ansatz. Die Konfiguration umfasst die Installation der benötigten Pakete, die Einrichtung von Metro und PostCSS, sowie die Erstellung von CSS-Komponenten-Wrappern, die `useCssElement` von react-native-css nutzen. Ein besonderer Fokus liegt auf plattformspezifischen Stilen, benutzerdefinierten Theme-Variablen und der Verwendung von Apple Systemfarben über CSS-Variablen. Diese Konfiguration ersetzt die ältere NativeWind v4- und Tailwind v3-Einrichtung, die eine babel.config.js erforderte, durch einen komplett CSS-basierten Workflow.

Funktioniert mit✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Samueladewole/claude-skills/tree/HEAD/expo-tailwind-setup

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Tailwind CSS Setup für Expo mit react-native-css

Diese Anleitung beschreibt die Einrichtung von Tailwind CSS v4 in Expo mit react-native-css und NativeWind v5 für universelles Styling auf iOS, Android und Web.

Überblick

Dieses Setup verwendet:

  • Tailwind CSS v4 - Moderne, CSS-zentrierte Konfiguration
  • react-native-css - CSS-Laufzeit für React Native
  • NativeWind v5 - Metro-Transformator für Tailwind in React Native
  • @tailwindcss/postcss - PostCSS-Plugin für Tailwind v4

Installation

# Abhängigkeiten installieren
npx expo install tailwindcss@^4 [email protected] [email protected] @tailwindcss/postcss tailwind-merge clsx

Resolutions für lightningcss-Kompatibilität hinzufügen:

// package.json
{
  "resolutions": {
    "lightningcss": "1.30.1"
  }
}
  • autoprefixer wird in Expo aufgrund von lightningcss nicht benötigt
  • postcss ist standardmäßig in Expo enthalten

Konfigurationsdateien

Metro-Konfiguration

Erstellen oder aktualisieren Sie 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-Variablen brechen PlatformColor in CSS-Variablen
  inlineVariables: false,
  // Wir fügen className-Unterstützung manuell hinzu
  globalClassNamePolyfill: false,
});

PostCSS-Konfiguration

Erstellen Sie postcss.config.mjs:

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

Globales CSS

Erstellen Sie src/global.css:

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

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

WICHTIG: Keine Babel-Konfiguration erforderlich

Mit Tailwind v4 und NativeWind v5 wird KEINE babel.config.js für Tailwind benötigt. Entfernen Sie vorhandene NativeWind-Babel-Presets:

// babel.config.js LÖSCHEN, wenn es nur die NativeWind-Konfiguration enthält
// Das Folgende wird NICHT MEHR BENÖTIGT:
// module.exports = function (api) {
//   api.cache(true);
//   return {
//     presets: [
//       ["babel-preset-expo", { jsxImportSource: "nativewind" }],
//       "nativewind/babel",
//     ],
//   };
// };

CSS-Komponenten-Wrapper

Da react-native-css explizites CSS-Element-Wrapping erfordert, erstellen Sie wiederverwendbare Komponenten:

Hauptkomponenten (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-fähiger 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 mit underlayColor-Extraktion
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)";

Bild-Komponente (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-Stil auf contentFit-Eigenschaft ummappen
  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: Stil wird oben umgemappt
      style={style}
    />
  );
}

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

Image.displayName = "CSS(Image)";

Animierte Komponenten (src/tw/animated.tsx)

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

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

Verwendung

Importieren Sie CSS-gewickelte Komponenten aus Ihrem tw-Verzeichnis:

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">Hallo Tailwind!</Text>
        <Image
          className="w-full h-48 rounded-lg object-cover"
          source={{ uri: "https://example.com/image.jpg" }}
        />
      </View>
    </ScrollView>
  );
}

Benutzerdefinierte Theme-Variablen

Fügen Sie benutzerdefinierte Theme-Variablen in Ihrer global.css mit @theme hinzu:

@layer theme {
  @theme {
    /* Benutzerdefinierte Schriftarten */
    --font-rounded: "SF Pro Rounded", sans-serif;

    /* Benutzerdefinierte Zeilenhöhen */
    --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);

    /* Benutzerdefinierte leading-Skalen */
    --leading-tight: 1.25em;
    --leading-snug: 1.375em;
    --leading-normal: 1.5em;
  }
}

Plattformspezifische Stile

Verwenden Sie Media Queries für plattformspezifisches Styling:

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

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

Apple Systemfarben mit CSS-Variablen

Erstellen Sie eine CSS-Datei für semantische Apple-Farben:

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

:root {
  /* Akzentfarben mit Hell/Dunkel-Modus */
  --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));

  /* Graustufen */
  --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));

  /* Textfarben */
  --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));

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

/* iOS native Farben via platformColor */
@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);
  }
}

/* Als Tailwind Theme-Farben registrieren */
@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);
  }
}

Dann in Komponenten verwenden:

<Text className="text-sf-text">Primärer Text</Text>
<Text className="text-sf-text-2">Sekundärer Text</Text>
<View className="bg-sf-bg">...</View>

Verwendung von CSS-Variablen in JavaScript

Verwenden Sie den useCSSVariable-Hook:

import { useCSSVariable } from "@/tw";

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

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

Wichtige Unterschiede zu NativeWind v4 / Tailwind v3

  1. Keine babel.config.js - Die Konfiguration ist jetzt CSS-zentriert
  2. PostCSS-Plugin - Verwendet @tailwindcss/postcss anstelle von tailwindcss
  3. CSS-Imports - Verwenden Sie @import "tailwindcss/..." anstelle von @tailwind-Direktiven
  4. Theme-Konfiguration - Verwenden Sie @theme in CSS anstelle von tailwind.config.js
  5. Komponenten-Wrapper - Komponenten müssen mit useCssElement für className-Unterstützung umwickelt werden
  6. Metro-Konfiguration - Verwenden Sie withNativewind mit anderen Optionen (inlineVariables: false)

Fehlerbehebung

Stile werden nicht angewendet

  1. Stellen Sie sicher, dass die CSS-Datei in Ihrem App-Einstiegspunkt importiert wird
  2. Überprüfen Sie, dass Komponenten mit useCssElement umwickelt sind
  3. Vergewissern Sie sich, dass die Metro-Konfiguration withNativewind verwendet

Plattformfarben funktionieren nicht

  1. Verwenden Sie platformColor() in @media ios-Blöcken
  2. Fallback auf light-dark() für Web/Android

TypeScript-Fehler

Fügen Sie className zu den Komponenten-Props hinzu:

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

Individual skills in this repo

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

Samueladewole/claude-skills

Create API handoff documentation for frontend developers. Use when backend work is complete and needs to be documented for frontend integration, or user says 'create handoff', 'document API', 'frontend handoff', or 'API documentation'.

Samueladewole/claude-skills

Build accessible, responsive, and performant frontend components with design system best practices, modern CSS, and framework-agnostic patterns.

Samueladewole/claude-skills

Document frontend data needs for backend developers. Use when frontend needs to communicate API requirements to backend, or user says 'backend requirements', 'what data do I need', 'API requirements', or is describing data needs for a UI.

Samueladewole/claude-skills

Master Next.js 14+ App Router with Server Components, streaming, parallel routes, and advanced data fetching. Use when building Next.js applications, implementing SSR/SSG, or optimizing React Server Components.

Samueladewole/claude-skills

Analyze a short-form video (TikTok, Instagram Reel, YouTube Short, or any MP4) to extract its full transcript, understand why it performed, and generate concrete repurposing ideas. Use this skill whenever the user asks to: analyze a video, understand why something went viral, reverse-engineer a creator's content, extract a transcript from a video, figure out what makes a video perform, or get repurposing ideas from a video. Also trigger when the user says things like "why did this perform", "what's the hook here", "can I repurpose this", "analyze this TikTok/Reel", or drops a video file path or URL.

Verwandte Skills