Design Lounge
Install

Nº 063 of 520

Split sign-in

45/55 sign-in screen: floating-label fields, a password visibility toggle and a magic-link button, beside a deep-green panel with testimonials crossfading every 5 seconds.

Preview

Open full size
1280 × 800 · 10.8 KB · 131 lines

Use it

Free to use · credit appreciated

1 Once, in your project

npx skills add SusanAcharya/Design-Lounge

2 Then ask

Using the Design Lounge skill, build "Split sign-in" (piece split-sign-in) in my stack. Match its brief; restyle it onto my product's theme.

No skill? Paste the brief instead. It carries the numbers, tokens and behaviour.

Brief

221 lines

Build brief for a coding agent. Rebuild this piece in the reader’s stack. If they haven’t said which stack, ask once, then default to semantic HTML + CSS + a little vanilla JS. Match the numbers below; don’t “improve” them.

What it is

The sign-in screen for Mira, a planning tool for teams. The viewport is split 45 / 55: the left 576px is a warm off-white column holding a 360px form (floating-label email and password fields, a filled “Continue” button, an “or” rule, and an outlined “Email me a magic link” button that turns into a confirmation). The right 704px is a deep-green panel with two thin concentric circles in the corner, a 40px DM Serif Display testimonial that crossfades to the next quote every 5 seconds, three 40px bar-shaped dots, and a footer meta line. The feeling is calm and print-like; the detail worth copying is the floating label: 56px fields whose label shrinks to 78% and lifts 10px on focus or when the field has a value, with no layout shift.

Reference behaviour

  1. Initial state: email field is prefilled (“priya@loam.studio”) so its label is already floated; password is empty with the label at rest; “Keep me signed in” is checked; testimonial 1 is visible and dot 1 is filled.
  2. Hover a field: border --line → --ink-3. Focus: border --ink, 3px ring rgba(31,107,82,.15), label lifts (translateY(-10px) scale(.78)) and darkens over 180ms.
  3. Type into the password field: label stays lifted after blur because the input is no longer :placeholder-shown.
  4. Click the eye button (40 × 40, inside the field’s right edge): input type flips password ↔ text, the icon swaps to a slashed eye, aria-pressed and aria-label (“Show password” / “Hide password”) update, and focus returns to the input.
  5. Hover “Continue”: background --green → --green-2. Submit is prevented in the demo.
  6. Click “Email me a magic link”: the button’s border and text become --accent, its label changes to “Link sent to priya@loam.studio · check your email”, and further clicks do nothing.
  7. Every 5000ms the visible quote fades out and the next fades in (both over 600ms, overlapping, since the figures are absolutely stacked). The corresponding dot fills.
  8. Click a dot: jumps to that quote and restarts the 5s timer. Hovering anywhere on the green panel pauses rotation; leaving resumes it.
  9. Reduced motion: quotes swap instantly; labels move in 1ms.

Structure

1280 × 800
┌──────────────────────────────────┬────────────────────────────────────────────┐
│ ● Mira                           │ TEAMS ON MIRA                    ( )  ( )  │  circles 420px, corner
│                                  │                                            │
│                                  │                                            │
│  Welcome back            (36px)  │                                            │
│  Sign in to your workspace …     │      “We stopped writing status            │
│  ┌ Email address ──────────────┐ │       updates. Mira became the             │  40px serif, 18ch max
│  │ priya@loam.studio           │ │       update.”                             │
│  └─────────────────────────────┘ │       Ines Halvorsen                       │
│  ┌ Password ───────────────(o)─┐ │       Head of Product, Fjord Bank          │
│  └─────────────────────────────┘ │                                            │
│  ☑ Keep me signed in   Forgot?   │                                            │
│  [        Continue          ]    │                                            │
│  ──────────── OR ─────────────   │                                            │
│  [ ✉ Email me a magic link  ]    │  ▬▬  ──  ──                                │  dots 40×2
│  New to Mira? Create a workspace │  4.8 / 5 across 1,200 workspaces  SOC 2 · EU│
└──────────────────────────────────┴────────────────────────────────────────────┘
   45% = 576px · padding 32/48         55% = 704px · padding 40/56
  • <body> — display:grid; grid-template-columns: 45% 1fr.
  • <section class="form"> — flex column; .logo link at top; <form> vertically centred with margin: auto 0, width 360px.
    • .field — position:relative; <input placeholder=" "> then <label>; password field also has <button class="eye" aria-pressed aria-controls="pw"> with two SVGs (.on, .off).
    • .row — checkbox label + “Forgot password?” link.
    • .btn submit, .or rule, .btn.ghost#magic, .foot paragraph.
  • <aside class="panel" aria-label="What customers say"> — .tag, .quotes[aria-live="polite"] with three stacked <figure class="q"> (blockquote + figcaption), .dots group of three buttons, .meta row. Circles are .panel::before/::after.

Tokens

