# Login Sheet

> A mobile app welcome screen with a Sign in with suiss bottom sheet for sharing or hiding an email.

A shadcn/ui block from the [@esuiss](https://suiss.app/ui?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/esuiss/login-sheet).

- Page, with a live preview: https://shadcn.exchange/r/esuiss/login-sheet
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/esuiss/login-sheet-light-f30c0d212eb17b99.png
- Registry item: https://suiss.app/ui/r/new-york-v4/login-sheet.json
- Categories: blocks, login
- Listed: 2026-10-20, source last changed 2026-10-20

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/esuiss/login-sheet.json
```

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

## Dependencies

- Registry items: `@esuiss/button`, `@esuiss/label`, `@esuiss/radio-group`, `@esuiss/spinner`

## Files

### `components/blocks/login-sheet.tsx`

```tsx
"use client"

import * as React from "react"

import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
import { Button } from "@/components/ui/button"
import { Label } from "@/components/ui/label"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Spinner } from "@/components/ui/spinner"

export function LoginSheet() {
  const [open, setOpen] = React.useState(false)
  const [email, setEmail] = React.useState("hide")
  const [busy, setBusy] = React.useState(false)
  const [done, setDone] = React.useState(false)
  const closeRef = React.useRef<HTMLButtonElement>(null)
  const triggerRef = React.useRef<HTMLButtonElement>(null)
  const titleId = React.useId()

  const close = () => {
    setOpen(false)
    triggerRef.current?.focus({ preventScroll: true })
  }

  React.useEffect(() => {
    if (open) closeRef.current?.focus({ preventScroll: true })
  }, [open])

  React.useEffect(() => {
    if (!open) return
    const onKey = (event: KeyboardEvent) => {
      if (event.key === "Escape") close()
    }
    window.addEventListener("keydown", onKey)
    return () => window.removeEventListener("keydown", onKey)
  })

  const proceed = () => {
    setBusy(true)
    window.setTimeout(() => {
      setBusy(false)
      setOpen(false)
      setDone(true)
    }, 1000)
  }

  return (
    <section className="flex min-h-175 items-center justify-center bg-muted sm:px-6 sm:py-7">
      <div className="relative flex min-h-175 w-full flex-col overflow-hidden bg-background sm:h-160 sm:min-h-0 sm:w-85 sm:rounded-[2.75rem] sm:shadow-[0_0_0_10px_var(--color-foreground),0_0_0_11px_var(--color-muted-foreground),0_30px_70px_rgb(0_0_0/0.3)] dark:sm:shadow-[0_0_0_10px_var(--color-muted),0_0_0_11px_var(--color-input),0_30px_70px_rgb(0_0_0/0.5)]">
        <span
          aria-hidden
          className="absolute top-2.5 left-1/2 z-10 hidden h-7.5 w-27.5 -translate-x-1/2 rounded-full bg-black sm:block"
        />
        <div
          inert={open}
          className="flex flex-1 flex-col items-center px-7 pt-22.5 pb-8.5 text-center"
        >
          <span className="mb-5 size-21 rounded-[1.25rem] bg-linear-160 from-[oklch(0.75_0.15_150)] to-[oklch(0.55_0.15_180)]" />
          <h1 className="text-3xl font-semibold tracking-tight">
            {done ? "Welcome, Jamie" : "Trail Routes"}
          </h1>
          <p className="mt-2 text-base text-muted-foreground">
            {done
              ? "Your account was created with suiss."
              : "Save your routes and share them with friends."}
          </p>
          <div className="flex-1" />
          {done ? (
            <button
              type="button"
              onClick={() => setDone(false)}
              className="rounded-xs text-base text-primary outline-none hover:underline focus-visible:ring-3 focus-visible:ring-ring/50"
            >
              Sign out ›
            </button>
          ) : (
            <div className="flex w-full flex-col gap-2.5">
              <Button
                ref={triggerRef}
                size="xl"
                block
                onClick={() => setOpen(true)}
                className="h-12.5 bg-foreground text-lg font-semibold text-background hover:bg-foreground/90"
              >
                Sign in with suiss
              </Button>
              <Button
                size="xl"
                block
                variant="secondary"
                className="h-12.5 text-lg font-semibold"
              >
                Continue with email
              </Button>
            </div>
          )}
        </div>
        <div
          aria-hidden
          data-open={open ? "" : undefined}
          onClick={close}
          className="pointer-events-none absolute inset-0 bg-black/18 opacity-0 transition-opacity duration-300 data-open:pointer-events-auto data-open:opacity-100"
        />
        <div
          role="dialog"
          aria-modal
          aria-labelledby={titleId}
          inert={!open}
          data-open={open ? "" : undefined}
          className="absolute inset-x-0 bottom-0 flex translate-y-[105%] flex-col gap-3.5 rounded-t-3xl bg-popover px-5 pt-3.5 pb-7.5 transition-transform duration-500 ease-[cubic-bezier(0.3,1.15,0.5,1)] data-open:translate-y-0 motion-reduce:transition-none"
        >
          <div className="flex items-center">
            <span className="w-7" />
            <span className="flex-1 text-center text-lg font-bold tracking-tight">
              suiss
            </span>
            <Button
              ref={closeRef}
              variant="secondary"
              size="icon-sm"
              aria-label="Close"
              onClick={close}
              className="size-7 text-muted-foreground"
            >
              <IconPlaceholder
                lucide="XIcon"
                tabler="IconX"
                hugeicons="Cancel01Icon"
                phosphor="XIcon"
                remixicon="RiCloseLine"
                className="size-3.5"
              />
            </Button>
          </div>
          <h2
            id={titleId}
            className="text-center text-2xl leading-tight font-semibold tracking-tight text-balance"
          >
            Use your suiss Account with Trail Routes
          </h2>
          <div className="rounded-xl bg-muted">
            <div className="flex items-center gap-3 px-3.5 py-2.5">
              <IconPlaceholder
                lucide="UserIcon"
                tabler="IconUser"
                hugeicons="UserIcon"
                phosphor="UserIcon"
                remixicon="RiUserLine"
                className="size-5 text-muted-foreground"
              />
              <div className="flex flex-col text-start">
                <span className="text-xs text-muted-foreground">Name</span>
                <span className="text-base">Jamie Rivera</span>
              </div>
            </div>
            <div className="ms-11.5 h-px bg-border" />
            <RadioGroup
              value={email}
              onValueChange={(value) => setEmail(value as string)}
              aria-label="Email"
              className="gap-0"
            >
              {[
                {
                  value: "share",
                  label: "Share My Email",
                  hint: "jamie@example.com",
                },
                {
                  value: "hide",
                  label: "Hide My Email",
                  hint: "A unique address that forwards to you",
                },
              ].map((option, index) => (
                <Label
                  key={option.value}
                  className="flex cursor-pointer items-center gap-3 py-2.5 pe-3.5 data-[index='0']:ps-3.5 data-[index='1']:ms-11.5 data-[index='1']:border-t data-[index='1']:border-border"
                  data-index={index}
                >
                  {index === 0 && (
                    <IconPlaceholder
                      lucide="MailIcon"
                      tabler="IconMail"
                      hugeicons="Mail01Icon"
                      phosphor="EnvelopeSimpleIcon"
                      remixicon="RiMailLine"
                      className="size-5 text-muted-foreground"
                    />
                  )}
                  <span className="flex flex-1 flex-col text-start">
                    <span className="text-base">{option.label}</span>
                    <span className="text-xs text-muted-foreground">
                      {option.hint}
                    </span>
                  </span>
                  <RadioGroupItem value={option.value} className="size-5.5" />
                </Label>
              ))}
            </RadioGroup>
          </div>
          <Button
            size="xl"
            block
            onClick={proceed}
            disabled={busy}
            className="h-12.5 text-lg font-semibold"
          >
            {busy ? <Spinner className="size-5" /> : "Continue with face scan"}
          </Button>
        </div>
      </div>
    </section>
  )
}
```
