Cómo crear una página de login split-screen en React
Una página de login split-screen en React usa CSS Grid con dos columnas iguales: un panel izquierdo para la identidad de marca y un panel derecho para el formulario. El panel del formulario entra con un fundido y deslizamiento mediante un spring de Framer Motion al montarse, lo que mantiene la entrada fluida sin una transición de página completa.
- Stack: React + Framer Motion + Tailwind v4, ~130 líneas, cero dependencias adicionales.
- Layout: lg:grid-cols-2, panel izquierdo oculto en mobile, formulario a todo el ancho por debajo del breakpoint lg.
- Animación de entrada: opacity 0→1 + y 20→0 en 600ms con un cubic-bezier spring (0.16, 1, 0.3, 1).
- Props: showSocial y showRemember activan o desactivan bloques de UI opcionales sin apaños de CSS condicional.
- Theming solo mediante CSS custom properties, cero colores fijos, compatible con todos los presets.
Auth Login Split es una página de login a pantalla completa de dos columnas construida con CSS Grid. La columna izquierda es un lienzo de branding, fondo de acento, nombre del producto, tagline, y la columna derecha contiene el formulario en sí. En mobile el panel izquierdo desaparece por completo, dejando un formulario de login centrado y limpio. El formulario entra con un sutil fundido y deslizamiento de Framer Motion, suficiente movimiento para verse pulido sin distraer a quienes solo quieren iniciar sesión.
Anatomía
La sección exterior es un grid min-h-screen con lg:grid-cols-2. El panel izquierdo (oculto por debajo de lg) es una flex column con justify-between para empujar el logo arriba, el tagline al centro y el copyright abajo; usa var(--color-accent) como fondo. El panel derecho centra una div max-w-sm que contiene el título, el botón opcional de Google, un separador, los inputs de email y contraseña, una fila opcional de recordar/olvidé, el botón de envío y un enlace de registro.
Cómo funciona
La animación es mínima pero precisa. El wrapper max-w-sm del formulario es un motion.div con `initial={{ opacity: 0, y: 20 }}` y `animate={{ opacity: 1, y: 0 }}`. La transición usa un cubic-bezier `[0.16, 1, 0.3, 1]`, una curva ease-out agresiva que arranca rápido y se asienta con suavidad, dando al formulario una sensación natural de aterrizaje. Duración: 0.6s. Como la animación apunta al wrapper en lugar de a los campos individuales, todo el formulario entra como una unidad, lo que se lee más limpio que animaciones escalonadas en un contexto de autenticación.
Cómo crearlo en React
Configurar el grid de dos columnas
Haz de la sección exterior un grid a viewport completo con dos columnas iguales por encima del breakpoint lg. Por debajo, CSS Grid vuelve automáticamente a una única columna apilada, así que el formulario ocupa todo el ancho en mobile sin ninguna media query adicional.
<section className="min-h-screen grid lg:grid-cols-2"> {/* left panel */} <div className="hidden lg:flex flex-col justify-between p-12" style={{ background: "var(--color-accent)" }}> ... </div> {/* right panel */} <div className="flex items-center justify-center p-8"> ... </div> </section>Animar el wrapper del formulario al montar
Envuelve el contenido del formulario en un motion.div y declara los estados initial y animate. El cubic-bezier `[0.16, 1, 0.3, 1]` es un ease-out rápido que da energía a la animación sin rebote. Anima el wrapper, no los inputs individuales, para que el formulario entre como un bloque cohesionado.
const ease: [number, number, number, number] = [0.16, 1, 0.3, 1]; <motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.6, ease }} className="w-full max-w-sm" >Alternar los bloques opcionales con props
Condiciona el bloque de social login a un booleano showSocial y la fila de recordar/olvidé a showRemember. Ambos están en true por defecto. Esto mantiene el componente útil en distintos contextos, una herramienta interna minimalista descarta el social login, un SaaS de consumo lo conserva todo, sin tocar el markup.
{showSocial && ( <> <button ...>Continuer avec Google</button> <div className="flex items-center gap-3 my-6">...</div> </> )}Aplicar los CSS tokens para el theming
Cada color referencia una CSS custom property: var(--color-accent) para el fondo del panel izquierdo y el botón de envío, var(--color-foreground) para el texto, var(--color-border) para los bordes de los inputs y los separadores. Esto hace que el componente cambie de tema automáticamente cuando el atributo data-theme cambia en un elemento padre.
<input type="email" style={{ background: "var(--color-background-alt)", color: "var(--color-foreground)", border: "1px solid var(--color-border)", }} />
Cuándo usarlo
El layout split funciona bien para productos SaaS y agencias donde la pantalla de login es un punto de contacto de marca. El panel izquierdo es un espacio ideal para reforzar la propuesta de valor justo antes de que el usuario inicie sesión. Evítalo en herramientas internas o dashboards donde a nadie le importa el branding en el momento del login, un formulario centrado más simple se escanea más rápido. El layout también requiere suficiente contenido para que ambas columnas se sientan equilibradas; un panel izquierdo con solo un logo y una tagline vacía se ve peor que un diseño de una sola columna.
Usado por
- Vercel, Layout de autenticación de dos columnas con mensaje de marca a la izquierda y formulario de login a la derecha.
- Linear, Página de login split con panel de identidad de marca y formulario email/social, coherente con su estética de producto limpia.
- Notion, Pantalla de autenticación de dos columnas que mantiene el branding del producto visible durante todo el flujo de login.
FAQ
¿Qué ocurre con el panel izquierdo en mobile?
Desaparece por completo. La div lleva hidden lg:flex, así que por debajo del breakpoint lg (1024px) queda en display:none y el panel del formulario se expande a todo el ancho. Sin layout shift, sin un elemento de branding colapsado ocupando espacio.
¿Cómo conecto el formulario a un proveedor de autenticación real?
Reemplaza el handler onSubmit={(e) => e.preventDefault()} por tu lógica de auth, NextAuth signIn(), Supabase auth.signInWithPassword(), o una llamada a una API propia. El formulario es intencionadamente no controlado en la demo, así que puedes añadir tu propia gestión de estado sin pelear contra inputs controlados existentes.
¿Puedo usar una imagen de fondo o una ilustración en el panel izquierdo?
Sí. Cambia el estilo background de var(--color-accent) a un background-image de CSS o añade un img/Image en posición absolute dentro de la div izquierda. Mantén el texto legible añadiendo un overlay semitransparente si la imagen está muy cargada.
¿Es accesible el componente?
El formulario usa elementos input nativos con type='email' y type='password', lo que da a los navegadores validación integrada y soporte para tecnologías de asistencia. Los labels faltan en los inputs de la demo, añade elementos label explícitos o atributos aria-label antes de pasar a producción.