Design Lounge
Install

Nº 455 of 520

Three-step phone sign-up

A soft, cream-and-green phone sign-up in three sliding steps: name and email, a password with live ticking rules, then pick 3+ interests as chips.

Preview

Open full size
390 × 844 · 15.2 KB · 248 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 "Three-step phone sign-up" (piece phone-sign-up-steps) 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

286 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. When a kit is locked, map colours onto the kit tokens. This is the Soft family: 14px radii on fields and chips, pill buttons. The motion language is Material 3 Expressive.

What it is

The sign-up flow of Sprig, a fictional app for home growers. It is three steps in one screen. Step 1 asks for a name and an email. Step 2 asks for a password and ticks off four rules as you type. Step 3 asks you to pick at least three things you grow, as chips. A segmented bar at the top shows the step. Back and Continue sit in a fixed bar at the bottom. Steps slide sideways in 420ms. The colours are warm cream, a deep garden green and a soft green tonal fill. Nunito carries everything, at 800 for headings and 600 for inputs. The detail worth copying is the rules list. Each rule’s circle fills green and draws a tick the moment the password meets it, so the user never guesses what is wrong.

Reference behaviour

  1. First frame: step 1. Segment 1 is green, segments 2 and 3 are track colour. The label reads “Step 1 of 3 · About you”. The heading reads “Let’s get you growing”. Name is prefilled “Mina Gurung”. Email is prefilled “mina.gurung@fernpost.org”. Back is shown at 45% opacity and is disabled. Continue is a green pill.
  2. Clear the name and tap Continue. The error “Tell us your name.” shows under it in --danger. The field border turns --danger. Focus moves to the name field.
  3. Type a bad email and tap Continue. The error reads “Enter an email like name@example.com.” An empty email reads “Enter your email address.”
  4. While an error shows, typing re-checks the field. The error clears as soon as the value is valid.
  5. Tap Continue with valid fields. The track slides left by one step in 420ms. Segment 2 fills left to right in 320ms. The label becomes “Step 2 of 3 · Password”. Back becomes active. Focus moves to the new heading after 200ms.
  6. Step 2 heading: “Make a password”. Four rules are listed: “At least 10 characters”, “One number”, “One capital letter”, “Not your name or email”. Each starts with an empty 22px circle.
  7. Type in the password. Each rule that passes fills its circle green, scales it to 1.08, and draws a white tick in 320ms. A rule that stops passing reverses. The line under the list reads “N of 4 rules met”.
  8. “Not your name or email” fails if the password contains the first name or the email’s local part, when either is longer than 2 characters. It also fails while the field is empty.
  9. Tap Continue with fewer than 4 rules met. The error reads “Meet all four rules to continue.” Focus moves to the password.
  10. The eye button shows and hides the password. Its label flips between “Show password” and “Hide password”.
  11. Step 3 heading: “What do you grow?”. Ten chips: Herbs, Tomatoes, Balcony pots, Composting, Seed saving, Houseplants, Native flowers, Fruit trees, Mushrooms, Chillies. Herbs and Tomatoes start picked.
  12. Tap a chip to toggle it. A picked chip fills green, swaps its plus icon for a tick, and its radius grows from 14px to 22px in 320ms. The tally reads “2 of 3 picked. 1 more to go.” or “3 picked. Good start.”
  13. On step 3, Continue reads “Create account”. With fewer than 3 picked, the error reads “Pick at least 3 to continue.”
  14. With 3 or more picked, Create account shows a spinner and the label “Creating account” for 1300ms, then “Welcome, Mina” for 2200ms. Then the demo clears the password and slides back to step 1.
  15. Back slides one step right. Picked chips and typed values stay.
  16. Enter in any input acts as Continue.

Structure

