Communitygithub.com

Samueladewole/claude-skills

Configure o Tailwind CSS v4 no Expo usando react-native-css e NativeWind v5 para estilização universal.

O que é claude-skills?

Esta skill configura o Tailwind CSS v4 em projetos Expo, utilizando react-native-css e NativeWind v5 para oferecer estilização universal entre iOS, Android e Web. Ela abrange desde a instalação das dependências necessárias até a criação de componentes CSS customizados que permitem o uso de classes de utilitário do Tailwind diretamente em elementos React Native, eliminando a necessidade de um arquivo `babel.config.js`. A configuração é baseada em CSS, com suporte a variáveis de tema customizadas, cores do sistema Apple via `platformColor()` e media queries específicas por plataforma. Além disso, fornece wrappers reutilizáveis para componentes comuns como View, Text, Pressable e Image, facilitando a adoção do Tailwind v4 em projetos Expo modernos.

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

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Configuração do Tailwind CSS para Expo com react-native-css

Este guia cobre a configuração do Tailwind CSS v4 no Expo usando react-native-css e NativeWind v5 para estilização universal em iOS, Android e Web.

Visão Geral

Esta configuração utiliza:

  • Tailwind CSS v4 - Configuração moderna baseada em CSS
  • react-native-css - Runtime CSS para React Native
  • NativeWind v5 - Transformador Metro para Tailwind no React Native
  • @tailwindcss/postcss - Plugin PostCSS para Tailwind v4

Instalação

# Instalar dependências
npx expo install tailwindcss@^4 [email protected] [email protected] @tailwindcss/postcss tailwind-merge clsx

Adicione resoluções para compatibilidade com lightningcss:

// package.json
{
  "resolutions": {
    "lightningcss": "1.30.1"
  }
}
  • autoprefixer não é necessário no Expo por causa do lightningcss
  • postcss já está incluído no Expo por padrão

Arquivos de Configuração

Configuração do Metro

Crie ou atualize 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, {
  // variáveis inline quebram PlatformColor em variáveis CSS
  inlineVariables: false,
  // Adicionamos suporte a className manualmente
  globalClassNamePolyfill: false,
});

Configuração do PostCSS

Crie postcss.config.mjs:

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

CSS Global

Crie src/global.css:

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

/* Famílias de fonte específicas por plataforma */
@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;
  }
}

IMPORTANTE: Configuração Babel não Necessária

Com Tailwind v4 e NativeWind v5, você NÃO precisa de um babel.config.js para Tailwind. Remova quaisquer presets NativeWind do Babel se existirem:

// DELETE babel.config.js se ele só contiver configuração NativeWind
// O seguinte NÃO é mais necessário:
// module.exports = function (api) {
//   api.cache(true);
//   return {
//     presets: [
//       ["babel-preset-expo", { jsxImportSource: "nativewind" }],
//       "nativewind/babel",
//     ],
//   };
// };

Wrappers de Componentes CSS

Como o react-native-css exige encapsulamento explícito de elementos CSS, crie componentes reutilizáveis:

Componentes Principais (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 habilitado para 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;

// Hook de variável 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 com extração 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)";

Componente de Imagem (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: Reatribui o estilo objectFit para a propriedade 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: O estilo é reatribuído acima
      style={style}
    />
  );
}

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

Image.displayName = "CSS(Image)";

Componentes Animados (src/tw/animated.tsx)

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

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

Uso

Importe os componentes encapsulados com CSS do seu diretório 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">Olá Tailwind!</Text>
        <Image
          className="w-full h-48 rounded-lg object-cover"
          source={{ uri: "https://exemplo.com/imagem.jpg" }}
        />
      </View>
    </ScrollView>
  );
}

Variáveis de Tema Customizadas

Adicione variáveis de tema customizadas no seu global.css usando @theme:

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

    /* Alturas de linha customizadas */
    --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);

    /* Escalas de leading customizadas */
    --leading-tight: 1.25em;
    --leading-snug: 1.375em;
    --leading-normal: 1.5em;
  }
}

Estilos Específicos por Plataforma

Use media queries de plataforma para estilização específica:

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

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

Cores do Sistema Apple com Variáveis CSS

Crie um arquivo CSS para cores semânticas da Apple:

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

:root {
  /* Cores de destaque com modo claro/escuro */
  --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));

  /* Escalas de cinza */
  --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));

  /* Cores de texto */
  --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));

  /* Cores de fundo */
  --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));
}

/* Cores nativas do 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);
  }
}

/* Registrar como cores do tema 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);
  }
}

Depois use nos componentes:

<Text className="text-sf-text">Texto primário</Text>
<Text className="text-sf-text-2">Texto secundário</Text>
<View className="bg-sf-bg">...</View>

Usando Variáveis CSS no JavaScript

Use o hook useCSSVariable:

import { useCSSVariable } from "@/tw";

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

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

Principais Diferenças do NativeWind v4 / Tailwind v3

  1. Sem babel.config.js - A configuração agora é baseada em CSS
  2. Plugin PostCSS - Usa @tailwindcss/postcss em vez de tailwindcss
  3. Imports CSS - Use @import "tailwindcss/..." em vez de diretivas @tailwind
  4. Configuração de tema - Use @theme no CSS em vez de tailwind.config.js
  5. Wrappers de componentes - Deve encapsular componentes com useCssElement para suporte a className
  6. Configuração do Metro - Use withNativewind com opções diferentes (inlineVariables: false)

Solução de Problemas

Estilos não estão sendo aplicados

  1. Certifique-se de que o arquivo CSS foi importado na entrada do seu app
  2. Verifique se os componentes estão encapsulados com useCssElement
  3. Confirme que a configuração do Metro tem withNativewind aplicado

Cores de plataforma não estão funcionando

  1. Use platformColor() em blocos @media ios
  2. Recorra a light-dark() para web/Android

Erros de TypeScript

Adicione className às props do componente:

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.

Habilidades Relacionadas