Auth Layout
A full sign-in screen: the brand at the top, the form centered in a narrow column on the page, and an optional panel beside it that sells the product.
Installation
$
npx shadcn@latest add https://ui.mattdowney.com/r/auth-layout.jsonUsage
import { AuthLayout } from "@/components/ui/auth-layout"Examples
Split screen
Brand, a bare AuthForm, a product panel and fine print. The panel shows from lg up; narrower, the form takes the screen. Put a crop of the product in the panel, built from kit parts.
Copperleaf
Sign in
Choose how you want to continue
or continue with
By signing in, you agree to the Terms.
Without a panel
Leave out aside and the form centers on the whole screen, with the brand still at the top left.
Copperleaf
Sign in
Choose how you want to continue
or continue with
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
| childrenAuthLayout | ReactNode | - | The form, usually <AuthForm surface="bare" />. Held in a column max-w-sm wide, centered in the space left over. |
| brandAuthLayout | ReactNode | - | Top left of the form column. Usually the product's mark, linked home. |
| asideAuthLayout | ReactNode | - | The panel beside the form, on an inset neutral panel one step off the page in each mode. Hidden below lg. Omit it and the form centers on the whole screen. |
| footerAuthLayout | ReactNode | - | Fine print at the bottom of the form column. Anchors get the inline-link style. |
| classNameAuthLayout | string | - | Applied to the root grid, which is min-h-svh. |
childrenAuthLayoutReactNodeThe form, usually <AuthForm surface="bare" />. Held in a column max-w-sm wide, centered in the space left over. |
| brandAuthLayoutReactNodeTop left of the form column. Usually the product's mark, linked home. |
asideAuthLayoutReactNodeThe panel beside the form, on an inset neutral panel one step off the page in each mode. Hidden below lg. Omit it and the form centers on the whole screen. |
| footerAuthLayoutReactNodeFine print at the bottom of the form column. Anchors get the inline-link style. |
classNameAuthLayoutstringApplied to the root grid, which is min-h-svh. |
import * as React from "react"
import { cn } from "@/registry/lib/utils"
type AuthLayoutProps = React.ComponentProps<"div"> & {
/** Top-left of the form column: the product's mark, usually a link home. */
brand?: React.ReactNode
/**
* The panel beside the form that sells the product: a crop of the product
* built from kit parts, a quote, a row of logos. Hidden below `lg`, where
* the form column takes the screen. Omit it and the form centres on the
* whole page.
*/
aside?: React.ReactNode
/** Fine print pinned to the bottom of the form column: terms, a support link. */
footer?: React.ReactNode
}
// @use-when a whole sign-in, sign-up or reset-password screen: the brand at
// the top, the form centred in a narrow column on the page, and an optional
// panel beside it selling the product. Holds an AuthForm; is not one.
function AuthLayout({
brand,
aside,
footer,
children,
className,
...props
}: AuthLayoutProps) {
return (
<div
data-slot="auth-layout"
className={cn(
// svh, not dvh: the form is centred in this box, and a dvh box
// resizes as the phone's browser chrome slides, which moves the form
// under the reader's thumb mid-scroll.
"grid min-h-svh grid-cols-1",
aside != null && "lg:grid-cols-2",
className
)}
{...props}
>
<div
data-slot="auth-layout-main"
className="flex flex-col p-chrome-gutter"
// The column's three rows are peers, so the peer rung sets the least
// space between them; the form row takes the rest and centres in it.
style={{ gap: "var(--spacing-rhythm-peer)" }}
>
{brand != null && (
<div data-slot="auth-layout-brand" className="flex items-center">
{brand}
</div>
)}
<div
data-slot="auth-layout-content"
className="flex flex-1 flex-col items-center justify-center"
>
{/* The form's own measure. AuthForm caps itself at the same width,
and anything else put here (a reset-password step, a sent-mail
Lockup) gets the column a form wants rather than half a screen. */}
<div className="w-full max-w-sm">{children}</div>
</div>
{footer != null && (
<div
data-slot="auth-layout-footer"
// Anchors get the inline-link rule, as AuthForm's footer does, so
// a terms link needs no classes.
className="text-center text-xs text-muted-foreground [&_a]:rounded-sm [&_a]:text-accent [&_a]:underline [&_a]:outline-none [&_a]:hover:no-underline [&_a]:focus-visible:ring-1 [&_a]:focus-visible:ring-ring-accent"
>
{footer}
</div>
)}
</div>
{aside != null && (
<aside data-slot="auth-layout-aside" className="hidden p-3 lg:flex">
{/* Inset, not full-bleed: a margin and a Sheet's corner, the tier
for a panel inset from the screen's edges, so it reads as a
surface set beside the form rather than half of the window.
The fill is one neutral step off the page in each mode, not a
semantic token: `muted` IS the page colour in light and the card
colour in dark, so it reads as no panel in one mode and swallows
the cards set on it in the other. Neutral, not brand, so the
product shown on it carries the colour. */}
<div
data-slot="auth-layout-panel"
className="flex flex-1 flex-col justify-center overflow-hidden rounded-xl bg-neutral-200 p-12 dark:bg-neutral-900"
>
{aside}
</div>
</aside>
)}
</div>
)
}
export { AuthLayout }
export type { AuthLayoutProps }