390 x 844
+--------------------------------------+
| 54px top clearance                   |
| (leaf) Sprig                         | 18px / 800, green
| [======][------][------]             | 3 segments, 6px tall, 6px gap
| Step 1 of 3 · About you              | 13px / 700
|--------------------------------------|
| sliding viewport, flex 1             |
| Let's get you growing                | 32px / 800
| Your plot, your seed swaps and ...   | 16px, 30ch
|                                      |
| Your name                            | 14px / 700 label
| [ Mina Gurung                      ] | 56px, 14px radius
| Email                                |
| [ mina.gurung@fernpost.org         ] |
|--------------------------------------| 1px --line
| [ < Back ]  [ Continue          -> ] | 56px pills, 12px gap
| 34px bottom clearance                |
+--------------------------------------+
  • header.top holds the brand row, the progress bar and the step label.
  • The progress bar is a div with role="progressbar", aria-valuemin="1", aria-valuemax="3", aria-valuenow and aria-valuetext.
  • main.view is overflow: hidden. Inside it, a form.track is 300% wide and holds three section.step, each one third wide.
  • Each step has one h1 with tabindex="-1", so focus can land on it.
  • Off-screen steps carry the inert attribute.
  • The rules are a ul with aria-label="Password rules". Each li has a visually hidden ”, met” or ”, not met”.
  • The chips are buttons with aria-pressed, inside a div role="group" labelled by the step heading.
  • nav.bar holds Back and Continue. It is outside the sliding track, so it never moves.
  • A hidden role="status" paragraph speaks the account result.

Tokens

:root {
  /* colour */
  --bg: #f6f0e3;            /* cream page */
  --surface: #fffbf2;       /* fields, chips, rules card */
  --ink: #1e2a22;           /* headings, input text */
  --ink-2: #4a5a4f;         /* lede, labels of quiet text */
  --ink-3: #6e7a70;         /* rare muted text */
  --line: #e2d8c3;          /* field borders, bar rule */
  --primary: #1f4d3a;       /* deep green: Continue, filled segments, picked chips, ticks */
  --on-primary: #fffbf2;
  --primary-soft: #dce8d9;  /* tonal: Back button, focus halo */
  --track: #e8dfcc;         /* empty segments */
  --danger: #a63d2a;        /* errors */

  /* type */
  --sans: "Nunito", system-ui, sans-serif;

  /* shape */
  --radius: 14px;
  --pill: 999px;
  --control: 56px;
  --hit: 44px;

  /* space: 4px base */
  --s-2: 8px; --s-3: 12px; --s-4: 16px; --s-5: 20px; --s-6: 24px;

  /* motion: M3 Expressive */
  --emph: cubic-bezier(0.2, 0, 0, 1);
  --decel: cubic-bezier(0.05, 0.7, 0.1, 1);
  --fast: 160ms;
  --mid: 320ms;
  --slide: 420ms;
}

Typography

RoleFamilySizeWeightLine-heightTracking
BrandNunito18px8001-0.01em
Step labelNunito13px7001.450.02em
HeadingNunito32px8001.08-0.02em
LedeNunito16px4001.450
Field labelNunito14px7001.450
InputNunito17px60056px box0
Rule itemNunito15px6001.450
ChipNunito15px70010
ButtonsNunito17px80010
ErrorNunito14px6001.450
  • One family, used at 400, 600, 700 and 800. Do not add a second face.
  • Headings are sentence case. Never caps.
  • At 360 wide, the heading drops to 28px.

Motion

ThingTriggerPropertyFrom → toDurationEasingReduced motion
Step trackContinue or BacktranslateX-33.33% × old → × new420ms--emphno slide; new step fades in 120ms
Progress segmentstep changewidth of inner fill0 → 100%, or back320ms--emph1ms
Rule circlerule passesbackground, border, scaleline → green, 1 → 1.08320ms--decel1ms
Rule tickrule passesstroke-dashoffset20 → 0320ms, 60ms delay--decel1ms
Chiptogglebackground, border, radiussurface → green, 14px → 22px160ms colour, 320ms radius--emph, --decel1ms
Field focusfocusborder, box-shadowline → green, 0 → 3px soft halo160ms--emphsame
Button pressactivescale1 → 0.97160ms--emphsame
Spinnercreatingrotate0 → 1turn700ms looplinear1.6s loop
  • The slide moves the whole track. Steps do not fade while sliding.
  • Focus moves to the new heading 200ms after the slide starts, so the screen reader hears the new step while it lands.

