GODRICH
#css-animation#button#loader#transition#scroll#dashboard#ui#design-trends

Register Form Design: 9 Keyboard-Friendly Inputs

Register form design lives or dies in the fields, not the submit button. These nine inputs cover everything a signup screen asks for, from the email box to the

Auto-plays · click a tile to jump to its section · all nine in one zip

The order follows the screen, not the usual register form design html template. The email box up top (01) says what it wants with an icon or a unit before you touch it, the name field (02) lifts its label out of the way to save space, and the birthdate (03) and phone number (04) reformat digits into the shape a person reads. Next come the fields you fill by picking from a list, the address (05) and the interests (06). The last three cover what happens while someone writes a bio: mentions and hashtags pick up color as you type (07), the character counter watches the limit (08), and the clear button empties the field in one tap when you want a do-over (09). The three that open a list (04, 05, 06) carry role="listbox" with aria-activedescendant, so the arrow keys genuinely work, and four of the nine answer Escape. The same input pillar widened to payment and notes is CSS Input Design, and judging whether what's typed is right or wrong is split out into 9 Form Validation UI Patterns.

01Input with icon or unit prefix/suffix

What this register form design ui shows

An email icon sits on the left and a kg unit on the right, so each field announces what it wants before you type.

Input with icon or unit prefix/suffix
Open in a new tab

01 · Input with icon or unit prefix/suffix — Measured area 1.326% · moving frames 11/23Original by GODRICH · SCSS and React ship in the zip · See the password

아이콘 접두단위 접미포커스 테두리

Where to look in the code

On focus the 2px border, the icon, and the unit all shift to the same color, and the icon and unit carry aria-hidden="true" with pointer-events: none so they never steal the click.

  &__input {
    box-sizing: border-box;
    width: 100%;
    border: 2px solid transparent;
    border-radius: $r-control;
    padding: $sp-3 $sp-4;
    font-weight: 500;
    font-size: clamp(14px, 4.2vw, 16px);
    font-family: inherit;
    color: $stage-ink;
    background: #fff;
    transition: border-color $duration $easing;

    &--prefix { padding-left: $sp-10; }
    &--suffix { padding-right: $sp-8; }

    &:focus {
      outline: none;
      border-color: $color;
      & ~ .affix__unit { color: $color; }
    }
  }

02Floating label input

What happens on screen

In the empty field the label rests in the middle of the box; the moment the field takes focus or holds a character, that label lifts and shrinks with translateY(-14px) scale(.78).

Floating label input
Open in a new tab

02 · Floating label input — Measured area 1.207% · moving frames 9/23Original by GODRICH · SCSS and React ship in the zip · See the password

label transform포커스 색 전환채움 상태 유지

How to read the code

No JavaScript is involved — just the :focus and :not(:placeholder-shown) pseudo-classes plus the + sibling combinator, fed by a single placeholder=" " in the markup.

  &__label {
    position: absolute;
    left: $sp-4;
    top: 50%;
    transform: translateY(-50%) scale(1);
    transform-origin: left center;
    font-size: 16px;
    font-weight: 500;
    font-family: inherit;
    color: rgba(23, 20, 26, .48);
    pointer-events: none;
    transition: transform $duration $easing, color $duration $easing;
  }

  &__input:focus + &__label,
  &__input:not(:placeholder-shown) + &__label {
    transform: translateY(-14px) scale(.78);
    color: $color;
  }

03Date input with auto-slash

What moves

Type digits only and slashes appear after the fourth and sixth digits, so 2026/09/11 assembles itself.

Date input with auto-slash
Open in a new tab

03 · Date input with auto-slash — Measured area 8.603% · moving frames 10/23Original by GODRICH · SCSS and React ship in the zip · See the password

YYYY/MM/DD숫자만 허용자동 슬래시

What to change

There is no calendar to open — the field is a plain text box with inputmode="numeric", which brings up the number pad on a phone, and anything past eight digits is dropped.

  function format(raw) {
    var digits = raw.replace(/\D/g, '').slice(0, 8);
    var out = digits.slice(0, 4);
    if (digits.length > 4) out += '/' + digits.slice(4, 6);
    if (digits.length > 6) out += '/' + digits.slice(6, 8);
    return out;
  }

  input.addEventListener('input', function () {
    input.value = format(input.value);
  });

04Phone input with country code select

