Communitygithub.com

Configuration Tailwind CSS v4 pour Expo avec react-native-css

Configurez Tailwind CSS v4 dans Expo en utilisant react-native-css et NativeWind v5 pour un style universel sur iOS, Android et Web.

Qu'est-ce que Configuration Tailwind CSS v4 pour Expo avec react-native-css ?

Cette compétence guide le développeur dans l'installation et la configuration de Tailwind CSS v4 au sein d'un projet Expo, en tirant parti de react-native-css comme moteur CSS natif et de NativeWind v5 comme plugin de transform. Elle explique en détail les étapes d'installation, la configuration des fichiers metro.config.js, postcss.config.mjs et du fichier global.css, ainsi que la création de wrappers de composants via `useCssElement` pour activer le support des classes CSS sur les composants React Native (View, Text, ScrollView, Image, etc.). La compétence met l'accent sur une approche CSS-first, éliminant le besoin d'un babel.config.js dédié à Tailwind, et propose des exemples concrets pour utiliser `@theme` pour des variables personnalisées, les media queries de plateforme pour des styles spécifiques à iOS ou Android, et l'intégration des couleurs système Apple grâce à des variables CSS et `platformColor()`. Elle est utile à tout développeur souhaitant unifier le style d'une application Expo avec Tailwind CSS v4 tout en conservant les performances et la flexibilité de react-native-css.

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

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Configuration Tailwind CSS pour Expo avec react-native-css

Ce guide couvre la configuration de Tailwind CSS v4 dans Expo en utilisant react-native-css et NativeWind v5 pour un style universel sur iOS, Android et Web.

Aperçu

Cette configuration utilise :

  • Tailwind CSS v4 - Configuration moderne basée sur CSS
  • react-native-css - Moteur CSS pour React Native
  • NativeWind v5 - Transformateur Metro pour Tailwind dans React Native
  • @tailwindcss/postcss - Plugin PostCSS pour Tailwind v4

Installation

# Installer les dépendances
npx expo install tailwindcss@^4 [email protected] [email protected] @tailwindcss/postcss tailwind-merge clsx

Ajouter les résolutions pour la compatibilité lightningcss :

// package.json
{
  "resolutions": {
    "lightningcss": "1.30.1"
  }
}
  • autoprefixer n'est pas nécessaire dans Expo à cause de lightningcss
  • postcss est inclus par défaut dans Expo

Fichiers de configuration

Configuration Metro

Créez ou mettez à jour 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, {
  // les variables en ligne cassent PlatformColor dans les variables CSS
  inlineVariables: false,
  // Nous ajoutons le support de className manuellement
  globalClassNamePolyfill: false,
});

Configuration PostCSS

Créez postcss.config.mjs :

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

CSS Global

Créez src/global.css :

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

/* Familles de polices spécifiques à la plateforme */
@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;
  }
}

IMPORTANT : Aucune configuration Babel nécessaire

Avec Tailwind v4 et NativeWind v5, vous n'avez PAS besoin d'un babel.config.js pour Tailwind. Supprimez tous les presets NativeWind Babel s'ils sont présents :

// SUPPRIMEZ babel.config.js s'il ne contient que la configuration NativeWind
// Ce qui suit n'est PLUS nécessaire :
// module.exports = function (api) {
//   api.cache(true);
//   return {
//     presets: [
//       ["babel-preset-expo", { jsxImportSource: "nativewind" }],
//       "nativewind/babel",
//     ],
//   };
// };

Wrappers de composants CSS

Puisque react-native-css nécessite un encapsulage explicite des éléments CSS, créez des composants réutilisables :

Composants principaux (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";

// Link compatible CSS
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;

// Crochet de variable 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 avec extraction de 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)";

Composant 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: Réaffectation du style objectFit à la propriété 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: Le style est réaffecté ci-dessus
      style={style}
    />
  );
}

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

Image.displayName = "CSS(Image)";

Composants animés (src/tw/animated.tsx)

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

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

Utilisation

Importez les composants enveloppés CSS depuis votre répertoire tw :

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

Variables de thème personnalisées

Ajoutez des variables de thème personnalisées dans votre global.css en utilisant @theme :

@layer theme {
  @theme {
    /* Polices personnalisées */
    --font-rounded: "SF Pro Rounded", sans-serif;

    /* Hauteurs de ligne personnalisées */
    --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);

    /* Échelles de hauteur de ligne personnalisées */
    --leading-tight: 1.25em;
    --leading-snug: 1.375em;
    --leading-normal: 1.5em;
  }
}

Styles spécifiques à la plateforme

Utilisez les media queries de plateforme pour un style spécifique à chaque plateforme :

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

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

Couleurs système Apple avec variables CSS

Créez un fichier CSS pour les couleurs sémantiques Apple :

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

:root {
  /* Couleurs d'accentuation avec mode clair/sombre */
  --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));

  /* Échelles de gris */
  --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));

  /* Couleurs de texte */
  --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));

  /* Couleurs de fond */
  --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));
}

/* Couleurs natives iOS 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);
  }
}

/* Enregistrer comme couleurs de thème 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);
  }
}

Utilisez ensuite dans les composants :

<Text className="text-sf-text">Texte principal</Text>
<Text className="text-sf-text-2">Texte secondaire</Text>
<View className="bg-sf-bg">...</View>

Utilisation des variables CSS en JavaScript

Utilisez le crochet useCSSVariable :

import { useCSSVariable } from "@/tw";

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

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

Différences clés par rapport à NativeWind v4 / Tailwind v3

  1. Pas de babel.config.js - La configuration est désormais basée sur CSS
  2. Plugin PostCSS - Utilise @tailwindcss/postcss au lieu de tailwindcss
  3. Importations CSS - Utilisez @import "tailwindcss/..." au lieu des directives @tailwind
  4. Configuration de thème - Utilisez @theme dans CSS au lieu de tailwind.config.js
  5. Wrappers de composants - Les composants doivent être encapsulés avec useCssElement pour le support de className
  6. Configuration Metro - Utilisez withNativewind avec des options différentes (inlineVariables: false)

Dépannage

Les styles ne s'appliquent pas

  1. Assurez-vous que le fichier CSS est importé dans l'entrée de votre application
  2. Vérifiez que les composants sont encapsulés avec useCssElement
  3. Vérifiez que la configuration Metro inclut withNativewind

Les couleurs de plateforme ne fonctionnent pas

  1. Utilisez platformColor() dans les blocs @media ios
  2. Revenez à light-dark() pour le web/Android

Erreurs TypeScript

Ajoutez className aux props du composant :

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.

Skills associés