La página de contacto suele ser el último paso antes de que un lead se convierta en una conversación. Un formulario recargado o intimidante mata las conversiones. Uno limpio y bien estructurado, con etiquetas claras, valores por defecto inteligentes y feedback visual, facilita que los visitantes se pongan en contacto.
Esta guía cubre cinco patrones de formularios de contacto en React con Tailwind CSS, desde un formulario simple de tres campos hasta un asistente de varios pasos con indicadores de progreso.
1. El formulario de contacto simple
Tres campos, un botón. Nombre, email, mensaje. Esto es todo lo que la mayoría de los negocios necesitan.
"use client";
import { useState } from "react";
interface FormData {
name: string;
email: string;
message: string;
}
function ContactForm() {
const [form, setForm] = useState<FormData>({
name: "",
email: "",
message: "",
});
function handleChange(
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) {
setForm({ ...form, [e.target.name]: e.target.value });
}
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
const res = await fetch("/api/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(form),
});
if (res.ok) {
setForm({ name: "", email: "", message: "" });
}
}
return (
<form onSubmit={handleSubmit} className="mx-auto max-w-lg space-y-6">
<div>
<label htmlFor="name" className="block text-sm font-medium mb-2">
Name
</label>
<input
id="name"
name="name"
type="text"
required
value={form.name}
onChange={handleChange}
className="w-full rounded-lg border border-neutral-300 px-4 py-2.5 text-sm focus:border-neutral-900 focus:outline-none focus:ring-1 focus:ring-neutral-900 dark:border-neutral-700 dark:bg-neutral-900 dark:focus:border-white dark:focus:ring-white"
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium mb-2">
Email
</label>
<input
id="email"
name="email"
type="email"
required
value={form.email}
onChange={handleChange}
className="w-full rounded-lg border border-neutral-300 px-4 py-2.5 text-sm focus:border-neutral-900 focus:outline-none focus:ring-1 focus:ring-neutral-900 dark:border-neutral-700 dark:bg-neutral-900 dark:focus:border-white dark:focus:ring-white"
/>
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium mb-2">
Message
</label>
<textarea
id="message"
name="message"
rows={5}
required
value={form.message}
onChange={handleChange}
className="w-full rounded-lg border border-neutral-300 px-4 py-2.5 text-sm focus:border-neutral-900 focus:outline-none focus:ring-1 focus:ring-neutral-900 dark:border-neutral-700 dark:bg-neutral-900 dark:focus:border-white dark:focus:ring-white resize-none"
/>
</div>
<button
type="submit"
className="w-full rounded-lg bg-neutral-900 py-2.5 text-sm font-semibold text-white hover:bg-neutral-700 transition-colors dark:bg-white dark:text-neutral-900 dark:hover:bg-neutral-200"
>
Send message
</button>
</form>
);
}
Cada input tiene su <label> con un par htmlFor/id que coincide. Esto no es negociable de cara a la accesibilidad: al hacer clic en la etiqueta, el input debe recibir el foco. El atributo required aporta validación nativa del navegador antes de enviar el formulario.
Principios de diseño de formularios
Antes de explorar más patrones, estas son las reglas de diseño que se aplican a cualquier formulario de contacto:
Etiqueta encima del campo, no al lado. Las etiquetas alineadas arriba se leen más rápido que las laterales. Los estudios de seguimiento ocular muestran que los usuarios leen los formularios con etiquetas superiores un 50 % más rápido.
Una sola columna. Los formularios a dos columnas hacen que los usuarios se salten campos. La única excepción es nombre / apellidos en la misma fila, un patrón que todo el mundo entiende.
El botón debe describir la acción. "Enviar mensaje" es mejor que "Enviar". "Solicitar una llamada" es mejor que "Enviar". La etiqueta debe coincidir con lo que ocurre a continuación.
Muestra confirmación de éxito. Tras el envío, sustituye el formulario por un mensaje de confirmación o muestra una notificación toast. Nunca dejes al usuario con la duda de si el formulario llegó o no.
2. Diseño dividido: formulario + información
El patrón de página de contacto más popular para webs de empresa. La columna izquierda lleva el formulario; la derecha, la información de contacto (dirección, teléfono, email) y el horario de oficina.
Usa grid md:grid-cols-2 gap-12 para el layout. La columna de información ayuda a los visitantes que prefieren el email o el teléfono al formulario, y aporta credibilidad al mostrar una dirección física.
Añade enlaces sociales en la columna de información si el negocio tiene cuentas activas. Así das a los visitantes una tercera vía de contacto y transmites que la empresa es cercana.
3. Formulario de contacto con mapa
Para negocios con ubicación física, incrustar un mapa junto al formulario aporta contexto. El mapa puede ir encima del formulario (a todo el ancho) o al lado (diseño dividido).
Usa una imagen de mapa estático de Mapbox o de la Google Maps Static API en lugar de un embed interactivo. El iframe interactivo de Google Maps carga más de 500 KB de JavaScript, lo que perjudica el rendimiento de la página. Una imagen estática con un enlace a Google Maps ofrece el mismo valor a una fracción del coste.
Si de verdad necesitas un mapa interactivo, carga el iframe de forma diferida (lazy-load) para que solo se cargue cuando sea visible:
<iframe
src={`https://www.google.com/maps/embed/v1/place?key=${apiKey}&q=${encodeURIComponent(address)}`}
className="h-64 w-full rounded-xl border-0"
loading="lazy"
allowFullScreen
/>
4. Etiquetas flotantes
Las etiquetas flotantes empiezan dentro del campo como texto de placeholder y se animan hacia arriba cuando el usuario enfoca o escribe. Esto ahorra espacio vertical sin perder un etiquetado claro.
El truco de CSS: usa las utilidades peer y peer-placeholder-shown de Tailwind para controlar la posición de la etiqueta según el estado del input:
<div className="relative">
<input
id="email"
name="email"
type="email"
placeholder=" "
required
className="peer w-full rounded-lg border border-neutral-300 px-4 pt-5 pb-2 text-sm focus:border-neutral-900 focus:outline-none focus:ring-1 focus:ring-neutral-900"
/>
<label
htmlFor="email"
className="absolute left-4 top-2 text-xs text-neutral-500 transition-all peer-placeholder-shown:top-3.5 peer-placeholder-shown:text-sm peer-focus:top-2 peer-focus:text-xs peer-focus:text-neutral-900"
>
Email address
</label>
</div>
La clave está en placeholder=" " (un solo espacio). El estado peer-placeholder-shown es verdadero cuando el placeholder está visible (el input está vacío y sin foco), lo que coloca la etiqueta como si fuera un placeholder. Cuando el usuario escribe o enfoca, la etiqueta sube flotando.
Este patrón es elegante, pero tiene una contrapartida: el movimiento de la etiqueta puede confundir a algunos usuarios, y el texto más pequeño de la etiqueta en la parte superior se lee peor para personas con dificultades visuales. Úsalo en sitios donde prime el diseño; quédate con las etiquetas superiores estándar en formularios donde la accesibilidad sea crítica.
5. Formulario de varios pasos
Para formularios con muchos campos (candidaturas de empleo, consultas detalladas, onboarding), un asistente de varios pasos reduce la carga cognitiva al mostrar solo unos pocos campos cada vez.
Estructura el formulario como un array de pasos, cada uno con sus propios campos y validación. Un indicador de progreso en la parte superior muestra el paso actual:
const steps = [
{ title: "Your info", fields: ["name", "email", "phone"] },
{ title: "Project details", fields: ["budget", "timeline", "description"] },
{ title: "Review & send", fields: [] },
];
Navega entre pasos con los botones "Siguiente" y "Atrás". Valida los campos del paso actual antes de dejar avanzar al usuario. El paso final muestra un resumen de todos los datos introducidos, con un botón de edición para cada sección.
Mantén el total de pasos en tres o cuatro. Más allá de eso aparece la fatiga del formulario. Cada paso debería completarse en menos de 30 segundos.
Validación y estados de error
La validación del lado del cliente debería ocurrir al perder el foco (cuando el usuario sale de un campo), no al cambiar (mientras sigue escribiendo). Mostrar errores en mitad de la pulsación de teclas resulta frustrante.
Da estilo a los estados de error con un borde rojo y un mensaje de error debajo del campo:
<input
className={`w-full rounded-lg border px-4 py-2.5 text-sm ${
error
? "border-red-500 focus:border-red-500 focus:ring-red-500"
: "border-neutral-300 focus:border-neutral-900 focus:ring-neutral-900"
}`}
/>
{error && <p className="mt-1 text-xs text-red-500">{error}</p>}
Usa aria-invalid="true" y aria-describedby apuntando al mensaje de error para dar soporte a los lectores de pantalla. Los atributos nativos required, type="email" y minLength aportan una primera capa de validación gratuita.
Prevención de spam
Los formularios de contacto atraen bots. Tres contramedidas eficaces:
- Campo honeypot: añade un campo oculto. Los bots lo rellenan; los humanos no. Rechaza los envíos en los que el campo oculto tenga valor.
- Comprobación temporal: registra cuándo se cargó la página. Rechaza los envíos que ocurran en menos de 2 segundos, porque una persona no puede rellenar un formulario tan rápido.
- reCAPTCHA v3 o Turnstile: retos invisibles que puntúan el envío. Déjalo como último recurso, ya que añade una dependencia externa.
Secciones de contacto listas para usar
Construir formularios accesibles y responsive con validación, estados de error y prevención de spam lleva tiempo. El catálogo de contacto de Incubator tiene más de 10 secciones de formulario de contacto listas para usar: simples, divididas, con mapa, con etiquetas flotantes, de varios pasos, todas construidas en React con Tailwind CSS.
Explora la biblioteca de componentes completa para cada sección que tu sitio necesite, desde secciones de FAQ hasta testimonios.
Relacionado en incubator
- Secciones de contacto en React: diseños de formulario de contacto listos para usar en React.
- Componentes de sección FAQ: combina los formularios de contacto con una FAQ por encima de la línea de flotación.
- Componentes de sección CTA: impulsa los envíos del formulario con secciones de llamada a la acción de alta conversión.
- Explorador de efectos hover: patrones interactivos de hover para botones e inputs de formulario.
- Incubator vs shadcn/ui: cómo se comparan las secciones prediseñadas con una biblioteca de componentes.