What you see

Pick a country from the flag button and the dial code swaps while the number field rewrites itself in that country's digit grouping.

Phone input with country code select
Open in a new tab

04 · Phone input with country code select — Measured area 14.364% · moving frames 14/23Original by GODRICH · SCSS and React ship in the zip · See the password

국기 드롭다운국가별 자릿수키보드로 국가 탐색

Where to look in the code

South Korea's +82 groups as 3-4-4, the United States' +1 as 3-3-4, and Japan's +81 as 2-4-4, so the same ten digits read differently depending on where the number lives.

  trigger.addEventListener('keydown', function (e) {
    if (!open) {
      if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || e.key === ' ') {
        e.preventDefault();
        openListbox();
      }
      return;
    }
    if (e.key === 'ArrowDown') { e.preventDefault(); highlight((active + 1) % options.length); }
    else if (e.key === 'ArrowUp') { e.preventDefault(); highlight((active - 1 + options.length) % options.length); }
    else if (e.key === 'Enter') { e.preventDefault(); selectCountry(options[active].dataset.code); closeListbox(true); }
    else if (e.key === 'Escape') { e.preventDefault(); closeListbox(true); }
    else if (e.key === 'Tab') { closeListbox(false); }
  });

05Address autocomplete style input

What happens on screen

As you type, the suggestion list below filters five fixed addresses down to the matching rows, and the arrow keys plus Enter fill the field.

Address autocomplete style input
Open in a new tab

05 · Address autocomplete style input — Measured area 15.744% · moving frames 7/23Original by GODRICH · SCSS and React ship in the zip · See the password

role listbox화살표 키 탐색정적 후보 목록

How to read the code

The input carries role="combobox" with aria-autocomplete="list", and the rows come from a static list, so there's no API key to set up.

  input.addEventListener('keydown', function (e) {
    if (e.key === 'ArrowDown') {
      e.preventDefault();
      if (!open && matches.length) { openList(); setActive(0); return; }
      if (open && matches.length) setActive(Math.min(active + 1, matches.length - 1));
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      if (open) { if (active <= 0) setActive(-1); else setActive(active - 1); }
    } else if (e.key === 'Enter') {
      if (open && active > -1) { e.preventDefault(); selectOption(active, true); }
    } else if (e.key === 'Escape') {
      if (open) { e.preventDefault(); closeList(); setActive(-1); }
    }
  });

06Multi-select combobox

What moves

Four rows — design, dev, marketing, planning — stack every pick as a pill-shaped chip inside the field.

Multi-select combobox
Open in a new tab

06 · Multi-select combobox — Measured area 18.629% · moving frames 13/23Original by GODRICH · SCSS and React ship in the zip · See the password

체크박스 목록선택 칩 표시키보드로 토글

What to change

The list carries aria-multiselectable="true" so a second pick never deselects the first, and each chip arrives through the chip-pop keyframes, overshooting slightly before settling at full size.

