Retour au catalogue

Login Centered

Page de connexion centree avec formulaire email/password, boutons sociaux et liens.

authsimple Both Responsive a11y
minimalcorporatesaasuniversalcentered
Theme

How to build a centered login form in React with Framer Motion

A centered React login form wraps email, password, and optional social buttons in a single narrow card, vertically and horizontally centered with Flexbox. A Framer Motion fade-in (opacity 0→1, y 20→0) over 600ms with a custom spring ease gives the card a polished entry without any page load jank.

  • Stack: React + Framer Motion 11 + Tailwind v4, ~194 lines, no icon library used at runtime.
  • Entry animation: motion.div with initial { opacity: 0, y: 20 } animated to { opacity: 1, y: 0 }, duration 0.6s, custom cubic-bezier [0.16, 1, 0.3, 1].
  • Social buttons (Google + GitHub) and the remember-me checkbox are toggled via boolean props, zero extra markup when disabled.
  • Fully theme-aware: all colors read from CSS custom properties (--color-background, --color-accent, --color-border), compatible with all 7 registry presets.
  • Accessible: labels are associated with their inputs, form uses a native <form> element, submit button is type='submit'.

Auth Login Centered is a standalone React authentication page that centers a compact login card in the viewport. It combines an email/password form with optional OAuth shortcuts (Google, GitHub), a remember-me toggle, and a forgot-password link, all the building blocks a SaaS product needs without any hard-wired dependencies on a specific auth library.

Anatomy

The outer section stretches to min-h-screen and uses Flexbox (items-center justify-center) to center the inner card. The card is a motion.div capped at max-w-sm. Inside it: a brand block (initial letter in an accent square + h1 + optional subtitle), an optional social row (two side-by-side buttons separated from the form by a horizontal rule with an 'or' label), then the form with email input, password input, a remember/forgot row, and the submit button. A register link sits below the form.

How it works

The card entry is a single Framer Motion transition on the wrapping motion.div. The ease array [0.16, 1, 0.3, 1] is an expo-out curve: it accelerates sharply at the start then decelerates into the resting position, which makes the card feel like it snaps into place rather than drifting. The y offset of 20px keeps the travel short so the animation reads as 'arrival' rather than a scroll reveal. No scroll triggers, no viewport observers, the animation fires immediately on mount.

How to build it in React

  1. Center the page layout

    Wrap everything in a section with min-h-screen and Flexbox centering. This handles both vertical and horizontal centering without a fixed height or absolute positioning, so the card stays centered even if content overflows on small screens.

    <section className="min-h-screen flex items-center justify-center py-16 px-6"
      style={{ background: "var(--color-background)" }}>
  2. Animate the card on mount

    Replace the inner div with a motion.div and set initial/animate. The custom ease curve gives the pop-in feel. Keep the y offset small (20px) so it reads as a landing, not a drop.

    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"
    >
  3. Gate the social block with a prop

    Wrap the social buttons and the divider in a single showSocial check. This way the component renders cleanly as an email-only form when OAuth is not available, with no empty whitespace to compensate for.

    {showSocial && (
      <>
        <div className="flex gap-3 mb-6">
          <button style={{ border: "1px solid var(--color-border)" }}>Google</button>
          <button style={{ border: "1px solid var(--color-border)" }}>GitHub</button>
        </div>
        {/* divider */}
      </>
    )}
  4. Wire up the form fields with CSS tokens

    Use CSS custom properties for all colors so the form adapts to every theme preset without a single conditional class. The background-alt token provides the subtle input background that differentiates fields from the card surface.

    <input
      type="email"
      style={{
        background: "var(--color-background-alt)",
        color: "var(--color-foreground)",
        border: "1px solid var(--color-border)",
      }}
    />

When to use it

Reach for this layout any time the login screen is the primary destination: SaaS dashboards, internal tools, B2B apps. It works well when authentication is an expected friction point and the user arrives with intent. Skip it for products where login is buried inside a flow (like a checkout), a modal or an inline form fits better there. The showSocial prop makes it easy to strip OAuth buttons when only email/password auth is supported.

Used by

  • Linear, Minimal centered login with email and Google OAuth, clean separation between social and email flows.
  • Vercel, Centered auth card with GitHub, GitLab, and Bitbucket social buttons above an email form, matching this component's layout pattern exactly.
  • Notion, Single-column centered login with Google and Apple OAuth, then an email option below a divider, the classic stacked structure this component follows.
  • Loom, Centered auth card with brand logo at top, social login buttons, and an email form below, the same anatomy as this component.

FAQ

How do I connect this form to a real auth library like NextAuth or Supabase?

Replace the onSubmit handler (currently e.preventDefault()) with your library's sign-in call. For NextAuth: signIn('credentials', { email, password }). For Supabase: supabase.auth.signInWithPassword({ email, password }). The social buttons take the same pattern: signIn('google') or supabase.auth.signInWithOAuth({ provider: 'google' }).

Can I disable the entry animation for users who prefer reduced motion?

Yes. Wrap the transition config in a check against the useReducedMotion hook from Framer Motion. When it returns true, set duration to 0 or skip the initial/animate props entirely: initial={reducedMotion ? false : { opacity: 0, y: 20 }} with transition={{ duration: reducedMotion ? 0 : 0.6, ease }}.

How do I add form validation?

The form is uncontrolled by default. Swap the inputs for controlled versions with useState, then validate on submit or on blur. React Hook Form integrates cleanly: register each input and call handleSubmit, which replaces the current onSubmit.

Is the component accessible on keyboard navigation?

The native form elements (input, button, a, label) handle tab order and focus automatically. The checkbox for 'remember me' uses a native input type='checkbox', so it receives focus and responds to Space without extra ARIA. The submit button is type='submit', so pressing Enter in any input submits the form.

"use client";

import { motion } from "framer-motion";

interface AuthLoginCenteredProps {
  brandName?: string;
  title?: string;
  subtitle?: string;
  showSocial?: boolean;
  showRemember?: boolean;
  forgotPasswordUrl?: string;
  registerUrl?: string;
}

const ease: [number, number, number, number] = [0.16, 1, 0.3, 1];

export default function AuthLoginCentered({
  brandName = "Acme",
  title = "Connexion",
  subtitle = "",
  showSocial = true,
  showRemember = true,

Code complet réservé à Pro

Code source intégral, export multi-framework et playground.

Passer en Pro, 9,99€/mois

Reviews

React Login Form Centered Layout, Code + Tutorial