:root {
  /* colour — warm off-white left, deep green right, cream type on green */
  --bg:         #fbfaf7;  /* left column */
  --field:      #ffffff;  /* input surface */
  --ink:        #1b1f1c;  /* headings, input text, links */
  --ink-2:      #5d645f;  /* lede, floated labels, footer text */
  --ink-3:      #8c938e;  /* resting labels, eye icon, "or" */
  --line:       #dcdfda;  /* field borders, ghost button, rules */
  --line-focus: #1b1f1c;  /* focused field border */
  --green:      #0f2e26;  /* panel, primary button */
  --green-2:    #173d33;  /* primary button hover */
  --cream:      #f1e9d6;  /* panel text, active dot, button label */
  --cream-2:    #b9b19b;  /* panel secondary text, italic emphasis */
  --accent:     #1f6b52;  /* focus ring, magic-link sent state */
  --ring:       rgba(31, 107, 82, .15);
  --circle:     rgba(241, 233, 214, .14);

  /* type */
  --serif: "DM Serif Display", Georgia, serif;
  --sans:  "DM Sans", system-ui, sans-serif;
  --quote-size: 40px;    /* 32 ≤1100 */

  /* layout */
  --split: 45%;
  --form-w: 360px;
  --field-h: 56px;
  --btn-h: 50px;
  --eye: 40px;
  --r: 8px;
  --r-eye: 6px;

  /* motion */
  --t-micro: 160ms;
  --t-label: 180ms;
  --t-fade: 600ms;
  --t-rotate: 5000ms;
  --ease: cubic-bezier(.2, .7, .2, 1);
  --expo: cubic-bezier(.16, 1, .3, 1);
}

Typography

RoleFamilySizeWeightLine-heightTrackingNotes
WordmarkDM Serif Display24px4001−0.01em26px two-ring logo mark before it
HeadingDM Serif Display36px4001.1−0.01em
LedeDM Sans15px4001.50--ink-2
Input textDM Sans15px40010padding 22px 48px 6px 14px
Label at restDM Sans15px40010--ink-3, left:14px; top:18px
Label floatedDM Sans15px × .78 (≈11.7px)40010--ink-2, translateY(-10px)
Checkbox / linksDM Sans13px4001.50links underlined, text-underline-offset: 3px
ButtonsDM Sans15px50010
“OR” rule labelDM Sans12px4001+0.06emUPPERCASE, --ink-3
Panel tagDM Sans12px4001+0.14emUPPERCASE, --cream-2
QuoteDM Serif Display40px4001.2−0.01emmax-width: 18ch; text-indent: -.4em (hanging open quote); <em> italic in --cream-2
AttributionDM Sans14px500 name / 400 role1.50name --cream, role --cream-2
Panel metaDM Sans12px4001.50--cream-2

Motion

ElementTriggerPropertyFrom → ToDurationEasingNotes
.field labelinput focus / has valuetransform, colornone, --ink-3 → translateY(-10px) scale(.78), --ink-2180ms--easetransform-origin: left top
.field inputhover / focusborder-color, box-shadow--line → --ink-3 / --ink + 3px ring160ms--ease
.btnhoverbackground--green → --green-2160ms--ease
.btn.ghosthoverborder-color, background--line → --ink, transparent → white160ms--ease
.btn.ghostclick → sentborder-color, color→ --accent160ms--easeone-way
.qevery 5000ms / dot clickopacity1 → 0 (outgoing), 0 → 1 (incoming)600ms--easesimultaneous; figures are stacked with position:absolute; inset:0
.dots button::beforecurrent / hoverbackground28% cream → cream / --cream-2160ms--ease
.eyehovercolor, background--ink-3 → --ink, transparent → --bg160ms--ease

Timer: setInterval(next, 5000); cleared on pointerenter of the panel and on dot click (then restarted), restarted on pointerleave. Reduced motion: .q { transition: none }.

States

  • Field rest: white, 1px --line border, 8px radius, 56px tall.
  • Field hover: border --ink-3.
  • Field focus: border --ink, box-shadow: 0 0 0 3px var(--ring), no outline.
  • Field filled (blurred): label stays floated (:not(:placeholder-shown)).
  • Field error (not in demo): border --error: #a9401f, a 13px message below in the same colour, aria-describedby linking them.
  • Eye pressed: slashed icon, aria-pressed="true", input type text.
  • Primary button: fill --green, text --cream; hover --green-2; focus-visible 2px --accent outline at 3px offset.
  • Ghost button: transparent, 1px --line, --ink text; hover border --ink + white fill.
  • Ghost sent: border and text --accent, cursor default, label replaced; not disabled (so it stays readable), but clicks are ignored.
  • Dot current: aria-current="true", bar --cream; others 28% cream; hover --cream-2.
  • Panel hover: rotation paused (no visual change).

