Open demo

CSS code

HTML
<div class="lf">
<main class="lf-box">
  <div class="lf-mark" aria-hidden="true">N</div>
  <section id="lf-step1">
    <h1>Sign in</h1>
    <p class="lf-sub">Use your work email to continue to Northwind.</p>
    <form id="lf-f1">
      <label class="lf-label" for="lf-ident">Email address</label>
      <input class="lf-field" id="lf-ident" name="username" type="email" autocomplete="username"
             placeholder="[email protected]" required>
      <button class="lf-btn" type="submit">Next</button>
    </form>
    <p class="lf-foot">No account? <a href="#">Create one</a></p>
  </section>
  <section id="lf-step2" hidden>
    <h1>Welcome back</h1>
    <p class="lf-sub lf-sub-tight">Enter your password to finish signing in.</p>
    <p class="lf-who"><span class="lf-av" aria-hidden="true" data-avatar>D</span>
      <span id="lf-shown">[email protected]</span>
      <button type="button" id="lf-change" aria-label="Change email address">Change</button></p>
    <form onsubmit="event.preventDefault()">
      <input type="email" name="username" id="lf-user" autocomplete="username" value="[email protected]" hidden>
      <label class="lf-label" for="lf-pw">Password</label>
      <input class="lf-field" id="lf-pw" name="password" type="password" autocomplete="current-password"
             placeholder="••••••••" required>
      <button class="lf-btn" type="submit">Sign in</button>
    </form>
    <p class="lf-links"><a href="#">Forgot your password?</a></p>
  </section>
</main>
</div>
CSS
/* Identifier-first login: email on the first panel, password on the second. The script carries the email
   forward into a visible chip and a hidden username field (so password managers save the pair) and moves focus. */
.lf{--lf-brand:#4f46e5;--lf-brand-dark:#4338ca;--lf-ink:#111827;--lf-muted:#6b7280;--lf-line:#e5e7eb;--lf-edge:#868c99;--lf-bg:#f5f6fa;
  min-height:100vh;display:grid;grid-template-columns:minmax(0,1fr);place-items:center;padding:28px 20px;
  background:var(--lf-bg);color:var(--lf-ink);line-height:normal;
  font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif}
.lf,.lf *,.lf *::before,.lf *::after{box-sizing:border-box}
.lf :where(h1,p,form){margin:0}
.lf :where(input,button){font:inherit}
.lf [hidden]{display:none!important}
.lf a{color:var(--lf-brand);text-decoration:none;font-weight:600}
.lf a:hover{text-decoration:underline}
.lf :focus-visible{outline:2px solid var(--lf-brand);outline-offset:2px}
.lf-box{width:100%;max-width:400px;background:#fff;border:1px solid var(--lf-line);border-radius:16px;
  padding:38px 34px;box-shadow:0 10px 34px rgba(17,24,39,.06);overflow:hidden}
