NativeWind (Tailwind para RN)
Clases de utilidad, modo oscuro y configuración de compilación en Expo.
Busca en todas las páginas de la documentación
Clases de utilidad, modo oscuro y configuración de compilación en Expo.
NativeWind aporta la ergonomía de Tailwind CSS a React Native: className="flex-1 p-4 bg-white dark:bg-slate-900" en lugar de objetos StyleSheet extensos. En Expo SDK 57, NativeWind v4 se conecta con Metro y compila utilidades a estilos RN en tiempo de compilación - no un analizador CSS en tiempo de ejecución en el dispositivo.
Tarjeta de referencia rápida - lista para copiar y pegar.
npx create-expo-app@latest MyWindApp --template blank-typescript
cd MyWindApp
npx expo install nativewind tailwindcss@^3.4.0 react-native-reanimated react-native-safe-area-context
npm install --save-dev prettier-plugin-tailwindcss
npx tailwindcss init// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./app/**/*.{js,jsx,ts,tsx}", "./components/**/*.{js,jsx,ts,tsx}"],
presets: [require("nativewind/preset")],
theme: {
extend: {
colors: {
brand: { DEFAULT: "#2563eb", muted: "#93c5fd" },
},
},
},
plugins: [],
};/* global.css */
@tailwind base;
@tailwind components;
@tailwind utilities;// metro.config.js
const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require("nativewind/metro");
const config = getDefaultConfig(__dirname);
module.exports = withNativeWind(config, { input: "./global.css" });// nativewind-env.d.ts
/// <reference types="nativewind/types" />// app/_layout.tsx (Expo Router) o App.tsx
import "../global.css";
import { Stack } from "expo-router";
export default function RootLayout() {
return <Stack screenOptions={{ headerShown: false }} />;
}// components/Card.tsx
import { Text, View } from "react-native";
export function Card({ title, body }: { title: string; body: string }) {
return (
<View className="rounded-xl border border-slate-200 bg-white p-4 dark:border-slate-700 dark:bg-slate-900">
<Text className="text-base font-semibold text-slate-900 dark:text-slate-50">{title}</Text>
<Text className="mt-1 text-sm text-slate-600 dark:text-slate-400">{body}</Text>
</View>
);
}Cuándo usarlo:
StyleSheet está ralentizando la velocidad.dark: vinculadas al esquema del sistema sin mantener manualmente dos paletas por componente.md:, lg:) para diseños de tableta junto a los valores predeterminados de teléfono.Pantalla de configuración con modo oscuro del sistema, tokens de marca semántica y un botón con estado presionado.
// app/settings.tsx
import { Pressable, ScrollView, Text, useColorScheme, View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { StatusBar } from "expo-status-bar";
const ROWS = [
{ id: "profile", label: "Editar perfil" },
{ id: "notifications", label: "Notificaciones" },
{ id: "privacy", label: "Privacidad" },
] as const;
export default function SettingsScreen() {
const scheme = useColorScheme() ?? "light";
return (
<SafeAreaView className="flex-1 bg-slate-50 dark:bg-slate-950">
<StatusBar style={scheme === "dark" ? "light" : "dark"} />
<ScrollView contentContainerClassName="gap-3 p-4">
<Text className="text-2xl font-bold text-slate-900 dark:text-slate-50">Configuración</Text>
<View className="overflow-hidden rounded-xl border border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-900">
{ROWS.map((row) => (
<Pressable
key={row.id}
accessibilityRole="button"
className="border-b border-slate-100 px-4 py-3.5 active:bg-slate-100 dark:border-slate-800 dark:active:bg-slate-800"
>
<Text className="text-base text-slate-900 dark:text-slate-100">{row.label}</Text>
</Pressable>
))}
</View>
<Pressable
accessibilityRole="button"
className="items-center rounded-xl bg-brand px-4 py-3.5 active:opacity-80"
>
<Text className="text-base font-semibold text-white">Guardar cambios</Text>
</Pressable>
</ScrollView>
</SafeAreaView>
);
}// tailwind.config.js - darkMode para utilidades dark: basadas en clase
module.exports = {
content: ["./app/**/*.{js,jsx,ts,tsx}", "./components/**/*.{js,jsx,ts,tsx}"],
presets: [require("nativewind/preset")],
darkMode: "class",
theme: {
extend: {
colors: {
brand: { DEFAULT: "#2563eb", foreground: "#ffffff" },
},
},
},
};// providers/ThemeModeProvider.tsx - invalidación opcional en la aplicación
import { createContext, useContext, useEffect, useState } from "react";
import { Appearance, useColorScheme } from "react-native";
import { colorScheme } from "nativewind";
type Mode = "light" | "dark" | "system";
const ThemeModeContext = createContext<{ mode: Mode; setMode: (m: Mode) => void }>({
mode: "system",
setMode: () => {},
});
export function ThemeModeProvider({ children }: { children: React.ReactNode }) {
const system = useColorScheme();
const [mode, setMode] = useState<Mode>("system");
useEffect(() => {
if (mode === "system") {
Appearance.setColorScheme(null);
colorScheme.set(system === "dark" ? "dark" : "light");
} else {
Appearance.setColorScheme(mode);
colorScheme.set(mode);
}
}, [mode, system]);
return (
<ThemeModeContext.Provider value={{ mode, setMode }}>{children}</ThemeModeContext.Provider>
);
}
export function useThemeMode() {
return useContext(ThemeModeContext);
}Esto demuestra:
withNativeWind en Metro compila global.css y conecta className en primitivas RN.dark: responden a la API colorScheme de NativeWind sincronizada con useColorScheme.contentContainerClassName en ScrollView aplica utilidades al envoltorio de contenido de desplazamiento.brand semántico en theme.extend - las pantallas usan bg-brand, no hex sin procesar.active: pseudo-utilidad para retroalimentación de presión sin estado StyleSheet en línea.nativewind/preset mapea utilidades Tailwind web a propiedades compatibles con RN - CSS no compatible se elimina o advierte en dev.className no es una prop nativa - NativeWind parchea componentes admitidos (View, Text, Pressable, etc.) mediante el jsxImportSource o el plugin Babel según la configuración.darkMode: "class", llama a colorScheme.set("dark") desde nativewind cuando anulez la apariencia del sistema; las clases dark: se activan en consecuencia.tailwind.config.js theme.extend; las pantallas consumen utilidades, no valores arbitrarios únicos como text-[#334155].| Aspecto | StyleSheet + tokens | NativeWind |
|---|---|---|
| Curva de aprendizaje | Nativo de RN | Vocabulario Tailwind |
| Sobrecarga del paquete | Mínima | Configuración + piso del compilador |
| Modo oscuro | Objeto de tema manual | Variantes dark: |
| Rutas calientes de lista | Referencias estables fáciles | Mantener cadenas de clase estáticas |
| Entrega del diseñador | Tabla de tokens | Especificación de clase de utilidad |
// nativewind-env.d.ts
/// <reference types="nativewind/types" />className esté tipado en componentes RN.className, usa cssInterop de NativeWind para mapear className - style.renderItem de lista - los literales de plantilla dinámica anulan la extracción en tiempo de compilación.// app/_layout.tsx
import "../global.css";
import { ThemeModeProvider } from "../providers/ThemeModeProvider";
import { Stack } from "expo-router";
export default function RootLayout() {
return (
<ThemeModeProvider>
<Stack />
</ThemeModeProvider>
);
}Importa global.css una vez en el diseño raíz - las rutas secundarias heredan utilidades compiladas.
Rutas content faltantes - Tailwind purga clases no utilizadas; si ./app/** se omite, los estilos desaparecen silenciosamente. Solución: Incluye todas las carpetas con cadenas className.
Olvidaste importar global.css - Las utilidades nunca se registran. Solución: Importa en la raíz App.tsx o app/_layout.tsx.
La caché de Metro se vuelve obsoleta después del cambio de configuración - Los estilos antiguos persisten. Solución: npx expo start --clear después de editar tailwind.config.js.
Valores arbitrarios en todas partes - p-[13px] omite tu escala. Solución: Extiende theme.spacing y refuerza en la revisión de código.
Concatenación dinámica de clases en listas - `bg-${color}-500` rompe la extracción en tiempo de compilación. Solución: Usa un mapa fijo: const bg = { red: "bg-red-500", blue: "bg-blue-500" }.
dark: sin sincronizar colorScheme - El interruptor en la aplicación cambia Appearance pero no NativeWind. Solución: Llama a colorScheme.set() en tu proveedor de tema.
TextInput de terceros ignora className - No todos los componentes están parcheados. Solución: cssInterop(TextInput, { className: "style" }) en un archivo de configuración.
| Alternativa | Usar cuándo | No usar cuándo |
|---|---|---|
| NativeWind v4 | Equipo Tailwind, muchas pantallas, variantes oscuras | Aplicación de dos pantallas con cinco estilos |
| StyleSheet + tokens | Mínimas dependencias, control total | Los diseñadores exigen flujo de trabajo de utilidades |
| Tamagui | Tokens compilados + componentes + web | Solo deseas cadenas de clase, no un kit de componentes |
| React Native Paper | Componentes Material listos para usar | Marca personalizada sin relación con MD3 |
| Unistyles | Temas en tiempo de ejecución con puntos de quiebre | El equipo no tiene experiencia en bibliotecas de estilo RN |
Sí. Instala mediante npx expo install nativewind tailwindcss react-native-reanimated, envuelve Metro con withNativeWind e importa global.css en la raíz de la aplicación. Borra la caché de Metro después de cambios de configuración.
En tailwind.config.js bajo theme.extend - colores, espaciado, fontSize, borderRadius. Las pantallas hacen referencia a text-brand, p-md, no valores sin procesar. Alinea nombres con Fundamentos de Sistemas de Diseño.
Configura darkMode: "class" y usa utilidades dark:bg-slate-900. Sincroniza con el sistema a través de useColorScheme, o anula con colorScheme.set("dark") desde nativewind cuando el usuario elige un tema en la aplicación. Ver Modo Oscuro y Esquemas de Color.
Sí. Importa global.css en app/_layout.tsx. Añade ./app/**/* a las rutas de content de Tailwind.
Trata className como una capa base y pasa style para valores verdaderamente dinámicos (ancho animado). Evita duplicar la misma propiedad en ambos.
NativeWind apunta a vistas de React Native. Expo web puede renderizar algunas utilidades, pero valida el diseño por separado - los problemas específicos de web pertenecen a Fundamentos de Estilo.
Carga fuentes con expo-font, luego mapea fontFamily en tailwind.config.js theme.extend. Ver Escala tipográfica y carga de fuentes.
Mantén cadenas estáticas className en componentes de fila. Extrae filas a hijos memo(). Perfila antes de asumir que las utilidades son gratuitas - ver Rendimiento de Estilo.
AppearanceaccessibilityRole en presionables con estiloVersiones de Stack: Esta página fue escrita para React 19.2.3, React Native 0.86.0 y Expo SDK 57 (
expo~57.0.4).
Revisado por Chris St. John·Última actualización: 16 jul 2026