Manuseio de Eventos & Touchables
Pressable, gestos na camada primitiva e padrões de hitSlop.
Busque em todas as páginas da documentação
Pressable, gestos na camada primitiva e padrões de hitSlop.
Cartão de receita de referência rápida - pronto para copiar e colar.
import { Pressable, StyleSheet, Text, View } from "react-native";
interface IconButtonProps {
label: string;
onPress: () => void;
disabled?: boolean;
}
export function IconButton({ label, onPress, disabled = false }: IconButtonProps) {
return (
<Pressable
onPress={onPress}
disabled={disabled}
hitSlop={12}
accessibilityRole="button"
accessibilityState={{ disabled }}
style={({ pressed }) => [
styles.button,
pressed && !disabled && styles.pressed,
disabled && styles.disabled,
]}
android_ripple={{ color: "rgba(255,255,255,0.25)", borderless: false }}
>
<Text style={styles.label}>{label}</Text>
</Pressable>
);
}
const styles = StyleSheet.create({
button: {
minHeight: 44,
minWidth: 44,
paddingHorizontal: 16,
borderRadius: 10,
backgroundColor: "#2563eb",
alignItems: "center",
justifyContent: "center",
},
pressed: { opacity: 0.85 },
disabled: { opacity: 0.4 },
label: { color: "#fff", fontWeight: "600", fontSize: 15 },
});Quando usar isso: Qualquer controle tocável - botões, linhas de lista, chips - onde você precisa de toques confiáveis, alvos acessíveis e feedback apropriado à plataforma.
import { useState } from "react";
import {
Platform,
Pressable,
StyleSheet,
Text,
View,
} from "react-native";
type Action = "save" | "share" | "delete";
const ACTIONS: { key: Action; label: string; destructive?: boolean }[] = [
{ key: "save", label: "Salvar rascunho" },
{ key: "share", label: "Compartilhar link" },
{ key: "delete", label: "Excluir", destructive: true },
];
export default function ActionSheetDemo() {
const [lastEvent, setLastEvent] = useState("Toque em uma ação");
const [pressedKey, setPressedKey] = useState<Action | null>(null);
return (
<View style={styles.screen}>
<Text style={styles.title}>Alvos de toque</Text>
<Text style={styles.subtitle}>{lastEvent}</Text>
<View style={styles.sheet}>
{ACTIONS.map((action) => (
<Pressable
key={action.key}
onPress={() => setLastEvent(`onPress: ${action.label}`)}
onPressIn={() => setPressedKey(action.key)}
onPressOut={() => setPressedKey(null)}
onLongPress={() => setLastEvent(`onLongPress: ${action.label}`)}
delayLongPress={400}
hitSlop={{ top: 6, bottom: 6, left: 4, right: 4 }}
accessibilityRole="button"
accessibilityHint={
action.destructive ? "Remove permanentemente o item" : undefined
}
style={({ pressed }) => [
styles.row,
pressed && styles.rowPressed,
action.destructive && styles.rowDestructive,
]}
android_ripple={{
color: action.destructive ? "rgba(239,68,68,0.2)" : "rgba(37,99,235,0.15)",
}}
>
<Text
style={[
styles.rowLabel,
action.destructive && styles.destructiveText,
pressedKey === action.key && styles.rowLabelPressed,
]}
>
{action.label}
</Text>
<Text style={styles.chevron}>›</Text>
</Pressable>
))}
</View>
<View style={styles.toolbar}>
<Pressable
onPress={() => setLastEvent("Ícone da barra de ferramentas tocado")}
hitSlop={16}
style={({ pressed }) => [styles.iconHit, pressed && styles.iconHitPressed]}
>
<View style={styles.iconGlyph} />
</Pressable>
<Text style={styles.toolbarHint}>
Ícone 24×24 com hitSlop de 16pt → alvo efetivo de 56×56
</Text>
</View>
{Platform.OS === "ios" && (
<Text style={styles.note}>
iOS: feedback de opacidade; Android: ripple via android_ripple
</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, padding: 20, backgroundColor: "#f8fafc", gap: 16 },
title: { fontSize: 22, fontWeight: "700", color: "#0f172a" },
subtitle: { fontSize: 15, color: "#475569" },
sheet: {
backgroundColor: "#fff",
borderRadius: 14,
overflow: "hidden",
borderWidth: StyleSheet.hairlineWidth,
borderColor: "#cbd5e1",
},
row: {
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 16,
paddingVertical: 14,
minHeight: 48,
},
rowPressed: { backgroundColor: "#f1f5f9" },
rowDestructive: {},
rowLabel: { flex: 1, fontSize: 16, color: "#0f172a" },
rowLabelPressed: { color: "#2563eb" },
destructiveText: { color: "#dc2626" },
chevron: { fontSize: 20, color: "#94a3b8" },
toolbar: { flexDirection: "row", alignItems: "center", gap: 12 },
iconHit: { padding: 4 },
iconHitPressed: { opacity: 0.6 },
iconGlyph: {
width: 24,
height: 24,
borderRadius: 6,
backgroundColor: "#2563eb",
},
toolbarHint: { flex: 1, fontSize: 13, color: "#64748b" },
note: { fontSize: 12, color: "#94a3b8" },
});O que isso demonstra:
Pressable reagindo ao estado pressed para feedback multiplataformaonPressIn / onPressOut para rastrear toques ativos sem disparar o toque completoonLongPress com delayLongPress para ações secundáriashitSlop ampliando alvos de ícones pequenos sem alterar as métricas de layoutandroid_ripple para ripples de toque no estilo Material no AndroidaccessibilityRole, accessibilityHint e accessibilityState em elementos interativosPressable rastreia os estados do ciclo de vida do toque (pressed, hovered, focused) e os mapeia para estilos ou funções de filhos.onPress dispara na liberação do toque dentro dos limites da view quando o gesto não é cancelado.disabled remove a view da cadeia de responder para ativação do toque.react-native-gesture-handler em vez das APIs de responder brutas.| Evento | Dispara quando | Uso Típico |
|---|---|---|
onPressIn | Dedo pressionado dentro da view | Destaque, preparação háptica |
onPressOut | Dedo liberado ou gesto cancelado | Limpar destaque |
onPress | Toque concluído (pressionado + liberado dentro) | Ação principal |
onLongPress | Mantido após delayLongPress (padrão 500ms) | Menu de contexto, confirmação de exclusão |
onPress não disparado | Scroll rouba o gesto, dedo desliza para fora, pai rola | Esperado - não lute contra o scroll |
// Expansão uniforme (número = todos os lados)
<Pressable hitSlop={12} />
// Controle por borda - útil acima de barras de abas ou sob entalhes
<Pressable hitSlop={{ top: 8, bottom: 16, left: 12, right: 12 }} />
// hitSlop negativo encolhe o alvo (raro - linhas sobrepostas)
<Pressable hitSlop={-4} />| Tamanho do Alvo | Mínimo Recomendado | Técnica |
|---|---|---|
| Link de texto | 44×44 pt efetivo | hitSlop + minHeight |
| Ícone de barra de ferramentas 24×24 | 44×44 pt efetivo | hitSlop={10} ou wrapper de padding |
| Linha de lista de largura total | 48dp de altura | paddingVertical em Pressable |
| Ação destrutiva | 44×44 + confirmação | onLongPress ou segunda etapa |
| Componente | Feedback | Status |
|---|---|---|
Pressable | Opacidade personalizada, ripple, função de filhos | Preferido |
TouchableOpacity | Diminui a opacidade ao tocar | Legado, ainda comum |
TouchableHighlight | Escurece o underlay | Legado |
TouchableWithoutFeedback | Nenhum | Evite para botões - lacunas de a11y |
// Camada primitiva - suficiente para botões e linhas de lista
<Pressable onPress={save} />
// Gesture Handler - pan, pinch, manipuladores simultâneos (instale no Expo)
import { Gesture, GestureDetector } from "react-native-gesture-handler";
const pan = Gesture.Pan().onEnd((e) => {
if (e.translationX < -80) archiveItem();
});Pressable até que conflitos de scroll ou requisitos de múltiplos toques apareçam.react-native-gesture-handler integra-se com drivers nativos para pans de 60fps.import type { PressableProps, StyleProp, ViewStyle } from "react-native";
import type { GestureResponderEvent } from "react-native";
type ButtonStyle = PressableProps["style"]; // StyleProp<ViewStyle> | fn
interface RowProps {
onPress: (event: GestureResponderEvent) => void;
style?: StyleProp<ViewStyle>;
}
// Forma de função de estilo
const styleFn: Extract<ButtonStyle, Function> = ({ pressed, hovered }) => [
styles.base,
pressed && styles.pressed,
];PressableProps["style"] cobre estilos estáticos e a forma de callback pressed.GestureResponderEvent tipa metadados de toque nativos (nativeEvent.locationX).() => void em props quando você não precisar do objeto de evento.Alvos de toque minúsculos - Um ícone de 20×20 sem hitSlop falha nos mínimos do HIG/Material e frustra os usuários. Correção: Adicione hitSlop ou envolva com padding para atingir ~44×44 pt.
ScrollView rouba toques - Deslizes horizontais em um Pressable dentro de ScrollView podem cancelar onPress. Correção: Ajuste keyboardShouldPersistTaps, use delayPressIn ou mova o gesto para Gesture Handler.
TouchableOpacity envolvendo grandes árvores - Diminuir a opacidade em uma subárvore enorme pisca todos os filhos. Correção: Aplique feedback apenas na linha Pressable, não no wrapper da tela inteira.
Feedback desabilitado ausente - disabled silencia onPress, mas os visuais podem parecer ativos. Correção: Combine estilo disabled, accessibilityState={{ disabled: true }} e pointerEvents onde necessário.
Pressables sobrepostos brigam - Overlays absolutos interceptam toques não intencionalmente. Correção: Defina pointerEvents="box-none" em pais decorativos ou pointerEvents="none" em overlays.
Confiar em onPress para detecção de arrastar - Press dispara após a liberação; gestos de arrastar precisam de manipuladores de pan. Correção: Use react-native-gesture-handler Pan para deslizar para excluir.
Ripple do Android sem overflow hidden - Ripples vazam para fora de cards arredondados. Correção: Defina overflow: "hidden" no card e combine borderRadius em android_ripple.
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
Pressable | Botões padrão, linhas de lista, chips | Gestos complexos com múltiplos dedos |
TouchableOpacity | Manter código legado rapidamente | Greenfield - prefira Pressable |
Text onPress | Links inline dentro de cópias | CTAs primários que precisam de alvos de 44pt |
react-native-gesture-handler | Linhas deslizantes, drawers, zoom de pinça | Botões estáticos simples |
Button Nativo | Ações triviais com estilo de sistema | Estilos de marca personalizados |
expo-haptics + Pressable | Confirmação tátil em sucesso | Cada toque - causa fadiga háptica |
Pressable expõe os estados pressed, hovered e focused em uma função de estilo.android_ripple, hitSlop e props de acessibilidade integrados são de primeira classe.TouchableOpacity sempre anima a opacidade - menos controle e maior superfície de re-renderização em árvores grandes.ScrollView pai antes de liberar.disabled={true} está definido no Pressable.<Pressable
android_ripple={{ color: "rgba(0,0,0,0.12)" }}
style={({ pressed }) => [styles.btn, pressed && styles.btnPressed]}
/>android_ripple é ignorado no iOS - seguro para usar em arquivos multiplataforma.pressed (tipicamente menor opacidade ou mudança de fundo).delayPressIn (padrão 0) pode ser aumentado por pais ou retenção de toque do ScrollView.onPressIn bloqueia o próximo frame.onPress ou requestAnimationFrame; mostre o destaque imediatamente.onLongPress dispare (padrão 500).onPress ainda dispara em toques rápidos, a menos que o manipulador de long-press consuma o gesto.auto (padrão) - a view e seus filhos recebem toques.none - a view e seus filhos são transparentes aos toques.box-none - a view ignora toques; os filhos ainda os recebem.box-only - a view recebe toques; os filhos não.Pressable para código novo - estado mais claro e controle de ripple.TouchableHighlight pode piscar incorretamente em listas virtualizadas durante scroll rápido.onPress por item.const [submitting, setSubmitting] = useState(false);
const onSubmit = async () => {
if (submitting) return;
setSubmitting(true);
try {
await save();
} finally {
setSubmitting(false);
}
};
<Pressable onPress={onSubmit} disabled={submitting} />Pressable com nós Text filhos.accessibilityRole="button" marca o elemento para leitores de tela.accessibilityLabel quando o texto visível é insuficiente (apenas ícone).accessibilityState={{ disabled, selected, busy }} espelha o estado visual.accessibilityHint para resultados destrutivos ou não óbvios.GestureDetector para casos avançados.Pressable executam na thread JavaScript (Hermes).react-native-reanimated podem permanecer na thread de UI separadamente.TextInput, keyboardShouldPersistTaps="handled" permite que toques em Pressable disparem sem fechar o teclado primeiro.hitSlop - ambos resolvem problemas diferentes.hitSlop modesto.Pressable; ajuste quando os usuários relatarem cancelamentos por "deslizar para fora".onPressVersõ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: 16 de jul. de 2026