@keyframes chip-pop {
  0%   { transform: scale(.5); opacity: 0; }
  70%  { transform: scale(1.06); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .multi__listbox { transition: none !important; }
  .multi__trigger, .multi__chevron, .multi__check { transition: none; }
  .multi__chip { animation: none; }
}

07Mention and hashtag highlight input

What you see

The moment you type @name or #tag, that word alone picks up color while the rest of the sentence stays plain.

Mention and hashtag highlight input
Open in a new tab

07 · Mention and hashtag highlight input — Measured area 1.192% · moving frames 15/23Original by GODRICH · SCSS and React ship in the zip · See the password

contenteditable실시간 하이라이트커서 위치 유지

Where to look in the code

There is no contenteditable here — a textarea with invisible text sits on a backdrop layer of the same width and wrapping rules, so the caret stays where it belongs.

  &__backdrop,
  &__input {
    box-sizing: border-box;
    width: 100%;
    min-height: 96px;
    max-height: 160px;
    padding: $sp-3 $sp-4;
    border: 2px solid transparent;
    border-radius: $r-control;
    font-weight: 500;
    font-size: clamp(13px, 3.8vw, 15px);
    line-height: 1.5;
    font-family: inherit;
    white-space: pre-wrap;
    word-wrap: break-word;
    overflow-wrap: break-word;
  }

08Character counter input

What happens on screen

The number in the corner climbs from 0/80, and once twelve characters remain, both the count and the border turn red.

Character counter input
Open in a new tab

08 · Character counter input — Measured area 2.316% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

maxlength 연동aria-live 안내임계값 색 전환

How to read the code

The limit itself lives in the HTML maxlength="80", and the count carries aria-live="polite", so someone who isn't watching the screen still hears the limit approach.

  function update() {
    var len = area.value.length;
    var remaining = MAX - len;
    count.textContent = len + '/' + MAX;
    if (remaining <= WARN) {
      count.classList.add('is-warn');
      area.classList.add('is-warn');
    } else {
      count.classList.remove('is-warn');
      area.classList.remove('is-warn');
    }
  }
  area.addEventListener('input', update);

09Input with clear (X) button

What moves

Type one character and a round X button surfaces on the right; click it and the field empties while focus stays exactly where it was.

Input with clear (X) button
Open in a new tab

09 · Input with clear (X) button — Measured area 2.114% · moving frames 11/23Original by GODRICH · SCSS and React ship in the zip · See the password

조건부 표시aria-label 지우기focus 유지

What to change

With no value the button sits at opacity: 0 with pointer-events: none, fully out of the way, and it only reappears the moment there's something to clear.

  &__clear {
    position: absolute;
    top: 50%;
    right: $sp-2;
    transform: translateY(-50%) scale(.5);
    width: 24px;
    height: 24px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 50%;
    background: rgba(23, 20, 26, .08);
    color: $stage-ink;
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transition: opacity $duration $easing, transform $duration $easing, background $duration $easing;

    &:hover, &:focus-visible { background: rgba(23, 20, 26, .16); }
  }

Where it breaks — the trap

The break showed up at 320px wide, and it was vertical. Items 07 and 08 both hold a three-row textarea, and with the field's minimum height at 96px the whole card grew 2px taller than the stage in 07 and 3px taller in 08, clipping the bottom border. At the 480px demo size the slack had hidden the problem, so eyes on a wide screen alone could never have caught it. The fix was not to pull the textarea out of the flow but to add an @media (max-width: 340px) block that lowers min-height from 96px to 88px once the viewport narrows past that point. The label and the hint stayed untouched, and shaving those 8px off the field alone cleared the overflow in both items. Solving 04, 05, and 06 the dropdown way instead is what 9 Select Dropdown Design Patterns covers. The sources carrying this rule sit inside the zip, which opens with the archive password ttw779pg.

Accessibility

Under prefers-reduced-motion: reduce, all nine drop their transitions and keep every state value. Border and icon colors switch instantly, the floating label stands in its lifted position, and 06's chips appear without the chip-pop bounce. The three list-openers (04, 05, 06) announce openness through aria-expanded and move a virtual cursor with aria-activedescendant instead of moving focus, and 08's count is read out through aria-live="polite". Focus returns through focus({ preventScroll: true }) because these demos sit in iframes inside the article, and a bare focus() would drag the reader's page down toward them. The meaning of the input attributes used here is documented in the MDN input element reference.

More parts in the same vein live in the forms category, and the parts that shake on invalid input live in the error category.

FAQ

Why build a birthdate field instead of letting the browser open a calendar?

The native calendar is dependable, but it looks different in every browser, and for someone who types fast, opening and dismissing it is slower than typing. This field takes digits only and assembles them into the 2026/09/11 shape, so the format never drifts — no calendar required. On screens that do want a calendar, the typed field and a calendar picker can sit side by side.

Does this signup form design work without a mouse?

Yes. Items 01, 02, 03, 07, and 08 are ordinary inputs and textareas, so whatever the browser does for free is what they do. The list-openers 04, 05, and 06 move with the arrow keys, commit on Enter, and close on Escape; 04 and 06 wrap around at either end and close the list when you Tab away. In 09, pressing Escape in the field clears the value. Break these nine fields across several screens and you're in 9 Multi Step Form UI Patterns territory.

Can I change the limit or the colors in the React version?

The character limit in 08 is a max prop that defaults to 80. The warn threshold of twelve remaining characters is a constant inside the module, so changing it means editing that line. Colors and timing are inlined in each React module, so edit them where they appear; in the vanilla sources the same three knobs sit at the top of each style.scss — $duration, $easing, and $color.

Enter the archive password

The password is inside this article. You will find it as you read.