Two factor and sign in
Sign in, split
Two columns: the second factor step on one side and an illustration panel with a quote on the other, drawn with inline SVG over the theme colors. The panel hides itself when the container is narrow. Needs monsoon.js for the code cells and the countdown.
Needs monsoon.js. The styles work on their own, and the behavior comes from the optional interactive layer: add <script src="https://cdn.jsdelivr.net/npm/monsooncss/dist/monsoon.js" defer></script> or import 'monsooncss/js'. See the interactive layer.
Example
<div class="auth-split">
<div class="auth-split-main">
<form class="grid gap-6" action="#" onsubmit="event.preventDefault()">
<ol class="auth-flow" aria-label="Sign in progress">
<li class="auth-flow-step auth-flow-done"><span class="auth-flow-label">Password<span class="sr-only">, completed</span></span></li>
<li class="auth-flow-step" aria-current="step"><span class="auth-flow-label">Verify</span></li>
<li class="auth-flow-step"><span class="auth-flow-label">Done</span></li>
</ol>
<header class="auth-header">
<span class="auth-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4.5 5.5h15a1 1 0 0 1 1 1V16a1 1 0 0 1-1 1H9l-4.5 3.5V6.5a1 1 0 0 1 1-1z"/><path d="M8.5 11.25h.01M12 11.25h.01M15.5 11.25h.01" stroke-width="2.4"/></svg></span>
<h2 class="auth-title">Enter the code we texted you</h2>
<p class="auth-description">We sent a 6 digit code to <span class="auth-destination">+55 •• •••••-0474</span>. It expires in 10 minutes.</p>
</header>
<div class="otp mx-auto" data-m-otp data-m-otp-separator="3" data-m-otp-autosubmit>
<input class="otp-field" name="code" maxlength="6" inputmode="numeric" autocomplete="one-time-code" aria-label="Verification code">
</div>
<label class="flex items-start gap-2.5 text-sm">
<input type="checkbox" class="checkbox checkbox-sm mt-0.5" name="trust_device" checked>
<span>Don't ask again on this device for 30 days</span>
</label>
<button class="btn btn-primary btn-block">Verify</button>
<div class="auth-alt">
<span>Didn't get it? <button type="button" class="link link-primary" data-m-auth-resend data-m-auth-resend-seconds="30">Resend code</button></span>
<a class="link link-muted" href="#methods">Try another method</a>
</div>
</form>
</div>
<aside class="auth-split-panel">
<svg viewBox="0 0 400 480" fill="none" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
<g stroke="currentColor" stroke-opacity=".16"><circle cx="300" cy="120" r="60"/><circle cx="300" cy="120" r="110"/><circle cx="300" cy="120" r="160"/><circle cx="300" cy="120" r="210"/></g>
<path d="M300 64 254 82v34c0 30 19.5 56 46 66 26.5-10 46-36 46-66V82z" fill="currentColor" fill-opacity=".14" stroke="currentColor" stroke-opacity=".7" stroke-width="3" stroke-linejoin="round"/>
<path d="m281 120 13 13 26-27" stroke="currentColor" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<g fill="currentColor" fill-opacity=".9"><rect x="70" y="250" width="34" height="42" rx="8"/><rect x="112" y="250" width="34" height="42" rx="8"/><rect x="154" y="250" width="34" height="42" rx="8"/><rect x="204" y="250" width="34" height="42" rx="8" fill-opacity=".35"/><rect x="246" y="250" width="34" height="42" rx="8" fill-opacity=".35"/><rect x="288" y="250" width="34" height="42" rx="8" fill-opacity=".35"/></g>
</svg>
<p class="auth-split-quote">"Turning on two step sign in took our team five minutes, and account takeovers went to zero."</p>
<p class="auth-split-caption">Security lead at a fictional example company</p>
</aside>
</div>Classes
5 classes. Click a name to copy it. Utilities on the same element always win over component defaults, so any of them can be adjusted with plain utility classes.
| Class | Type | What it does |
|---|---|---|
| Base | Base class of the sign in, split. On a <div> in the example. Sets grid display, grid tracks, overflow, background, border and corner radius.CSS |
|
| Part | Part of the sign in, split. On a <div> in the example. Sets grid display, alignment, gap and padding.CSS |
|
| Part | Part of the sign in, split. On an <aside> in the example. Sets grid display, alignment, gap, height, padding and overflow.CSS |
|
| Part | Part of the sign in, split. On a <p> in the example. Sets margin, text size, font weight and text style.CSS |
|
| Part | Part of the sign in, split. On a <p> in the example. Sets margin, text size and opacity.CSS |
Usage
The styles ship inside the same stylesheet as the utilities, and the behavior comes from the optional monsoon.js script, which enhances any element with a matching data-m attribute, including markup added later.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/monsooncss">
<script src="https://cdn.jsdelivr.net/npm/monsooncss/dist/monsoon.js" defer></script>
<!-- Sign in, split: start from the base class, then add modifiers -->
<div class="auth-split">
<div class="auth-split-main">
<form class="grid gap-6" action="#" onsubmit="event.preventDefault()">
<ol class="auth-flow" aria-label="Sign in progress">
<li class="auth-flow-step auth-flow-done"><span class="auth-flow-label">Password<span class="sr-only">, completed</span></span></li>
<li class="auth-flow-step" aria-current="step"><span class="auth-flow-label">Verify</span></li>
<li class="auth-flow-step"><span class="auth-flow-label">Done</span></li>
</ol>
<header class="auth-header">
<span class="auth-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4.5 5.5h15a1 1 0 0 1 1 1V16a1 1 0 0 1-1 1H9l-4.5 3.5V6.5a1 1 0 0 1 1-1z"/><path d="M8.5 11.25h.01M12 11.25h.01M15.5 11.25h.01" stroke-width="2.4"/></svg></span>
<h2 class="auth-title">Enter the code we texted you</h2>
<p class="auth-description">We sent a 6 digit code to <span class="auth-destination">+55 •• •••••-0474</span>. It expires in 10 minutes.</p>
</header>
<div class="otp mx-auto" data-m-otp data-m-otp-separator="3" data-m-otp-autosubmit>
<!-- the full example is in the Code tab above -->Theme it with --seed on any ancestor, for example style="--seed: #0d9488", and force a scheme with class="dark" or class="light" on a wrapper.
Accessibility
- Give every control a visible
labellinked withfor, or anaria-labelwhen the purpose is obvious from context. Link help and error text witharia-describedby; invalid styles use:user-invalid, so they appear only after the user interacts. - Checkboxes and radios stay real inputs, so they keep native keyboard behavior (Space toggles, arrow keys move within a radio group) and form submission.
- Use
buttonfor actions andafor navigation. Icon only buttons need anaria-label; decorative icons carryaria-hidden="true". - Every interactive part shows a visible focus ring with
:focus-visible, and tone colors pair with-foregroundtokens that keep text at 4.5:1 or better in light and dark mode.