Signing in is the one screen every product has. This block is the card: a title, the fast ways in, a divider, the fields, and the way to the other card. It is a sheet that is also a form, so its fields keep one distance and it stacks by itself.
Sign in
<form class="sheet form w-full max-w(380) p-xl"> <header class="flex flex-col items-center gap-xs text-center"> <span class="fg-text-strong"> <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> <path d="M12 3a9 9 0 1 0 0 18a9 9 0 0 0 0-18M12 3a9 9 0 0 0 0 18" /> </svg> </span> <h1 class="heading heading-lg">Sign in to Atlas</h1> <p class="m(0) fg-text-muted">Welcome back. Pick up where you left off.</p> </header> <div class="flex flex-col gap-xs"> <button type="button" class="btn"> <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> <path d="M6 6a2 2 0 1 0 0 .01M18 12a2 2 0 1 0 0 .01M6 18a2 2 0 1 0 0 .01M6 8v8M8 6h4a4 4 0 0 1 4 4v0" /> </svg>Continue with GitHub</button> <button type="button" class="btn"> <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> <path d="M14 10a4 4 0 1 0-3.5 4L8 16.5V19h2.5v-2H13l1.5-1.5A4 4 0 0 0 14 10M16.5 7.5h.01" /> </svg>Continue with a passkey</button> </div> <div class="divider divider-label" role="separator">or</div> <label class="field">Email <input class="input" type="email" name="email" autocomplete="username" placeholder="you@example.com" required> </label> <div class="field"> <div class="flex items-baseline justify-between gap-sm"> <label for="signin-password">Password</label> <a class="link link-quiet text-xs fg-text-muted" href="#">Forgot it?</a> </div> <input class="input" id="signin-password" type="password" name="password" autocomplete="current-password" required> </div> <label class="check"> <input type="checkbox" class="checkbox" name="remember" checked>Keep me signed in on this device</label> <button class="btn btn-primary">Sign in</button> <p class="m(0) text-center fg-text-muted">New here? <a class="link" href="#">Create an account</a> </p></form>Blocks are markup to copy. They take the page's own text color and size; the previews set fg-text-body text-sm on the body. Each block is a size container, so it reflows by its own width: in a wide page, a sidebar's worth of room or a dialog. Center the card on its page with the centered layout.
#What is in it
sheet form w-full max-w(380) p-xl. The card fills a phone and stops at 380px.formsets the gap between everything in it.- The fast ways first. A provider and a passkey are one click each; the password is the fallback. A divider with "or" parts the two.
- "Forgot it?" beside the label, not under the field. It is a
link-quietin muted ink at the end of the label's row, so it is found without competing with the button. The label points at its field withfor, since the field is no longer inside it. - One primary button, the last control, as wide as the fields.
- The way to the other card under it: one line, one link.
The fields carry autocomplete values, username and current-password, which is what lets a password manager fill them.
#Sign up
The same card with more fields. The names share a row, grid grid-cols(2) gap-sm: two short fields that are one answer. A form-row would not split here, since it waits for 28rem of form and this card has 22. The password's rule is a field-hint tied to the input with aria-describedby, shown before the reader makes the mistake.
Sign up
<form class="sheet form w-full max-w(420) p-xl"> <header class="flex flex-col gap-xs"> <h1 class="heading heading-lg">Create your account</h1> <p class="m(0) fg-text-muted">Free for personal projects. No card needed.</p> </header> <div class="grid grid-cols(2) gap-sm"> <label class="field">First name <input class="input" name="given-name" autocomplete="given-name" required> </label> <label class="field">Last name <input class="input" name="family-name" autocomplete="family-name" required> </label> </div> <label class="field">Work email <input class="input" type="email" name="email" autocomplete="email" placeholder="you@company.com" required> </label> <label class="field">Password <input class="input" type="password" name="password" autocomplete="new-password" minlength="12" required aria-describedby="signup-hint"> <span class="field-hint" id="signup-hint">At least 12 characters. A sentence works well.</span> </label> <label class="check"> <input type="checkbox" class="checkbox" name="terms" required> <span>I agree to the <a class="link" href="#">terms</a>and the <a class="link" href="#">privacy policy</a> </span> </label> <button class="btn btn-primary">Create account</button> <p class="m(0) text-center fg-text-muted">Already have one? <a class="link" href="#">Sign in</a> </p></form>Ask only for what the first screen needs. Every field here loses some of the people who started.
#Verify
After a code is sent, one job: type it. An OTP field in the middle, the address the code went to in ink, and a quiet way to send another.
Verify
<form class="sheet form w-full max-w(380) p-xl"> <header class="flex flex-col items-center gap-xs text-center"> <span class="well flex items-center justify-center w(44) h(44) fg-text-strong"> <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> <path d="M4 6h16a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zM3 7l9 6 9-6" /> </svg> </span> <h1 class="heading heading-lg mt-xs">Check your email</h1> <p class="m(0) fg-text-muted">We sent a six-digit code to <span class="fg-text-strong">ada@example.com</span>. It works for ten minutes.</p> </header> <fieldset class="otp mx-auto" aria-label="Verification code"> <input class="otp-slot" inputmode="numeric" pattern="[0-9]" maxlength="1" autocomplete="one-time-code" aria-label="Digit 1" value="4"> <input class="otp-slot" inputmode="numeric" pattern="[0-9]" maxlength="1" aria-label="Digit 2" value="2"> <input class="otp-slot" inputmode="numeric" pattern="[0-9]" maxlength="1" aria-label="Digit 3"> <span class="otp-separator" aria-hidden="true"></span> <input class="otp-slot" inputmode="numeric" pattern="[0-9]" maxlength="1" aria-label="Digit 4"> <input class="otp-slot" inputmode="numeric" pattern="[0-9]" maxlength="1" aria-label="Digit 5"> <input class="otp-slot" inputmode="numeric" pattern="[0-9]" maxlength="1" aria-label="Digit 6"> </fieldset> <button class="btn btn-primary">Verify</button> <p class="m(0) text-center fg-text-muted">Nothing arrived? <button type="button" class="link">Send it again</button> </p></form><script type="module"> import { attach } from '@master/ui/behaviors' attach()</script>The otp behavior moves focus from one slot to the next and spreads a pasted code across them; without it each slot is still a field that takes one digit. "Send it again" is a <button> drawn as a link: it does something here, it does not go anywhere.
#Accessibility
- The card's title is the page's
<h1>. - Every field has a visible label. A placeholder is an example, not a name.
- Errors appear with the field they belong to: add a
field-errorand it shows while the field is invalid; see input. - Do not disable the button until the form is valid. A disabled button cannot say what is missing; a submitted form can.
- Keep "show password" in mind for long passwords: a quiet button inside an input group that switches the field's
type.
#Related
- FormThe rhythm of a form: rows, sections and actions that follow their column.
- SurfaceTrays, sheets and wells: the material ladder every component is built on.
- HeadingA title in the ink of the page, with the line over it and the paragraph under it.
- InputText fields cut into the surface, with labels, hints, errors and groups.
- CheckboxA small native well that fills with ink when checked.
- OTPA short code typed one character to a slot.
- DividerOne hairline between sections, with an optional label.
- ButtonA raised control that lifts on hover and sinks when pressed.
- LinkText that goes somewhere, in the ink of its sentence.