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
Use it
Free to use · credit appreciated1 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.
<!-- Design Lounge Nº 063 · "Split sign-in" · designlounge.vercel.app -->
# Split sign-in
> **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 ───────────── │ │
… 181 more lines. Copy or download for the full brief. Loading the source…Brief
221 linesBuild 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
- 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.
- Hover a field: border
--line→--ink-3. Focus: border--ink, 3px ringrgba(31,107,82,.15), label lifts (translateY(-10px) scale(.78)) and darkens over 180ms. - Type into the password field: label stays lifted after blur because the input is no longer
:placeholder-shown. - 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-pressedandaria-label(“Show password” / “Hide password”) update, and focus returns to the input. - Hover “Continue”: background
--green→--green-2. Submit is prevented in the demo. - 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. - 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.
- Click a dot: jumps to that quote and restarts the 5s timer. Hovering anywhere on the green panel pauses rotation; leaving resumes it.
- 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;.logolink at top;<form>vertically centred withmargin: 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..btnsubmit,.orrule,.btn.ghost#magic,.footparagraph.
<aside class="panel" aria-label="What customers say">—.tag,.quotes[aria-live="polite"]with three stacked<figure class="q">(blockquote + figcaption),.dotsgroup of three buttons,.metarow. 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
| Role | Family | Size | Weight | Line-height | Tracking | Notes |
|---|---|---|---|---|---|---|
| Wordmark | DM Serif Display | 24px | 400 | 1 | −0.01em | 26px two-ring logo mark before it |
| Heading | DM Serif Display | 36px | 400 | 1.1 | −0.01em | |
| Lede | DM Sans | 15px | 400 | 1.5 | 0 | --ink-2 |
| Input text | DM Sans | 15px | 400 | 1 | 0 | padding 22px 48px 6px 14px |
| Label at rest | DM Sans | 15px | 400 | 1 | 0 | --ink-3, left:14px; top:18px |
| Label floated | DM Sans | 15px × .78 (≈11.7px) | 400 | 1 | 0 | --ink-2, translateY(-10px) |
| Checkbox / links | DM Sans | 13px | 400 | 1.5 | 0 | links underlined, text-underline-offset: 3px |
| Buttons | DM Sans | 15px | 500 | 1 | 0 | |
| “OR” rule label | DM Sans | 12px | 400 | 1 | +0.06em | UPPERCASE, --ink-3 |
| Panel tag | DM Sans | 12px | 400 | 1 | +0.14em | UPPERCASE, --cream-2 |
| Quote | DM Serif Display | 40px | 400 | 1.2 | −0.01em | max-width: 18ch; text-indent: -.4em (hanging open quote); <em> italic in --cream-2 |
| Attribution | DM Sans | 14px | 500 name / 400 role | 1.5 | 0 | name --cream, role --cream-2 |
| Panel meta | DM Sans | 12px | 400 | 1.5 | 0 | --cream-2 |
Motion
| Element | Trigger | Property | From → To | Duration | Easing | Notes |
|---|---|---|---|---|---|---|
.field label | input focus / has value | transform, color | none, --ink-3 → translateY(-10px) scale(.78), --ink-2 | 180ms | --ease | transform-origin: left top |
.field input | hover / focus | border-color, box-shadow | --line → --ink-3 / --ink + 3px ring | 160ms | --ease | |
.btn | hover | background | --green → --green-2 | 160ms | --ease | |
.btn.ghost | hover | border-color, background | --line → --ink, transparent → white | 160ms | --ease | |
.btn.ghost | click → sent | border-color, color | → --accent | 160ms | --ease | one-way |
.q | every 5000ms / dot click | opacity | 1 → 0 (outgoing), 0 → 1 (incoming) | 600ms | --ease | simultaneous; figures are stacked with position:absolute; inset:0 |
.dots button::before | current / hover | background | 28% cream → cream / --cream-2 | 160ms | --ease | |
.eye | hover | color, background | --ink-3 → --ink, transparent → --bg | 160ms | --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
--lineborder, 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-describedbylinking them. - Eye pressed: slashed icon,
aria-pressed="true", input typetext. - Primary button: fill
--green, text--cream; hover--green-2; focus-visible 2px--accentoutline at 3px offset. - Ghost button: transparent, 1px
--line,--inktext; 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; theplaceholder=" "(single space) exists only so:placeholder-shownworks. Never use the placeholder as the label. - The eye toggle is a
<button type="button" aria-pressed aria-controls="pw">with a livearia-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 areopacity:0— if that causes double announcements in your framework, togglearia-hiddenon the inactive figures. - Dots are buttons with
aria-label="Testimonial n"andaria-currenton the active one, insiderole="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-2on--bg6.7:1;--ink-3on white 3.4:1, used only for the resting label (which becomes--ink-2once floated) and the 12px “OR”;--creamon--green13.6:1;--cream-2on--green7.3:1;--creamon--greenbutton 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 padding32px 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 hasmin-height: 320pxand 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
#dcdfdaborders and 8px radius; label floats viatranslateY(-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
#1b1f1cborder plus a 3pxrgba(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
#1f6b52and 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
- Tags
On the shelves