Accessibility

  • Labels are real <label for> elements positioned over the input; the placeholder=" " (single space) exists only so :placeholder-shown works. Never use the placeholder as the label.
  • The eye toggle is a <button type="button" aria-pressed aria-controls="pw"> with a live aria-label. After toggling, focus returns to the input so keyboard users continue typing.
  • The magic-link button changes its own text; because it stays a focused button, screen readers announce the new label. If you prefer, add aria-live="polite" to a status paragraph instead.
  • Quotes live in .quotes[aria-live="polite"]; only the visible figure is announced because hidden ones are opacity:0 — if that causes double announcements in your framework, toggle aria-hidden on the inactive figures.
  • Dots are buttons with aria-label="Testimonial n" and aria-current on the active one, inside role="group" aria-label="Choose testimonial". Hit area 40 × 20 around a 2px bar.
  • Keyboard order: logo → email → password → eye → checkbox → forgot link → Continue → magic link → Create a workspace → dot 1–3.
  • Contrast: --ink-2 on --bg 6.7:1; --ink-3 on white 3.4:1, used only for the resting label (which becomes --ink-2 once floated) and the 12px “OR”; --cream on --green 13.6:1; --cream-2 on --green 7.3:1; --cream on --green button 13.6:1.
  • Checkbox uses accent-color: var(--green) so the native control matches.

Responsive rules

  • ≥ 1280: as specified (576 / 704).
  • 1024–1279: same split; form padding 28px 36px; panel padding 32px 40px; quote 32px.
  • 768–1023: panel hidden; the form column takes full width, form stays 360px and centred; page scrolls if shorter than 640px tall.
  • < 640: form width 100% with 24px padding; field height stays 56px; buttons stay 50px; footer paragraph wraps.
  • Heights: the form is vertically centred by margin: auto 0; the quotes block has min-height: 320px and sits in the panel’s flexible middle, so the panel works down to ~640px tall.

Acceptance checklist

  • Columns are 45% / 55% at 1280 (576px / 704px); left background #fbfaf7, right #0f2e26.
  • Form is 360px wide and vertically centred; heading is DM Serif Display 36px.
  • Fields are 56px tall with 1px #dcdfda borders and 8px radius; label floats via translateY(-10px) scale(.78) over 180ms on focus or when filled.
  • The prefilled email shows its label already floated on first paint, with no animation.
  • Focused field shows a #1b1f1c border plus a 3px rgba(31,107,82,.15) ring.
  • Eye button is 40 × 40, toggles input type, swaps icon, updates aria-pressed/aria-label, and refocuses the input.
  • Magic-link button turns #1f6b52 and reads “Link sent to · check your email” after one click; further clicks are ignored.
  • Testimonials crossfade over 600ms every 5000ms; dots reflect the current quote; clicking a dot jumps and restarts the timer.
  • Hovering the green panel pauses rotation; leaving resumes it.
  • Two 420px hairline circles (rgba(241,233,214,.14)) sit in the panel’s top-right corner, clipped by the panel.
  • Focus rings (2px #1f6b52, 3px offset) are visible on every control including the dots.
  • No layout shift occurs when labels float or when the magic-link text changes (button height stays 50px).
  • Reduced motion: quote swaps are instant.

Implementation notes

Floating label with zero layout shift. Reserve the label’s floated position inside the input’s padding; the label is absolutely positioned and only transforms:

.field { position: relative; }
.field input { height: 56px; padding: 22px 48px 6px 14px; }
.field label { position: absolute; left: 14px; top: 18px; color: var(--ink-3); pointer-events: none;
  transform-origin: left top; transition: transform 180ms var(--ease), color 180ms var(--ease); }
.field input:focus + label,
.field input:not(:placeholder-shown) + label { transform: translateY(-10px) scale(.78); color: var(--ink-2); }

placeholder=" " must be exactly one space; an empty string makes :placeholder-shown false in some browsers.

Crossfade by stacking, not by swapping DOM:

.quotes { position: relative; min-height: 320px; }
.q { position: absolute; inset: 0; opacity: 0; transition: opacity 600ms var(--ease);
     display: flex; flex-direction: column; justify-content: flex-end; }
.q.on { opacity: 1; }
let i = 0, timer;
const show = n => { i = (n + qs.length) % qs.length;
  qs.forEach((q, k) => q.classList.toggle('on', k === i));
  dots.forEach((d, k) => k === i ? d.setAttribute('aria-current', 'true') : d.removeAttribute('aria-current')); };
const start = () => { clearInterval(timer); timer = setInterval(() => show(i + 1), 5000); };
panel.addEventListener('pointerenter', () => clearInterval(timer));
panel.addEventListener('pointerleave', start);

Password toggle keeps focus in the field: after flipping input.type, call input.focus(); set both aria-pressed and the label text.

Common mistakes: animating font-size on the label instead of scale (blurry, janky); using display:none for inactive quotes (kills the crossfade); disabling the magic-link button after sending (greys out the confirmation text below 4.5:1); forgetting overflow:hidden on the panel so the circles spill into the form column.

Details

Palette
Type
DM Serif Display · DM Sans
Motion
Subtle motion
Build
One session
Designed bySusan AcharyaKathmandu

Studied

Igloo

A product with one job. Do not invent a homepage they did not ship.

All sources