.lf-mark{width:42px;height:42px;border-radius:12px;background:var(--lf-brand);display:grid;place-items:center;
  color:#fff;font-weight:800;margin-bottom:22px}
.lf h1{font-size:1.4rem;letter-spacing:-.02em;margin-bottom:7px}
.lf-sub{color:var(--lf-muted);font-size:.93rem;line-height:1.6;margin-bottom:26px}
.lf-sub-tight{margin-bottom:16px}
.lf-label{display:block;font-size:.85rem;font-weight:600;margin-bottom:6px}
.lf-field{width:100%;padding:12px 14px;border:1px solid var(--lf-edge);border-radius:10px;font-size:.95rem;
  background:#fff;color:inherit;margin-bottom:20px;transition:border-color .15s,box-shadow .15s}
.lf-field::placeholder{color:var(--lf-muted)}
.lf-field:focus{outline:none;border-color:var(--lf-brand);box-shadow:0 0 0 3px rgba(79,70,229,.15)}
.lf-btn{width:100%;padding:13px;border:0;border-radius:10px;background:var(--lf-brand);color:#fff;
  font-size:.97rem;font-weight:700;cursor:pointer;transition:background .15s}
.lf-btn:hover{background:var(--lf-brand-dark)}
.lf-foot{margin-top:24px;text-align:center;font-size:.89rem;color:var(--lf-muted)}
.lf-who{display:flex;align-items:center;gap:10px;border:1px solid var(--lf-line);border-radius:999px;
  padding:6px 6px 6px 12px;margin-bottom:26px;font-size:.89rem;width:max-content;max-width:100%}
.lf-av{width:24px;height:24px;border-radius:50%;background:#ddd6fe;color:#5b21b6;display:grid;
  place-items:center;font-size:.74rem;font-weight:800;flex-shrink:0}
.lf-who span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lf-who button{background:none;border:0;color:var(--lf-brand);font-weight:600;cursor:pointer;
  font-size:.86rem;padding:4px 8px;border-radius:999px;flex-shrink:0}
.lf-who button:hover{background:#eef2ff}
.lf-links{margin-top:18px;font-size:.88rem}
@media (prefers-reduced-motion:reduce){.lf-field,.lf-btn{transition:none}}
JavaScript
// Carry the email forward so the second panel shows who is signing in, and move focus so a keyboard
// or screen-reader user is not left on the panel that just disappeared.
(() => {
  const s1 = document.getElementById('lf-step1'), s2 = document.getElementById('lf-step2');
  const ident = document.getElementById('lf-ident'), pw = document.getElementById('lf-pw');
  if (!s1 || !s2 || !ident || !pw) return;
  document.getElementById('lf-f1').addEventListener('submit', e => {
    e.preventDefault();
    const email = ident.value.trim() || '[email protected]';
    document.getElementById('lf-shown').textContent = email;
    document.getElementById('lf-user').value = email;
    document.querySelector('#lf-step2 [data-avatar]').textContent = email.charAt(0).toUpperCase();
    s1.hidden = true; s2.hidden = false;
    pw.focus();
  });
  document.getElementById('lf-change').addEventListener('click', () => {
    s2.hidden = true; s1.hidden = false;
    ident.focus();
  });
})();

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div class="lf d-grid">
<main class="card w-100">
  <div class="card-body">
    <div class="lf-mark" aria-hidden="true">N</div>
    <section id="lf-step1">
      <h1>Sign in</h1>
      <p class="lf-sub text-body-secondary">Use your work email to continue to Northwind.</p>
      <form id="lf-f1">
        <label class="form-label" for="lf-ident">Email address</label>
        <input class="form-control" id="lf-ident" name="username" type="email" autocomplete="username"
               placeholder="[email protected]" required>
        <button class="btn btn-brand w-100" type="submit">Next</button>
      </form>
      <p class="lf-foot text-body-secondary text-center mb-0">No account? <a href="#">Create one</a></p>
    </section>
    <section id="lf-step2" hidden>
      <h1>Welcome back</h1>
      <p class="lf-sub lf-sub-tight text-body-secondary">Enter your password to finish signing in.</p>
      <p class="lf-who d-flex align-items-center rounded-pill"><span class="lf-av rounded-circle flex-shrink-0" aria-hidden="true" data-avatar>D</span>
        <span id="lf-shown" class="text-truncate">[email protected]</span>
        <button class="btn btn-change rounded-pill flex-shrink-0" type="button" id="lf-change" aria-label="Change email address">Change</button></p>
      <form onsubmit="event.preventDefault()">
        <input type="email" name="username" id="lf-user" autocomplete="username" value="[email protected]" hidden>
        <label class="form-label" for="lf-pw">Password</label>
        <input class="form-control" id="lf-pw" name="password" type="password" autocomplete="current-password"
               placeholder="••••••••" required>
        <button class="btn btn-brand w-100" type="submit">Sign in</button>
      </form>
      <p class="lf-links mb-0"><a href="#">Forgot your password?</a></p>
    </section>
  </div>
</main>
</div>
CSS
/* Bootstrap .card, .form-control and .btn, recoloured through Bootstrap's CSS variables.
   The script swaps the two panels with the hidden attribute (Bootstrap's reboot hides [hidden]). */
.lf{--lf-brand:#4f46e5;--lf-brand-dark:#4338ca;--lf-ink:#111827;--lf-muted:#6b7280;--lf-line:#e5e7eb;--lf-edge:#868c99;
  --bs-body-color:var(--lf-ink);--bs-secondary-color:var(--lf-muted);--bs-border-color:var(--lf-edge);
  --bs-link-color-rgb:79,70,229;--bs-link-hover-color-rgb:67,56,202;
  min-height:100vh;grid-template-columns:minmax(0,1fr);place-items:center;padding:28px 20px;
  background:#f5f6fa;color:var(--lf-ink);line-height:normal}
.lf a{font-weight:600;text-decoration:none}
.lf a:hover{text-decoration:underline}
.lf :focus-visible{outline:2px solid var(--lf-brand);outline-offset:2px}
.lf .card{--bs-card-border-color:var(--lf-line);--bs-card-border-radius:16px;--bs-card-spacer-y:38px;--bs-card-spacer-x:34px;
  max-width:400px;box-shadow:0 10px 34px rgba(17,24,39,.06);overflow:hidden}
.lf-mark{width:42px;height:42px;border-radius:12px;background:var(--lf-brand);display:grid;place-items:center;
  color:#fff;font-weight:800;margin-bottom:22px}
.lf h1{font-size:1.4rem;font-weight:700;line-height:normal;letter-spacing:-.02em;margin-bottom:7px}
.lf-sub{font-size:.93rem;line-height:1.6;margin-bottom:26px}
.lf-sub-tight{margin-bottom:16px}
.lf .form-label{font-size:.85rem;font-weight:600;margin-bottom:6px}
.lf .form-control{padding:12px 14px;border-radius:10px;font-size:.95rem;line-height:normal;margin-bottom:20px;transition:border-color .15s,box-shadow .15s}
.lf .form-control:focus{border-color:var(--lf-brand);box-shadow:0 0 0 3px rgba(79,70,229,.15);outline:none}
.lf .form-control::placeholder{color:var(--lf-muted)}
.lf .btn-brand{--bs-btn-line-height:normal;--bs-btn-border-width:0;--bs-btn-padding-y:13px;--bs-btn-font-size:.97rem;--bs-btn-font-weight:700;--bs-btn-border-radius:10px;
  --bs-btn-bg:var(--lf-brand);--bs-btn-border-color:var(--lf-brand);--bs-btn-color:#fff;
  --bs-btn-hover-bg:var(--lf-brand-dark);--bs-btn-hover-border-color:var(--lf-brand-dark);--bs-btn-hover-color:#fff;
  --bs-btn-active-bg:var(--lf-brand-dark);--bs-btn-active-border-color:var(--lf-brand-dark);--bs-btn-active-color:#fff;
  --bs-btn-focus-box-shadow:none;transition:background-color .15s}
.lf-foot{margin-top:24px;font-size:.89rem}
.lf-who{gap:10px;border:1px solid var(--lf-line);padding:6px 6px 6px 12px;margin-bottom:26px;font-size:.89rem;width:max-content;max-width:100%}
.lf-av{width:24px;height:24px;background:#ddd6fe;color:#5b21b6;display:grid;place-items:center;font-size:.74rem;font-weight:800}
.lf .btn-change{--bs-btn-line-height:normal;--bs-btn-padding-y:4px;--bs-btn-padding-x:8px;--bs-btn-font-size:.86rem;--bs-btn-font-weight:600;--bs-btn-border-width:0;
  --bs-btn-color:var(--lf-brand);--bs-btn-hover-color:var(--lf-brand);--bs-btn-hover-bg:#eef2ff;
  --bs-btn-active-color:var(--lf-brand);--bs-btn-active-bg:#eef2ff;--bs-btn-focus-box-shadow:none}
.lf-links{margin-top:18px;font-size:.88rem}
@media (prefers-reduced-motion:reduce){.lf .form-control,.lf .btn{transition:none}}
JavaScript
// Carry the email forward so the second panel shows who is signing in, and move focus so a keyboard
// or screen-reader user is not left on the panel that just disappeared.
(() => {
  const s1 = document.getElementById('lf-step1'), s2 = document.getElementById('lf-step2');
  const ident = document.getElementById('lf-ident'), pw = document.getElementById('lf-pw');
  if (!s1 || !s2 || !ident || !pw) return;
  document.getElementById('lf-f1').addEventListener('submit', e => {
    e.preventDefault();
    const email = ident.value.trim() || '[email protected]';
    document.getElementById('lf-shown').textContent = email;
    document.getElementById('lf-user').value = email;
    document.querySelector('#lf-step2 [data-avatar]').textContent = email.charAt(0).toUpperCase();
    s1.hidden = true; s2.hidden = false;
    pw.focus();
  });
  document.getElementById('lf-change').addEventListener('click', () => {
    s2.hidden = true; s1.hidden = false;
    ident.focus();
  });
})();

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div class="grid min-h-screen grid-cols-[minmax(0,1fr)] place-items-center bg-page px-5 py-7 font-[-apple-system,BlinkMacSystemFont,'Segoe_UI',Roboto,Helvetica,Arial,sans-serif] leading-[normal] text-ink">
<main class="w-full max-w-[400px] overflow-hidden rounded-2xl border border-line bg-white px-[34px] py-[38px] shadow-[0_10px_34px_rgba(17,24,39,.06)]">
  <div class="mb-[22px] grid size-[42px] place-items-center rounded-xl bg-brand font-extrabold text-white" aria-hidden="true">N</div>
  <section id="lf-step1">
    <h1 class="mb-[7px] text-[1.4rem] font-bold tracking-[-.02em]">Sign in</h1>
    <p class="mb-[26px] text-[.93rem] leading-[1.6] text-muted">Use your work email to continue to Northwind.</p>
    <form id="lf-f1">
      <label class="mb-1.5 block text-[.85rem] font-semibold" for="lf-ident">Email address</label>
      <input class="mb-5 w-full rounded-[10px] border border-edge bg-white px-3.5 py-3 text-[.95rem] transition-[border-color,box-shadow] duration-150 placeholder:text-muted focus:border-brand focus:shadow-[0_0_0_3px_rgba(79,70,229,.15)] focus:outline-none motion-reduce:transition-none"
             id="lf-ident" name="username" type="email" autocomplete="username" placeholder="[email protected]" required>
      <button class="w-full cursor-pointer rounded-[10px] bg-brand p-[13px] text-[.97rem] font-bold text-white transition-[background-color,border-color,color] duration-150 hover:bg-brand-dark focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand motion-reduce:transition-none" type="submit">Next</button>
    </form>
    <p class="mt-6 text-center text-[.89rem] text-muted">No account? <a class="font-semibold text-brand hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand" href="#">Create one</a></p>
  </section>
  <section id="lf-step2" hidden>
    <h1 class="mb-[7px] text-[1.4rem] font-bold tracking-[-.02em]">Welcome back</h1>
    <p class="mb-4 text-[.93rem] leading-[1.6] text-muted">Enter your password to finish signing in.</p>
    <p class="mb-[26px] flex w-max max-w-full items-center gap-2.5 rounded-full border border-line py-1.5 pr-1.5 pl-3 text-[.89rem]"><span class="grid size-6 shrink-0 place-items-center rounded-full bg-[#ddd6fe] text-[.74rem] font-extrabold text-[#5b21b6]" aria-hidden="true" data-avatar>D</span>
      <span id="lf-shown" class="truncate">[email protected]</span>
      <button class="shrink-0 cursor-pointer rounded-full px-2 py-1 text-[.86rem] font-semibold text-brand hover:bg-[#eef2ff] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand" type="button" id="lf-change" aria-label="Change email address">Change</button></p>
    <form onsubmit="event.preventDefault()">
      <input type="email" name="username" id="lf-user" autocomplete="username" value="[email protected]" hidden>
      <label class="mb-1.5 block text-[.85rem] font-semibold" for="lf-pw">Password</label>
      <input class="mb-5 w-full rounded-[10px] border border-edge bg-white px-3.5 py-3 text-[.95rem] transition-[border-color,box-shadow] duration-150 placeholder:text-muted focus:border-brand focus:shadow-[0_0_0_3px_rgba(79,70,229,.15)] focus:outline-none motion-reduce:transition-none"
             id="lf-pw" name="password" type="password" autocomplete="current-password" placeholder="••••••••" required>
      <button class="w-full cursor-pointer rounded-[10px] bg-brand p-[13px] text-[.97rem] font-bold text-white transition-[background-color,border-color,color] duration-150 hover:bg-brand-dark focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand motion-reduce:transition-none" type="submit">Sign in</button>
    </form>
    <p class="mt-[18px] text-[.88rem]"><a class="font-semibold text-brand hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand" href="#">Forgot your password?</a></p>
  </section>
</main>
</div>
CSS
@theme {
  --color-brand: #4f46e5;
  --color-brand-dark: #4338ca;
  --color-ink: #111827;
  --color-muted: #6b7280;
  --color-line: #e5e7eb;
  --color-edge: #868c99;
  --color-page: #f5f6fa;
}
JavaScript
// Carry the email forward so the second panel shows who is signing in, and move focus so a keyboard
// or screen-reader user is not left on the panel that just disappeared.
(() => {
  const s1 = document.getElementById('lf-step1'), s2 = document.getElementById('lf-step2');
  const ident = document.getElementById('lf-ident'), pw = document.getElementById('lf-pw');
  if (!s1 || !s2 || !ident || !pw) return;
  document.getElementById('lf-f1').addEventListener('submit', e => {
    e.preventDefault();
    const email = ident.value.trim() || '[email protected]';
    document.getElementById('lf-shown').textContent = email;
    document.getElementById('lf-user').value = email;
    document.querySelector('#lf-step2 [data-avatar]').textContent = email.charAt(0).toUpperCase();
    s1.hidden = true; s2.hidden = false;
    pw.focus();
  });
  document.getElementById('lf-change').addEventListener('click', () => {
    s2.hidden = true; s1.hidden = false;
    ident.focus();
  });
})();

About this set

A two-panel login that asks for the email first and the password second, the pattern used when some accounts sign in through SSO or passkeys and the service has to know who is signing in before it knows how. Panel one holds an email field and a Next button. Panel two greets the person, shows the email in a pill with an initial avatar and a Change button, and asks for the password with a forgotten-password link below. A short script handles the swap: it copies the typed email into the pill and into a hidden username field in the second form, so password managers save and fill the right pair, hides one panel with the hidden attribute, shows the other and moves focus to the password field. Change reverses it and returns focus to the email. In a real product you would check the email with your server in the Next handler and choose the second step from the answer. The Bootstrap version uses .card, .form-control and .btn, and the Tailwind version is the same script with utility classes.

What’s included

  • Email and password on separate panels with one card
  • Email pill with avatar initial and a Change button
  • Hidden username field so password managers save the pair
  • Focus moves to the new panel's field on each step
  • About 20 lines of dependency-free JavaScript

Accessibility

Both fields have visible labels and correct autocomplete tokens (username, current-password). When a panel changes, focus moves to the first field of the new panel, so keyboard and screen reader users are not left on hidden content. The Change button is named Change email address, and every control shows a focus outline or ring.

Customise it

Colours are --lf-brand, --lf-ink, --lf-muted and --lf-edge on .lf (plain CSS and Bootstrap) or the --color-* theme values (Tailwind). The avatar colours are #ddd6fe and #5b21b6 on .lf-av or its utilities.