States

  • Field resting: --surface, 1.5px --line border, 14px radius, 56px tall.
  • Field focus: border --primary, 3px --primary-soft halo.
  • Field error: border --danger, error text under it, aria-invalid="true".
  • Rule not met: empty circle with a 2px --line border, text --ink-2, hidden text ”, not met”.
  • Rule met: green circle, white tick, text --ink, hidden text ”, met”.
  • Chip resting: --surface, 1.5px --line border, plus icon, 44px tall. Hover border --primary.
  • Chip picked: --primary fill, --on-primary text, tick icon, radius 22px, aria-pressed="true".
  • Segment empty: --track. Segment done or current: --primary.
  • Back on step 1: disabled, opacity 0.45, no press scale.
  • Back active: --primary-soft fill, --primary text, 112px wide.
  • Continue: --primary fill, flex 1, arrow on the right. Hover #18402f. Label “Create account” on step 3.
  • Continue busy: arrow swaps for a 20px ring spinner, aria-busy="true", taps ignored.
  • Continue done: label “Welcome, Mina”.
  • Focus-visible: 3px --primary outline, 3px offset.

Accessibility

  • Each step has one h1. Focus moves to it after every step change.
  • Off-screen steps are inert, so Tab never reaches a hidden field.
  • The progress bar has aria-valuetext="Step 2 of 3, Password". The visible label repeats it.
  • Inputs have visible label elements. Each has aria-describedby pointing at its error. The password also points at the rules list.
  • Autocomplete: name, email, and new-password.
  • Errors are aria-live="polite". On a failed Continue, focus the first bad field.
  • Rules use hidden ”, met” and ”, not met” text, so the state is not colour only. The “N of 4 rules met” line is a polite live region.
  • Chips are button elements with aria-pressed. The tally is linked by aria-describedby on the group and is a polite live region.
  • Keyboard: Tab moves through fields, chips, Back, Continue. Enter in an input acts as Continue. Space or Enter toggles a chip.
  • Contrast: #1e2a22 on #f6f0e3 is about 13:1. #4a5a4f on #f6f0e3 is about 6.5:1. #fffbf2 on #1f4d3a is about 9.3:1. #a63d2a on #f6f0e3 is about 5.6:1. #1f4d3a on #dce8d9 is about 7.5:1.
  • Hit targets: buttons 56px tall, chips 44px tall, eye 44×44.

Responsive rules

  • The frame is 390×844. Top padding is max(54px, env(safe-area-inset-top)). The bar’s bottom padding is max(34px, env(safe-area-inset-bottom)).
  • At 360 wide: heading 28px, Back 96px wide. Chips wrap to more rows.
  • If a step is taller than the viewport, that step scrolls on its own. The bar stays fixed.
  • When the keyboard opens, keep the bar above it. Do not hide Back and Continue.
  • At tablet width, centre a 440px column. Keep the slide inside that column only.
  • Do not draw a status bar.

Acceptance checklist

Always

  • Three steps, one screen. The track slides sideways. Back and Continue never move.
  • A segmented bar with one segment per step. Done and current segments are filled.
  • Off-screen steps are inert. Focus lands on the new step’s heading.
  • Continue validates the current step. It is never disabled. Errors use aria-describedby and aria-invalid.
  • Password rules tick live as the user types, with text state for screen readers.
  • Chips are toggle buttons with aria-pressed. A minimum count is stated and checked.
  • The last step’s button names the outcome, then shows busy and done states.
  • Back is disabled on step 1 and keeps typed values when used.
  • Every hit target is 44px or more. Buttons are 56px pills.
  • Reduced motion swaps the slide for a 120ms fade.

