Modo Escuro e Esquemas de Cores
useColorScheme, paletas dinâmicas e sincronização com o sistema.
Busque em todas as páginas da documentação
useColorScheme, paletas dinâmicas e sincronização com o sistema.
Cartão de receita de referência rápida - pronto para copiar e colar.
import { useColorScheme, StyleSheet, Text, View } from "react-native";
const colors = {
light: { background: "#ffffff", text: "#0f172a", muted: "#64748b", border: "#e2e8f0" },
dark: { background: "#0f172a", text: "#f8fafc", muted: "#94a3b8", border: "#334155" },
} as const;
export function ThemedCard({ title, body }: { title: string; body: string }) {
const scheme = useColorScheme() ?? "light";
const theme = colors[scheme];
return (
<View style={[styles.card, { backgroundColor: theme.background, borderColor: theme.border }]}>
<Text style={[styles.title, { color: theme.text }]}>{title}</Text>
<Text style={[styles.body, { color: theme.muted }]}>{body}</Text>
</View>
);
}
const styles = StyleSheet.create({
card: { borderRadius: 12, borderWidth: 1, padding: 16, gap: 6 },
title: { fontSize: 17, fontWeight: "600" },
body: { fontSize: 15, lineHeight: 22 },
});Quando usar isso: Qualquer tela que precise respeitar a configuração clara/escura do sistema - ou oferecer uma substituição no aplicativo - sem manter duas árvores de componentes separadas.
import { useCallback, useMemo, useState } from "react";
import {
Appearance,
Pressable,
StyleSheet,
Text,
useColorScheme,
View,
} from "react-native";
import { StatusBar } from "expo-status-bar";
import { SafeAreaView } from "react-native-safe-area-context";
type Scheme = "light" | "dark";
type Preference = "system" | Scheme;
const palette = {
light: {
background: "#f8fafc",
surface: "#ffffff",
text: "#0f172a",
subtext: "#475569",
accent: "#2563eb",
border: "#cbd5e1",
},
dark: {
background: "#020617",
surface: "#0f172a",
text: "#f8fafc",
subtext: "#94a3b8",
accent: "#60a5fa",
border: "#334155",
},
} as const;
function useAppColorScheme(): Scheme {
const system = useColorScheme();
return system === "dark" ? "dark" : "light";
}
export default function SettingsAppearanceScreen() {
const scheme = useAppColorScheme();
const theme = palette[scheme];
const [preference, setPreference] = useState<Preference>("system");
const applyPreference = useCallback((next: Preference) => {
setPreference(next);
if (next === "system") {
Appearance.setColorScheme(null); // seguir o SO novamente
} else {
Appearance.setColorScheme(next);
}
}, []);
const styles = useMemo(() => makeStyles(theme), [theme]);
return (
<SafeAreaView style={styles.safe} edges={["top", "left", "right"]}>
<StatusBar style={scheme === "dark" ? "light" : "dark"} />
<Text style={styles.heading}>Aparência</Text>
<Text style={styles.subheading}>
O sistema é {scheme}. Preferência: {preference}.
</Text>
<View style={styles.card}>
{(["system", "light", "dark"] as const).map((option) => (
<Pressable
key={option}
onPress={() => applyPreference(option)}
style={[styles.row, preference === option && styles.rowActive]}
>
<Text style={styles.rowLabel}>{option}</Text>
{preference === option && <Text style={styles.check}>✓</Text>}
</Pressable>
))}
</View>
<View style={[styles.preview, { backgroundColor: theme.surface }]}>
<Text style={styles.previewTitle}>Cartão de pré-visualização</Text>
<Text style={styles.previewBody}>Tokens semânticos trocam com o esquema ativo.</Text>
</View>
</SafeAreaView>
);
}
function makeStyles(theme: (typeof palette)[Scheme]) {
return StyleSheet.create({
safe: { flex: 1, backgroundColor: theme.background, padding: 16, gap: 12 },
heading: { fontSize: 28, fontWeight: "700", color: theme.text },
subheading: { fontSize: 15, color: theme.subtext },
card: {
backgroundColor: theme.surface,
borderRadius: 12,
borderWidth: StyleSheet.hairlineWidth,
borderColor: theme.border,
overflow: "hidden",
},
row: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 16,
paddingVertical: 14,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: theme.border,
},
rowActive: { backgroundColor: theme.background },
rowLabel: { fontSize: 16, color: theme.text, textTransform: "capitalize" },
check: { color: theme.accent, fontWeight: "700" },
preview: {
borderRadius: 12,
padding: 16,
gap: 6,
borderWidth: StyleSheet.hairlineWidth,
borderColor: theme.border,
},
previewTitle: { fontSize: 17, fontWeight: "600", color: theme.text },
previewBody: { fontSize: 15, lineHeight: 22, color: theme.subtext },
});
}O que isso demonstra:
useColorScheme() lendo o esquema ativo do sistema ("light" | "dark" | null).background, surface, text) em vez de hexadecimais codificados em componentes.Appearance.setColorScheme() para uma alternância de preferência clara/escura/sistema no aplicativo.StatusBar de expo-status-bar alternando style com o esquema ativo.useMemo para reconstruir StyleSheet quando o tema muda.useColorScheme é um hook do React de react-native que se inscreve na configuração de aparência do SO. Ele retorna "light", "dark", ou null (desconhecido / indisponível) e re-renderiza quando o usuário alterna o modo escuro do sistema.Appearance - Appearance.getColorScheme() para leituras pontuais; Appearance.addChangeListener para inscrições manuais; Appearance.setColorScheme("dark" | "light" | null) para substituir o aplicativo independentemente do sistema (null restaura a sincronização com o sistema).textPrimary, borderSubtle) e não por valor (gray500). Componentes referenciam funções; a paleta mapeia funções para hexadecimais por esquema.userInterfaceStyle - Em app.json / app.config.js, "userInterfaceStyle": "automatic" segue o SO (padrão). "light" ou "dark" bloqueia o aplicativo independentemente da configuração do sistema.useColorScheme vs Appearance| API | Tipo | Usar para |
|---|---|---|
useColorScheme() | Hook - aciona re-renderização | Estilos de componentes, JSX temático |
Appearance.getColorScheme() | Instantâneo | Verificações únicas em nível de módulo (raro) |
Appearance.addChangeListener | Assinatura de evento | Caminhos de código não-React (prefira hook em componentes) |
Appearance.setColorScheme() | Substituição | Seletor de tema no aplicativo (sistema / claro / escuro) |
import { Appearance, useColorScheme } from "react-native";
// Em um componente - reativo
function Banner() {
const scheme = useColorScheme() ?? "light";
return <View style={{ backgroundColor: scheme === "dark" ? "#1e293b" : "#fff" }} />;
}
// Substituição para alternância no aplicativo
Appearance.setColorScheme("dark"); // forçar escuro
Appearance.setColorScheme(null); // seguir o sistema novamentePaleta plana (aplicativos pequenos)
const colors = {
light: { bg: "#fff", text: "#111", accent: "#2563eb" },
dark: { bg: "#111", text: "#f8fafc", accent: "#60a5fa" },
};Funções semânticas (recomendado)
const tokens = {
light: {
backgroundPrimary: "#ffffff",
textPrimary: "#0f172a",
borderDefault: "#e2e8f0",
},
dark: {
backgroundPrimary: "#0f172a",
textPrimary: "#f8fafc",
borderDefault: "#334155",
},
};Provedor de contexto (acesso compartilhado)
import { createContext, useContext, useMemo } from "react";
import { useColorScheme } from "react-native";
type Theme = (typeof palette)["light"];
const ThemeContext = createContext<Theme>(palette.light);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const scheme = useColorScheme() ?? "light";
const theme = useMemo(() => palette[scheme], [scheme]);
return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>;
}
export function useTheme() {
return useContext(ThemeContext);
}app.json do Expo{
"expo": {
"userInterfaceStyle": "automatic",
"ios": { "userInterfaceStyle": "automatic" },
"android": { "userInterfaceStyle": "automatic" }
}
}"automatic" - seguir o SO (padrão recomendado)."light" / "dark" - travar o aplicativo; useColorScheme ainda relata o valor forçado.import { ColorSchemeName, useColorScheme } from "react-native";
type Scheme = "light" | "dark";
function resolveScheme(name: ColorSchemeName): Scheme {
return name === "dark" ? "dark" : "light";
}
function useResolvedScheme(): Scheme {
return resolveScheme(useColorScheme());
}
// Paleta tipada - verificada exaustivamente
const palette: Record<Scheme, { background: string; text: string }> = {
light: { background: "#fff", text: "#111" },
dark: { background: "#111", text: "#fff" },
};ColorSchemeName é "light" | "dark" | null | undefined.null - use como padrão "light" ou o fallback do seu aplicativo.Cores codificadas em StyleSheet.create no escopo do módulo - Os estilos são congelados no momento do carregamento; eles não reagem a mudanças de esquema. Correção: Construa estilos dentro do componente com useMemo, ou aplique cores dinâmicas inline: style={[styles.card, { backgroundColor: theme.surface }]}.
Ignorar null de useColorScheme - Em alguns caminhos, o hook retorna null. Correção: const scheme = useColorScheme() ?? "light".
Barra de status clara em um fundo claro - Ícones da barra de status branco sobre branco desaparecem. Correção: StatusBar style="dark" em modo claro, style="light" em modo escuro.
Esquecer imagens e ícones - Logos e ilustrações projetados para fundos brancos parecem errados em superfícies escuras. Correção: Forneça variantes escuras @2x, use tintColor, ou envolva em um contêiner temático.
Appearance.setColorScheme sem persistir a preferência - A escolha do usuário é redefinida na reinicialização. Correção: Armazene a preferência em AsyncStorage / MMKV e aplique no lançamento do aplicativo antes da primeira renderização.
userInterfaceStyle: "light" bloqueado em app.json enquanto constrói UI escura - A configuração contradiz seu trabalho de tema. Correção: Use "automatic" a menos que você envie intencionalmente um aplicativo de esquema único.
Sobreposições semitransparentes - rgba(0,0,0,0.5) funciona no claro; em fundos escuros, a mesma sobreposição pode parecer lamacenta. Correção: Defina tokens de sobreposição separados por esquema.
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
useColorScheme + objeto de token | Padrão - zero dependências, controle total | Você precisa de temas complexos com breakpoints e media queries |
Contexto React ThemeProvider | Muitos componentes aninhados compartilham tokens | Telas únicas (ramificação inline é aceitável) |
Appearance.setColorScheme | Alternância clara/escura/sistema no aplicativo | Substituir definições de token - apenas altera o esquema ativo |
@react-navigation/native theme | O chrome de navegação corresponde às cores do aplicativo | UI não de navegação (ainda precisa de seus próprios tokens) |
react-native-unistyles / Tamagui / NativeWind | Escala de design system, tokens responsivos + escuros | Aplicativos simples com uma dúzia de cores |
expo-system-ui | Definir a cor de fundo raiz nativamente na mudança de esquema | Cores de texto em nível de componente |
"light", "dark", ou null. Trate null como desconhecido e use como fallback "light" (ou o padrão do seu aplicativo). O hook re-renderiza o componente quando a aparência do SO muda.
import { Appearance } from "react-native";
Appearance.setColorScheme("dark"); // forçar escuro
Appearance.setColorScheme("light"); // forçar claro
Appearance.setColorScheme(null); // seguir o sistemaPersista a escolha do usuário e reaplique no início frio. useColorScheme reflete o esquema efetivo após a substituição.
Chaves estáticas (fontSize, fontWeight, borderRadius) podem viver em um StyleSheet estático. Cores que mudam por esquema devem ser aplicadas dinamicamente - reconstrua os estilos com useMemo quando o esquema mudar, ou use style={[styles.card, { color: theme.text }]}.
Configuração do Expo que define a política de aparência em nível de aplicativo: "automatic" (seguir o SO), "light", ou "dark". Afeta a tela de splash e os padrões do shell nativo. Prefira "automatic" para a maioria dos aplicativos.
import { StatusBar } from "expo-status-bar";
<StatusBar style={scheme === "dark" ? "light" : "dark"} />Conteúdo claro (ícones brancos) em fundos escuros; conteúdo escuro em fundos claros.
Você provavelmente codificou cores em um StyleSheet.create de nível de módulo. Mova os valores de cor para uma paleta com chaves por esquema e aplique-os na renderização, ou reconstrua os estilos quando useColorScheme mudar.
Não - ramifique estilos, não árvores. Uma estrutura de componente com tokens de tema é mais fácil de manter do que JSX duplicado para cada esquema.
Componentes referenciam theme.text em vez de #0f172a. Quando você ajusta a paleta escura, cada tela é atualizada. Renomear gray-700 para uma função como textSecondary sobrevive a revisões de paleta.
Passe um theme e darkTheme para NavigationContainer usando sua paleta de tokens. O conteúdo da tela ainda precisa de seu próprio ramificação useColorScheme - o tema de navegação cobre apenas cabeçalhos, abas e cartões embutidos no navegador.
Defina backgroundColor do splash para seu token escuro em app.json. Use expo-system-ui para definir o fundo da view raiz. Aplique o tema antes de renderizar a primeira tela.
Appearance.getColorScheme() retorna o valor atual de forma síncrona. Para UI reativa, prefira useColorScheme em componentes ou Appearance.addChangeListener em módulos não-UI.
Sombras são menos visíveis em fundos escuros - confie mais em tokens de borda (borderColor: theme.border) e elevação sutil de superfície. Veja Sombras, Elevação e Bordas.
Use scheme === "dark" para trocar source, aplicar uma tintColor clara, ou colocar a imagem em um contêiner surface fixo que permaneça claro em ambos os esquemas (por exemplo, logo da marca).
Sim - é um hook padrão do React Native. Use-o em qualquer tela ou layout do cliente. Ele atualiza quando a aparência do sistema muda enquanto o aplicativo está em execução.
Os preenchimentos de área segura são independentes do esquema de cores, mas o contraste da barra de status depende de ambos. Coordene o estilo StatusBar com o fundo do seu cabeçalho. Veja Áreas Seguras e Entalhes.
Text e aninhamentoVersões da Stack: Esta página foi escrita para React 19.2.3, React Native 0.86.0 e Expo SDK 57 (
expo~57.0.4).
Revisado por Chris St. John·Última atualização: 19 de jul. de 2026