# Login-3

> A login form with a customizable side background

A shadcn/ui block from the [@hexui](https://hexui.sh) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/hexui/login-3).

- Page, with a live preview: https://shadcn.exchange/r/hexui/login-3
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/hexui/login-3-light-3ecc7e10831ba22b.png
- Registry item: https://hexui.sh/r/login-3.json
- Categories: login
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://hexui.sh/r/login-3.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@icons-pack/react-simple-icons`, `lucide-react`
- Registry items: `button`, `field`, `input`

## Files

### `registry/blocks/login/login-3/source/app/login/page.tsx`

```tsx
import Image from "next/image"
import { LoginForm } from "../../components/login-form"

export default function Page() {
  return (
    // Two-column split on lg: form (4 cols) + decorative background image (6 cols, hidden below lg).
    <div className="grid min-h-svh w-full lg:grid-cols-10">
      <div className="flex col-span-4 items-center justify-center">
        <div className="w-full max-w-xs">
          <LoginForm />
        </div>
      </div>
      <div className="relative hidden bg-muted lg:block col-span-6">
        <Image
          width={1500}
          height={1600}
          src="/demo/backgrounds/sky.png"
          alt="Background"
          // Darken and desaturate the photo in dark mode so it doesn't compete with the form.
          className="h-full w-full object-cover dark:brightness-[0.2] dark:grayscale"
        />
      </div>
    </div>
  )
}
```

### `registry/blocks/login/login-3/source/components/login-form.tsx`

```tsx
"use client"

import { useState } from "react"
import { SiGoogle, SiApple } from "@icons-pack/react-simple-icons"
import { Command, Eye, EyeClosed } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
  Field,
  FieldDescription,
  FieldGroup,
  FieldLabel,
  FieldSeparator,
} from "@/components/ui/field"
import { Input } from "@/components/ui/input"

export function LoginForm() {
  // Toggles password visibility between masked (••••) and plaintext.
  const [showPassword, setShowPassword] = useState(false)

  return (
    <div className="flex w-full max-w-sm min-w-0 flex-col gap-6 pb-8">
      <form>
        <FieldGroup>
          <div className="flex flex-col items-center gap-1 text-center">
            <Command className="size-6 mb-4" />
            <h1 className="text-2xl font-semibold sm:text-3xl">Welcome back</h1>
            <FieldDescription className="text-center sm:text-base">
              Sign in to your account to continue
            </FieldDescription>
          </div>
          <Field>
            <FieldLabel htmlFor="email">Email</FieldLabel>
            <Input
              id="email"
              name="email"
              type="email"
              autoComplete="email"
              spellCheck={false}
              placeholder="m@example.com"
              required
            />
          </Field>
          <Field>
            <div className="flex items-center">
              <FieldLabel htmlFor="password">Password</FieldLabel>
              <a
                href="#"
                className="ml-auto inline-block text-sm text-muted-foreground underline-offset-4 hover:underline"
              >
                Forgot your password?
              </a>
            </div>
            <div className="relative">
              <Input
                id="password"
                name="password"
                type={showPassword ? "text" : "password"}
                autoComplete="current-password"
                placeholder="••••••••"
                className="pr-6"
                required
              />
              {/* Eye toggle overlays the input's right edge; aria-pressed/label reflect visibility state. */}
              <Button
                type="button"
                variant="ghost"
                size="icon"
                aria-label={showPassword ? "Hide password" : "Show password"}
                aria-pressed={showPassword}
                onClick={() => setShowPassword((currentValue) => !currentValue)}
                className="absolute top-1/2 right-1 z-10 -translate-y-1/2 text-muted-foreground active:-translate-y-1/2"
              >
                {showPassword ? <EyeClosed className="size-4" /> : <Eye className="size-4" />}
              </Button>
            </div>
          </Field>
          <Field className="mt-1">
            <Button type="submit" size="lg">
              Login
            </Button>
          </Field>
          <FieldSeparator>or</FieldSeparator>
          <Field>
            <Button variant="outline" type="button">
              <SiGoogle />
              Continue with Google
            </Button>
          </Field>
          <Field>
            <Button variant="outline" type="button">
              <SiApple />
              Continue with Apple
            </Button>
          </Field>
        </FieldGroup>
      </form>
      <FieldDescription className="px-0 text-center sm:px-6">
        Don&rsquo;t have an account?{" "}
        <a href="#" className="text-primary underline-offset-4 hover:underline">
          Sign up
        </a>
      </FieldDescription>
    </div>
  )
}
```