This demo

  • Brand row reads “Sprig” in #1f4d3a.
  • Step 1 heading “Let’s get you growing”, prefilled “Mina Gurung” and “mina.gurung@fernpost.org”.
  • The four rules read “At least 10 characters”, “One number”, “One capital letter”, “Not your name or email”.
  • Ten chips, with Herbs and Tomatoes picked at start. The tally reads “2 of 3 picked. 1 more to go.”
  • Step 3 button reads “Create account”, then “Creating account”, then “Welcome, Mina”.
  • Slide is 420ms cubic-bezier(0.2, 0, 0, 1).

Implementation notes

Slide the track, not the steps. One transform on a 300% wide row is cheaper and never desyncs. Mark the hidden steps inert.

.view { flex: 1; overflow: hidden; }
.track { display: flex; width: 300%; height: 100%; transition: transform 420ms var(--emph); }
.step { width: calc(100% / 3); padding: 20px 24px 16px; overflow-y: auto; }
@media (prefers-reduced-motion: reduce) { .track { transition: none; } .step { transition: opacity 120ms linear; } }
function go(n) {
  i = n;
  track.style.transform = `translateX(${-100 / 3 * i}%)`;
  steps.forEach((s, k) => s.inert = k !== i);
  segs.forEach((s, k) => s.classList.toggle('on', k <= i));
  back.disabled = i === 0;
  label.textContent = i === 2 ? 'Create account' : 'Continue';
  setTimeout(() => steps[i].querySelector('h1').focus({ preventScroll: true }), 200);
}

Rules from one function. Compute every rule on each input and write both the class and the hidden text.

function rules() {
  const v = pw.value, low = v.toLowerCase();
  const first = nameI.value.trim().split(/\s+/)[0].toLowerCase();
  const local = emailI.value.split('@')[0].toLowerCase();
  const r = {
    len: v.length >= 10, num: /\d/.test(v), up: /[A-Z]/.test(v),
    own: v.length > 0 && !(first.length > 2 && low.includes(first)) && !(local.length > 2 && low.includes(local)),
  };
  let n = 0;
  list.querySelectorAll('li').forEach(li => {
    const ok = r[li.dataset.r]; n += ok;
    li.classList.toggle('ok', ok);
    li.querySelector('.st').textContent = ok ? ', met' : ', not met';
  });
  meter.textContent = `${n} of 4 rules met`;
  return n === 4;
}

The chip shape change is the M3 touch. Picked chips round from 14px to 22px. Keep the height fixed at 44px so rows do not reflow.

.chip { min-height: 44px; border-radius: 14px; transition: background-color 160ms var(--emph), border-radius 320ms var(--decel); }
.chip[aria-pressed="true"] { background: var(--primary); color: var(--on-primary); border-radius: 22px; }

Common mistakes:

  • Leaving off-screen steps focusable. Tab then jumps into a hidden field.
  • Disabling Continue until valid. The user cannot learn what is missing.
  • A strength meter bar instead of named rules. This piece names each rule.
  • Moving the bottom bar with the track.
  • Animating left or margin instead of transform.
  • Dots for progress. This piece uses segments, one per step.
  • A second accent colour for picked chips. Picked is the same green.
  • Resetting typed values when Back is pressed.
  • Chips at 32px tall. They are 44px.

Where it sits:

  1. New users arrive here from “Create an account” on phone-sign-in.
  2. After “Create account”, show the first home, or set up a passkey with auth-passkey-setup restyled to Soft.
  3. If the product wants a tour before sign up, ios-onboarding-carousel comes first. Do not put the carousel inside these steps.
  4. On web, the wider stepper is multi-step-form-stepper.

Rebuild order:

  1. Lay out the header, the sliding viewport and the fixed bar.
  2. Build the progress segments and the step label.
  3. Build step 1 fields and errors.
  4. Build step 2 with the eye toggle and the rules list.
  5. Build step 3 chips and the tally.
  6. Wire go(), inert, focus and the progress values.
  7. Wire validation per step and the busy and done states.
  8. Test with reduced motion and with Tab only.

Details

Palette
Type
Nunito
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