28 examples Responsive Uses JS intermediate

28 CSS Input Field Designs

A CSS input field is a styled text-input control — text, email, password, search, number, date, time, colour, file, range and more. These 28 hand-coded designs cover every common pattern: floating labels, underline sweeps, neon glow, frosted glass, notched outlines, live validation, OTP code entry, password strength meters, tag chips, phone with country code, file drop zones, currency / date / time / colour pickers, auto-grow textarea using field-sizing, inline edit, range sliders with live bubbles, credit-card auto-format, and a password reveal toggle. Every demo wraps a native input element so keyboard, autofill, autocomplete, validation, screen-reader, and form-submission semantics all work natively.

Twenty-eight premium CSS input field designs covering every common form pattern — floating labels, notched outlines, OTP codes, password strength meters, search with voice and suggestions, file drop zones, phone with country code, tag input, currency, date / time / colour pickers, auto-grow textarea, inline edit, range slider, credit card, password toggle and more. Built with semantic HTML, modern standards-based CSS (`:has()`, `:focus-within`, `:placeholder-shown`, `field-sizing`), scoped class-based styles, and JavaScript only where it adds real interaction. Each demo uses a different accent colour so you can see the palette range at a glance — drop any of them straight into your project.

28 unique inputs 18 Pure CSS 10 Light JS WCAG-friendly accessible Mobile-first responsive MIT free to use
01 / 28
Floating Label
Pure CSS
Classic floating label input. The placeholder lifts and shrinks above the field on focus — a calm, accessible pattern used across every modern product surface.
.if-float {
  position: relative; display: block;
  width: 100%; max-width: 280px;
}
.if-float input {
  width: 100%; box-sizing: border-box;
  padding: 18px 14px 8px;
  background: #1a1a22;
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 10px;
  color: #f0eeff; font-size: 14px;
  outline: none;
  transition: border-color .2s, background .2s;
}
.if-float input:focus { border-color: #7c6cff; background: #1f1f2a; }
.if-float-label {
  position: absolute; left: 14px; top: 14px;
  font-size: 13px; color: #9d9bbf;
  pointer-events: none;
  transition: transform .2s, font-size .2s, color .2s;
}
.if-float input:focus + .if-float-label,
.if-float input:not(:placeholder-shown) + .if-float-label {
  transform: translateY(-9px);
  font-size: 10px;
  color: #a78bfa;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
<label class="if-float">
  <input type="email" name="email" autocomplete="email" placeholder=" " required />
  <span class="if-float-label">Email address</span>
</label>
02 / 28
Underline Sweep
Pure CSS
Minimal underline-style input. The bottom border draws outward from the centre on focus — a subtle, premium touch driven by `:focus-within`.
.if-line {
  display: grid; gap: 6px;
  width: 100%; max-width: 280px;
  font-size: 11px;
  color: #9d9bbf;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.if-line-wrap {
  position: relative; display: block;
  border-bottom: 1px solid rgba(255,255,255,0.14);
}
.if-line-wrap::after {
  content: ''; position: absolute;
  left: 50%; right: 50%; bottom: -1px;
  height: 2px;
  background: linear-gradient(90deg, #ff6c8a, #ff9a76);
  transition: left .3s ease, right .3s ease;
}
.if-line-wrap:focus-within::after { left: 0; right: 0; }
.if-line input {
  width: 100%; box-sizing: border-box;
  padding: 8px 0 9px;
  background: transparent;
  border: 0; outline: none;
  color: #f0eeff;
  font-size: 14px; letter-spacing: 0;
  text-transform: none;
}
.if-line input::placeholder { color: #6b6987; }
<label class="if-line">
  <span class="if-line-label">Username</span>
  <span class="if-line-wrap">
    <input type="text" name="username" autocomplete="username" placeholder="@codefronts" required />
  </span>
</label>
03 / 28
Neon Glow
Pure CSS
Cyan neon-glow input. The border crisps and the box-shadow blooms on focus — perfect for dark dashboards, gaming UIs, and developer tools.
.if-neon {
  display: grid; gap: 6px;
  width: 100%; max-width: 280px;
}
.if-neon-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px; letter-spacing: 0.18em;
  color: #6cffff;
  text-shadow: 0 0 8px rgba(108,255,255,0.5);
}
.if-neon input {
  width: 100%; box-sizing: border-box;
  background: #0a0a14;
  border: 1px solid rgba(108,255,255,0.4);
  border-radius: 6px;
  padding: 11px 14px;
  color: #fff;
  font-family: 'JetBrains Mono', monospace;
  font-size: 13px;
  outline: none;
  transition: border-color .2s, box-shadow .2s, background .2s;
}
.if-neon input::placeholder { color: rgba(108,255,255,0.45); }
.if-neon input:focus {
  border-color: #6cffff;
  background: #0d1820;
  box-shadow:
    0 0 0 1px #6cffff,
    0 0 16px rgba(108,255,255,0.45),
    inset 0 0 12px rgba(108,255,255,0.12);
}
<label class="if-neon">
  <span class="if-neon-label">SECURE TOKEN</span>
  <input type="text" name="token" placeholder="sk_live_••••••••••••" required />
</label>
04 / 28
Glass Frosted
Pure CSS
Frosted-glass input on a soft gradient. Translucent surface with `backdrop-filter` blur and an inner highlight — an elegant fit for hero sections.
.if-glass {
  position: relative;
  width: 100%; max-width: 280px;
  border-radius: 14px;
  padding: 14px;
  isolation: isolate;
  overflow: hidden;
}
.if-glass-bg {
  position: absolute; inset: 0; z-index: -1;
  background:
    radial-gradient(40% 60% at 30% 30%, #f59e0b 0%, transparent 60%),
    radial-gradient(50% 60% at 80% 70%, #7c3aed 0%, transparent 60%),
    #0a0a14;
  filter: blur(24px);
  transform: scale(1.2);
}
.if-glass-field {
  display: grid; gap: 6px;
  font-size: 11px; color: rgba(255,255,255,0.7);
  text-transform: uppercase; letter-spacing: 0.1em;
}
.if-glass-field input {
  width: 100%; box-sizing: border-box;
  background: rgba(255,255,255,0.1);
  border: 1px solid rgba(255,255,255,0.2);
  backdrop-filter: blur(20px) saturate(160%);
  -webkit-backdrop-filter: blur(20px) saturate(160%);
  border-radius: 10px;
  padding: 10px 12px;
  color: #fff;
  font-size: 14px;
  text-transform: none; letter-spacing: 0;
  outline: none;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.18);
  transition: border-color .2s, background .2s;
}
.if-glass-field input::placeholder { color: rgba(255,255,255,0.45); }
.if-glass-field input:focus {
  border-color: rgba(255,255,255,0.5);
  background: rgba(255,255,255,0.16);
}
<div class="if-glass">
  <div class="if-glass-bg" aria-hidden="true"></div>
  <label class="if-glass-field">
    <span>Search</span>
    <input type="search" name="q" placeholder="Search anything…" />
  </label>
</div>
05 / 28
Notched Outline
Pure CSS
Material-style notched outline. The label sits inside a gap cut into the border. Subtle, professional, and works beautifully for forms with dense fields.
.if-notch {
  position: relative; display: block;
  width: 100%; max-width: 280px;
}
.if-notch input {
  width: 100%; box-sizing: border-box;
  padding: 13px 14px;
  background: transparent;
  border: 0; outline: none;
  color: #f0eeff; font-size: 14px;
  position: relative; z-index: 1;
}
.if-notch input::placeholder { color: transparent; }
.if-notch-label {
  position: absolute; left: 14px; top: 13px;
  font-size: 13px; color: #6b6987;
  pointer-events: none;
  transition: opacity .15s;
  z-index: 2;
}
.if-notch input:focus ~ .if-notch-label,
.if-notch input:not(:placeholder-shown) ~ .if-notch-label {
  opacity: 0;
}
.if-notch-frame {
  position: absolute; inset: 0;
  display: grid;
  grid-template-columns: 12px auto 1fr;
  pointer-events: none;
}
.if-notch-l, .if-notch-cut, .if-notch-r {
  border: 1px solid rgba(46,184,138,0.35);
  transition: border-color .2s;
}
.if-notch-l   { border-right: 0; border-radius: 8px 0 0 8px; }
.if-notch-r   { border-left: 0;  border-radius: 0 8px 8px 0; }
.if-notch-cut {
  border-left: 0; border-right: 0;
  position: relative;
  display: flex; align-items: center; padding: 0 6px;
}
.if-notch-cut > span {
  font-size: 10px; color: rgba(46,184,138,0.85);
  font-weight: 600;
  background: #07070f;
  padding: 0 4px;
  letter-spacing: 0.06em; text-transform: uppercase;
  transform: translateY(-13px) scale(0);
  transform-origin: left center;
  transition: transform .2s ease;
}
.if-notch input:focus ~ .if-notch-frame .if-notch-cut > span,
.if-notch input:not(:placeholder-shown) ~ .if-notch-frame .if-notch-cut > span {
  transform: translateY(-13px) scale(1);
}
.if-notch:focus-within .if-notch-l,
.if-notch:focus-within .if-notch-cut,
.if-notch:focus-within .if-notch-r { border-color: #2eb88a; }
<label class="if-notch">
  <span class="if-notch-label">Full name</span>
  <input type="text" name="name" autocomplete="name" placeholder=" " required />
  <span class="if-notch-frame" aria-hidden="true">
    <span class="if-notch-l"></span>
    <span class="if-notch-cut"><span>Full name</span></span>
    <span class="if-notch-r"></span>
  </span>
</label>
06 / 28
Liquid Border
Pure CSS
Animated stroke that draws around the field on focus. A small flourish that makes a single text input feel deliberate and premium.
.if-liquid {
  display: grid; gap: 6px;
  width: 100%; max-width: 280px;
  font-size: 11px; color: #9d9bbf;
  text-transform: uppercase; letter-spacing: 0.08em;
}
.if-liquid input {
  width: 100%; box-sizing: border-box;
  padding: 11px 14px;
  background: #1a1a22;
  border: 1px solid #2a2a36;
  border-radius: 8px;
  color: #f0eeff;
  font-size: 14px; letter-spacing: 0;
  text-transform: none;
  outline: none;
  background-image:
    linear-gradient(90deg, #fbbf24, #fbbf24),
    linear-gradient(0deg,  #fbbf24, #fbbf24),
    linear-gradient(90deg, #fbbf24, #fbbf24),
    linear-gradient(0deg,  #fbbf24, #fbbf24);
  background-repeat: no-repeat;
  background-size: 0 2px, 2px 0, 0 2px, 2px 0;
  background-position: 0 0, 100% 0, 100% 100%, 0 100%;
  transition: background-size .35s linear, border-color .2s;
}
.if-liquid input::placeholder { color: #6b6987; }
.if-liquid input:focus {
  border-color: transparent;
  background-size: 100% 2px, 2px 100%, 100% 2px, 2px 100%;
  transition-delay: 0s, .35s, .7s, 1.05s;
  transition-duration: .35s;
}
<label class="if-liquid">
  <span class="if-liquid-label">Project name</span>
  <input type="text" name="project" placeholder="My next big idea…" required />
</label>
07 / 28
Inline Validation
Pure CSS
Live `:valid` / `:invalid` feedback. Green tick when correct, red cross when not — driven by `:has()` and pseudo-elements, no JavaScript required.
.if-val {
  display: grid; gap: 6px;
  width: 100%; max-width: 280px;
  font-size: 11px; color: #9d9bbf;
}
.if-val-label { font-weight: 600; letter-spacing: 0.04em; }
.if-val-wrap { position: relative; display: block; }
.if-val input {
  width: 100%; box-sizing: border-box;
  padding: 11px 38px 11px 14px;
  background: #1a1a22;
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 8px;
  color: #f0eeff; font-size: 14px;
  outline: none;
  transition: border-color .2s, background .2s;
}
.if-val input:focus { border-color: #7c6cff; }
.if-val-wrap::after {
  content: ''; position: absolute;
  right: 12px; top: 50%; transform: translateY(-50%) scale(.5);
  width: 18px; height: 18px;
  border-radius: 50%;
  opacity: 0;
  transition: transform .2s, opacity .2s, background .2s;
}
.if-val:has(input:not(:placeholder-shown):invalid) input { border-color: rgba(239,68,68,0.55); }
.if-val:has(input:not(:placeholder-shown):invalid) .if-val-wrap::after {
  opacity: 1; transform: translateY(-50%) scale(1);
  background: #ef4444 url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10' fill='none' stroke='white' stroke-width='1.6' stroke-linecap='round'><path d='M2 2 L8 8 M8 2 L2 8'/></svg>") center/10px no-repeat;
}
.if-val:has(input:not(:placeholder-shown):valid) input { border-color: rgba(34,197,94,0.55); }
.if-val:has(input:not(:placeholder-shown):valid) .if-val-wrap::after {
  opacity: 1; transform: translateY(-50%) scale(1);
  background: #22c55e url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10' fill='none' stroke='white' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'><path d='M2 5 L4.2 7.2 L8 3'/></svg>") center/10px no-repeat;
}
.if-val-help { font-size: 10.5px; color: #6b6987; }
<label class="if-val">
  <span class="if-val-label">Email</span>
  <span class="if-val-wrap">
    <input type="email" name="email" autocomplete="email" placeholder="[email protected]" required />
  </span>
  <span class="if-val-help">We'll never share your address.</span>
</label>
08 / 28
Search with Clear
Pure CSS
Search input with a leading magnifier icon and a trailing clear button. The clear control appears only when the field has content — handled with `:placeholder-shown`.
.if-search {
  position: relative;
  width: 100%; max-width: 280px;
  display: flex; align-items: center;
  background: #1a1a22;
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 999px;
  padding: 0 8px 0 36px;
  transition: border-color .2s, box-shadow .2s;
}
.if-search:focus-within {
  border-color: #60a5fa;
  box-shadow: 0 0 0 3px rgba(96,165,250,0.18);
}
.if-search-icon {
  position: absolute; left: 12px; top: 50%;
  transform: translateY(-50%);
  color: #6b6987;
  display: grid; place-items: center;
}
.if-search:focus-within .if-search-icon { color: #60a5fa; }
.if-search input {
  flex: 1; min-width: 0;
  background: transparent;
  border: 0; outline: none;
  color: #f0eeff;
  font-size: 14px;
  padding: 11px 0;
}
.if-search input::placeholder { color: #6b6987; }
.if-search input::-webkit-search-cancel-button { display: none; }
.if-search-clear {
  display: grid; place-items: center;
  width: 22px; height: 22px;
  background: rgba(255,255,255,0.06);
  border: 0; border-radius: 50%;
  color: #9d9bbf; cursor: pointer;
  transition: opacity .15s, background .15s, color .15s;
}
.if-search:has(input:placeholder-shown) .if-search-clear,
.if-search:has(input[value=""]) .if-search-clear {
  opacity: 0; pointer-events: none;
}
.if-search-clear:hover { background: rgba(255,255,255,0.12); color: #f0eeff; }
<form class="if-search" role="search">
  <span class="if-search-icon" aria-hidden="true">
    <svg viewBox="0 0 16 16" width="14" height="14"><path fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" d="M11 11l3 3M7 12a5 5 0 1 1 0-10 5 5 0 0 1 0 10z"/></svg>
  </span>
  <input type="search" name="q" placeholder="Search projects, people, files…" aria-label="Search" />
  <button type="reset" class="if-search-clear" aria-label="Clear search">
    <svg viewBox="0 0 12 12" width="10" height="10" aria-hidden="true"><path fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" d="M2 2l8 8M10 2l-8 8"/></svg>
  </button>
</form>
09 / 28
Password Strength
Pure CSS
Sign-up password input with a live strength bar that grades red → amber → green. Driven by `:has(input:valid/:invalid)` — zero scoring JavaScript.
.if-pw {
  display: grid; gap: 6px;
  width: 100%; max-width: 280px;
  font-size: 11px; color: #9d9bbf;
}
.if-pw-label { font-weight: 600; }
.if-pw input {
  width: 100%; box-sizing: border-box;
  padding: 11px 14px;
  background: #1a1a22;
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 8px;
  color: #f0eeff; font-size: 14px;
  outline: none;
  transition: border-color .2s;
}
.if-pw input:focus { border-color: #c084fc; }
.if-pw-meter {
  position: relative;
  height: 4px; border-radius: 99px;
  background: rgba(255,255,255,0.08);
  overflow: hidden;
}
.if-pw-fill {
  position: absolute; inset: 0;
  width: 0;
  background: linear-gradient(90deg, #ef4444, #f59e0b, #22c55e);
  border-radius: inherit;
  transition: width .3s ease;
}
.if-pw:has(input:placeholder-shown) .if-pw-fill { width: 0; }
.if-pw:has(input:not(:placeholder-shown):invalid) .if-pw-fill { width: 30%; }
.if-pw:has(input:not(:placeholder-shown):valid)   .if-pw-fill { width: 65%; }
.if-pw:has(input:focus:valid)                     .if-pw-fill { width: 100%; }
.if-pw-hint { font-size: 10.5px; color: #6b6987; }
<label class="if-pw">
  <span class="if-pw-label">Password</span>
  <input type="password" name="password" autocomplete="new-password" placeholder="At least 12 characters" minlength="6" required />
  <span class="if-pw-meter" aria-hidden="true"><span class="if-pw-fill"></span></span>
  <span class="if-pw-hint">Use 12+ characters with letters, numbers, and a symbol.</span>
</label>
10 / 28
OTP Code
Light JS
Verification code
One-time-passcode entry split across six boxes inside a single `<fieldset>`. Auto-advance, backspace step-back, and full paste-to-fill — `autocomplete="one-time-code"` drives SMS auto-suggest.
.if-otp {
  border: 0; padding: 0; margin: 0;
  display: grid; grid-template-columns: repeat(6, 1fr); gap: 8px;
  width: 100%; max-width: 280px;
}
.if-otp-legend {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.if-otp input {
  width: 100%; box-sizing: border-box;
  aspect-ratio: 1 / 1;
  background: #1a1a22;
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 10px;
  font-size: 18px; font-weight: 700;
  text-align: center;
  color: #f0eeff;
  outline: none;
  caret-color: #7c6cff;
  transition: border-color .15s, background .15s, transform .12s;
}
.if-otp input:focus {
  border-color: #7c6cff;
  background: rgba(124,108,255,0.1);
  transform: translateY(-1px);
}
<fieldset class="if-otp">
  <legend class="if-otp-legend">Verification code</legend>
  <input type="text" inputmode="numeric" maxlength="1" autocomplete="one-time-code" pattern="[0-9]" aria-label="Digit 1" data-if-otp />
  <input type="text" inputmode="numeric" maxlength="1" pattern="[0-9]" aria-label="Digit 2" data-if-otp />
  <input type="text" inputmode="numeric" maxlength="1" pattern="[0-9]" aria-label="Digit 3" data-if-otp />
  <input type="text" inputmode="numeric" maxlength="1" pattern="[0-9]" aria-label="Digit 4" data-if-otp />
  <input type="text" inputmode="numeric" maxlength="1" pattern="[0-9]" aria-label="Digit 5" data-if-otp />
  <input type="text" inputmode="numeric" maxlength="1" pattern="[0-9]" aria-label="Digit 6" data-if-otp />
</fieldset>
// OTP — auto-advance, backspace step-back, full paste-to-fill
document.querySelectorAll('.if-otp').forEach(function (row) {
  var inputs = Array.prototype.slice.call(row.querySelectorAll('[data-if-otp]'));
  inputs.forEach(function (input, i) {
    input.addEventListener('input', function () {
      input.value = (input.value || '').replace(/\D/g, '').slice(0, 1);
      if (input.value && inputs[i + 1]) inputs[i + 1].focus();
    });
    input.addEventListener('keydown', function (e) {
      if (e.key === 'Backspace' && !input.value && inputs[i - 1]) inputs[i - 1].focus();
    });
    input.addEventListener('paste', function (e) {
      var data = (e.clipboardData || window.clipboardData).getData('text');
      var digits = (data || '').replace(/\D/g, '').slice(0, inputs.length);
      if (!digits) return;
      e.preventDefault();
      for (var j = 0; j < digits.length; j++) {
        if (inputs[i + j]) inputs[i + j].value = digits[j];
      }
      inputs[Math.min(i + digits.length, inputs.length - 1)].focus();
    });
  });
});
11 / 28
Rotating Placeholder
Light JS
The native placeholder attribute itself rotates through four sample queries every 2.5 seconds — "Search products…" → "Search docs…" → "Search people…" → "Search settings…". Light JS swaps the attribute; the browser does the rest. Stops the moment the user focuses or starts typing.
.if-anim {
  display: grid; gap: 6px;
  width: 100%; max-width: 280px;
}
.if-anim-cap {
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px; letter-spacing: 0.12em;
  color: #9d9bbf; text-transform: uppercase;
}
.if-anim input {
  width: 100%; box-sizing: border-box;
  padding: 12px 14px;
  background: #1a1a22;
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 10px;
  color: #f0eeff;
  font: 500 14px/1 system-ui, sans-serif;
  outline: none;
  transition: border-color .25s, background .2s;
}
.if-anim input:focus { border-color: #c084fc; background: #1f1f2a; }
.if-anim input::placeholder {
  color: #9d9bbf;
  transition: opacity .25s ease;
}
.if-anim input.is-fading::placeholder { opacity: 0; }
<label class="if-anim">
  <span class="if-anim-cap">Search</span>
  <input type="search" name="q" inputmode="search" placeholder="Search products…" aria-label="Search" data-if-rotate='["Search products…","Search docs…","Search people…","Search settings…"]' />
</label>
// Rotating Placeholder — swap the input's placeholder attribute on a 2.5s loop
document.querySelectorAll('[data-if-rotate]').forEach(function (input) {
  var phrases;
  try { phrases = JSON.parse(input.getAttribute('data-if-rotate') || '[]'); }
  catch (e) { phrases = []; }
  if (!phrases.length) return;
  var i = 0, paused = false;
  function tick() {
    if (paused || document.activeElement === input || input.value) return;
    input.classList.add('is-fading');
    setTimeout(function () {
      i = (i + 1) % phrases.length;
      input.setAttribute('placeholder', phrases[i]);
      input.classList.remove('is-fading');
    }, 250);
  }
  setInterval(tick, 2500);
  input.addEventListener('focus', function () { paused = true;  });
  input.addEventListener('blur',  function () { paused = !!input.value; });
});
12 / 28
Tag Input
Light JS
Type a tag and press Enter or comma to commit it as a chip. Backspace on an empty input removes the last chip — a polished pattern for filters and email recipients.
.if-tag {
  display: grid; gap: 6px;
  width: 100%; max-width: 280px;
  font-size: 11px; color: #9d9bbf;
}
.if-tag-label { font-weight: 600; }
.if-tag-wrap {
  display: flex; flex-wrap: wrap; gap: 6px;
  background: #1a1a22;
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 8px;
  padding: 8px 10px;
  align-items: center;
  transition: border-color .2s, box-shadow .2s;
}
.if-tag-wrap:focus-within {
  border-color: #fb923c;
  box-shadow: 0 0 0 3px rgba(251,146,60,0.15);
}
.if-tag-list {
  display: contents;
}
.if-tag-chip {
  display: inline-flex; align-items: center; gap: 5px;
  background: rgba(251,146,60,0.12);
  border: 1px solid rgba(251,146,60,0.35);
  color: #fdba74;
  font-size: 12px;
  padding: 2px 8px;
  border-radius: 99px;
  font-weight: 500;
}
.if-tag-chip button {
  background: transparent; border: 0; padding: 0;
  color: inherit; cursor: pointer;
  font-size: 12px; line-height: 1;
  opacity: 0.7;
}
.if-tag-chip button:hover { opacity: 1; }
.if-tag input {
  flex: 1; min-width: 80px;
  background: transparent;
  border: 0; outline: none;
  color: #f0eeff;
  font-size: 14px;
  padding: 2px 0;
}
.if-tag input::placeholder { color: #6b6987; }
.if-tag-help { font-size: 10.5px; color: #6b6987; }
.if-tag-help kbd {
  display: inline-block;
  background: #2a2a36;
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 4px;
  padding: 0 4px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px;
}
<label class="if-tag">
  <span class="if-tag-label">Tags</span>
  <span class="if-tag-wrap" data-if-tags>
    <span class="if-tag-list"></span>
    <input type="text" placeholder="Add a tag…" aria-label="Tags" />
  </span>
  <span class="if-tag-help">Press <kbd>Enter</kbd> or <kbd>,</kbd> to add</span>
</label>
// Tag input — Enter/comma commits a chip, Backspace removes the last
document.querySelectorAll('[data-if-tags]').forEach(function (wrap) {
  var input = wrap.querySelector('input');
  var list  = wrap.querySelector('.if-tag-list');
  if (!input || !list) return;

  function addChip(value) {
    value = (value || '').trim();
    if (!value) return;
    var chip = document.createElement('span');
    chip.className = 'if-tag-chip';
    chip.textContent = value + ' ';
    var x = document.createElement('button');
    x.type = 'button';
    x.setAttribute('aria-label', 'Remove tag ' + value);
    x.textContent = '×';
    x.addEventListener('click', function () { chip.remove(); });
    chip.appendChild(x);
    list.appendChild(chip);
  }

  input.addEventListener('keydown', function (e) {
    if (e.key === 'Enter' || e.key === ',') {
      e.preventDefault();
      addChip(input.value);
      input.value = '';
    } else if (e.key === 'Backspace' && !input.value) {
      var last = list.querySelector('.if-tag-chip:last-child');
      if (last) last.remove();
    }
  });
});
13 / 28
Phone with Country
Pure CSS
Phone number input with a native `<select>` for the country dialling code. Semantic, accessible by default, and styled to match the input seamlessly.
.if-phone {
  display: grid; gap: 6px;
  width: 100%; max-width: 280px;
  font-size: 11px; color: #9d9bbf;
}
.if-phone-label { font-weight: 600; }
.if-phone-wrap {
  display: grid; grid-template-columns: auto 1fr;
  background: #1a1a22;
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 8px;
  overflow: hidden;
  transition: border-color .2s, box-shadow .2s;
}
.if-phone-wrap:focus-within {
  border-color: #ef4444;
  box-shadow: 0 0 0 3px rgba(239,68,68,0.15);
}
.if-phone select {
  background: rgba(255,255,255,0.04);
  border: 0; border-right: 1px solid rgba(255,255,255,0.1);
  outline: none;
  color: #f0eeff;
  font-size: 13px;
  padding: 10px 28px 10px 12px;
  cursor: pointer;
  appearance: none; -webkit-appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6' fill='none' stroke='%239d9bbf' stroke-width='1.5' stroke-linecap='round'><path d='M1 1l4 4 4-4'/></svg>");
  background-repeat: no-repeat;
  background-position: right 10px center;
}
.if-phone select option { background: #1a1a22; color: #f0eeff; }
.if-phone input {
  background: transparent;
  border: 0; outline: none;
  color: #f0eeff; font-size: 14px;
  padding: 10px 12px;
  min-width: 0;
}
.if-phone input::placeholder { color: #6b6987; }
<label class="if-phone">
  <span class="if-phone-label">Phone</span>
  <span class="if-phone-wrap">
    <select aria-label="Country code" name="country">
      <option value="+44">🇬🇧 +44</option>
      <option value="+1" selected>🇺🇸 +1</option>
      <option value="+91">🇮🇳 +91</option>
      <option value="+33">🇫🇷 +33</option>
      <option value="+49">🇩🇪 +49</option>
      <option value="+81">🇯🇵 +81</option>
    </select>
    <input type="tel" name="phone" autocomplete="tel" placeholder="(555) 123-4567" required />
  </span>
</label>
14 / 28
Stepper Number
Light JS
Quantity input with custom −/+ controls. Native `<input type="number">` underneath keeps keyboard arrows, validation, and screen-reader semantics intact.
.if-step {
  display: grid; gap: 6px;
  width: 100%; max-width: 220px;
  font-size: 11px; color: #9d9bbf;
}
.if-step-label { font-weight: 600; }
.if-step-wrap {
  display: grid; grid-template-columns: 36px 1fr 36px;
  background: #1a1a22;
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 8px;
  overflow: hidden;
  transition: border-color .2s;
}
.if-step-wrap:focus-within { border-color: #14b8a6; }
.if-step-btn {
  background: rgba(20,184,166,0.06);
  border: 0; color: #5eead4;
  font-size: 18px; font-weight: 700;
  cursor: pointer;
  transition: background .15s, color .15s;
}
.if-step-btn:hover { background: rgba(20,184,166,0.18); color: #fff; }
.if-step-btn:active { background: rgba(20,184,166,0.28); }
.if-step input {
  background: transparent;
  border: 0; outline: none;
  color: #f0eeff;
  font-size: 16px; font-weight: 600;
  text-align: center;
  padding: 10px 0;
  -moz-appearance: textfield;
}
.if-step input::-webkit-outer-spin-button,
.if-step input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
<label class="if-step">
  <span class="if-step-label">Quantity</span>
  <span class="if-step-wrap">
    <button type="button" class="if-step-btn" data-if-step="-1" aria-label="Decrease">−</button>
    <input type="number" name="qty" min="0" max="99" value="1" aria-label="Quantity" />
    <button type="button" class="if-step-btn" data-if-step="1" aria-label="Increase">+</button>
  </span>
</label>
// Stepper +/- buttons — clamp to min/max
document.querySelectorAll('.if-step-wrap').forEach(function (wrap) {
  var input = wrap.querySelector('input[type="number"]');
  if (!input) return;
  wrap.querySelectorAll('[data-if-step]').forEach(function (btn) {
    btn.addEventListener('click', function () {
      var dir = parseInt(btn.dataset.ifStep, 10) || 0;
      var min = input.min !== '' ? Number(input.min) : -Infinity;
      var max = input.max !== '' ? Number(input.max) : Infinity;
      var val = (Number(input.value) || 0) + dir;
      if (val < min) val = min;
      if (val > max) val = max;
      input.value = String(val);
    });
  });
});
15 / 28
File Drop Zone
Pure CSS
Drag-and-drop file input with a custom dashed boundary. Native `<input type="file">` underneath, the visible filename appearing via `:has()` — accessible, keyboard-friendly, and minimal.
.if-file {
  position: relative; display: block;
  width: 100%; max-width: 320px;
  cursor: pointer;
  border: 1.5px dashed rgba(99,102,241,0.5);
  border-radius: 12px;
  padding: 22px 18px;
  background: rgba(99,102,241,0.05);
  text-align: center;
  transition: border-color .2s, background .2s, transform .15s;
}
.if-file:hover {
  border-color: #818cf8;
  background: rgba(99,102,241,0.1);
}
.if-file:focus-within {
  border-color: #818cf8;
  background: rgba(99,102,241,0.12);
  box-shadow: 0 0 0 3px rgba(129,140,248,0.18);
}
.if-file input[type="file"] {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  opacity: 0; cursor: pointer;
}
.if-file-icon {
  display: grid; place-items: center;
  width: 40px; height: 40px;
  margin: 0 auto 8px;
  border-radius: 10px;
  background: rgba(99,102,241,0.18);
  color: #a5b4fc;
}
.if-file-text {
  display: block;
  font-size: 13px; color: #c8c0ff;
}
.if-file-text strong {
  color: #a5b4fc; font-weight: 600;
}
.if-file-types {
  display: block;
  font-size: 11px; color: #6b6987;
  margin-top: 4px;
}
.if-file:has(input[type="file"]:not([value=""])) {
  border-style: solid;
  border-color: #22c55e;
  background: rgba(34,197,94,0.08);
}
<label class="if-file">
  <input type="file" name="upload" accept="image/*,.pdf" />
  <span class="if-file-icon" aria-hidden="true">
    <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
      <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
      <polyline points="17 8 12 3 7 8"/>
      <line x1="12" y1="3" x2="12" y2="15"/>
    </svg>
  </span>
  <span class="if-file-text">
    <strong>Click to upload</strong> or drag &amp; drop
    <span class="if-file-types">PNG, JPG, or PDF · max 10 MB</span>
  </span>
</label>
16 / 28
Currency Input
Pure CSS
Right-aligned numeric input with an inline `$` prefix. `inputmode="decimal"` brings up the right mobile keyboard; `:focus-within` lights the prefix to match the field accent.
.if-curr { display: grid; gap: 6px; width: 100%; max-width: 280px; }
.if-curr-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px; letter-spacing: 0.12em;
  color: #9d9bbf; text-transform: uppercase;
}
.if-curr-wrap {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 0 14px;
  background: #1a1a22;
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 10px;
  transition: border-color .2s, background .2s;
}
.if-curr-wrap:focus-within { border-color: #f5a84a; background: #1f1f2a; }
.if-curr-prefix {
  color: #6b6987; font: 600 14px/1 system-ui, sans-serif;
  transition: color .2s;
}
.if-curr-wrap:focus-within .if-curr-prefix { color: #f5a84a; }
.if-curr input {
  flex: 1; min-width: 0;
  padding: 12px 0;
  border: 0; outline: none; background: transparent;
  color: #f0eeff; font: 600 14px/1 system-ui, sans-serif;
  text-align: right;
  -moz-appearance: textfield;
}
.if-curr input::-webkit-outer-spin-button,
.if-curr input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.if-curr input::placeholder { color: #6b6987; }
.if-curr-suffix {
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px; color: #6b6987;
  letter-spacing: 0.06em;
}
<label class="if-curr">
  <span class="if-curr-label">Amount</span>
  <span class="if-curr-wrap">
    <span class="if-curr-prefix">$</span>
    <input type="text" name="amount" inputmode="decimal" pattern="[0-9.,]*" placeholder="0.00" />
    <span class="if-curr-suffix">USD</span>
  </span>
</label>
17 / 28
Date Picker Native
Pure CSS
A styled `<input type="date">` keeps every native picker behaviour (keyboard, accessibility, browser/OS calendars) while the chrome matches the rest of your form. `::-webkit-calendar-picker-indicator` is recoloured to match the accent.
.if-date { display: grid; gap: 6px; width: 100%; max-width: 280px; }
.if-date-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px; letter-spacing: 0.12em;
  color: #9d9bbf; text-transform: uppercase;
}
.if-date input {
  width: 100%; box-sizing: border-box;
  padding: 12px 14px;
  background: #1a1a22;
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 10px;
  color: #f0eeff; font: 500 14px/1 system-ui, sans-serif;
  outline: none;
  transition: border-color .2s, background .2s;
  color-scheme: dark;
}
.if-date input:focus { border-color: #06b6d4; background: #1f1f2a; }
.if-date input::-webkit-calendar-picker-indicator {
  filter: invert(0.7) sepia(1) saturate(5) hue-rotate(150deg);
  cursor: pointer;
  opacity: 0.8;
}
.if-date input::-webkit-calendar-picker-indicator:hover { opacity: 1; }
<label class="if-date">
  <span class="if-date-label">Start date</span>
  <input type="date" name="start" />
</label>
18 / 28
Time Range
Pure CSS
Quiet hours
Two `<input type="time">` paired with a typographic `→` divider. Single visual unit while remaining two independent fields for keyboard nav and form submission.
.if-time {
  width: 100%; max-width: 320px;
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 12px;
  padding: 12px 14px;
  background: #1a1a22;
}
.if-time-legend {
  padding: 0 6px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px; letter-spacing: 0.12em;
  color: #9d9bbf; text-transform: uppercase;
}
.if-time-row {
  display: flex; align-items: center; gap: 12px;
}
.if-time label { flex: 1; display: grid; gap: 4px; }
.if-time-cap {
  font-size: 10px; color: #6b6987;
  letter-spacing: 0.08em; text-transform: uppercase;
}
.if-time input {
  width: 100%; box-sizing: border-box;
  padding: 8px 10px;
  background: #0c0c12;
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 8px;
  color: #f0eeff; font: 600 13px/1 'JetBrains Mono', monospace;
  outline: none;
  color-scheme: dark;
}
.if-time input:focus { border-color: #a78bfa; }
.if-time-sep {
  color: #a78bfa; font-size: 14px; align-self: end; padding-bottom: 9px;
}
<fieldset class="if-time">
  <legend class="if-time-legend">Quiet hours</legend>
  <span class="if-time-row">
    <label><span class="if-time-cap">From</span><input type="time" name="from" value="22:00" /></label>
    <span class="if-time-sep" aria-hidden="true">→</span>
    <label><span class="if-time-cap">To</span><input type="time" name="to" value="07:00" /></label>
  </span>
</fieldset>
19 / 28
Color Picker
Pure CSS
A `<input type="color">` swatch chip paired with a hex text field — both accept the same `name` and submit value via the underlying form. Sibling-`+` selectors mirror the swatch state.
.if-color { display: grid; gap: 6px; width: 100%; max-width: 280px; }
.if-color-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px; letter-spacing: 0.12em;
  color: #9d9bbf; text-transform: uppercase;
}
.if-color-wrap {
  display: inline-flex; align-items: stretch; gap: 0;
  background: #1a1a22;
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 10px;
  overflow: hidden;
  transition: border-color .2s;
}
.if-color-wrap:focus-within { border-color: #7c6cff; }
.if-color input[type="color"] {
  width: 44px; height: 44px;
  padding: 6px;
  border: 0; outline: none; cursor: pointer;
  background: transparent;
  border-right: 1px solid rgba(255,255,255,0.1);
}
.if-color input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.if-color input[type="color"]::-webkit-color-swatch { border: 0; border-radius: 6px; }
.if-color input[type="text"] {
  flex: 1; min-width: 0;
  padding: 0 14px;
  border: 0; outline: none; background: transparent;
  color: #f0eeff; font: 600 13px/1 'JetBrains Mono', monospace;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
<label class="if-color">
  <span class="if-color-label">Brand colour</span>
  <span class="if-color-wrap">
    <input type="color" id="if-color-c" value="#7c6cff" aria-label="Pick colour" />
    <input type="text" id="if-color-t" value="#7c6cff" aria-label="Hex value" pattern="^#[0-9A-Fa-f]{6}$" />
  </span>
</label>
20 / 28
Search with Voice
Light JS
Search field with a microphone trigger that toggles a recording state — pulses a halo while "listening". Real `<input type="search">` so AT announces it as a search field.
.if-voice { display: grid; gap: 6px; width: 100%; max-width: 320px; }
.if-voice-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px; letter-spacing: 0.12em;
  color: #9d9bbf; text-transform: uppercase;
}
.if-voice-wrap {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 4px 4px 4px 14px;
  background: #1a1a22;
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 10px;
  transition: border-color .2s;
}
.if-voice-wrap:focus-within { border-color: #ff6c8a; }
.if-voice-glass { width: 16px; height: 16px; flex-shrink: 0; fill: none; stroke: #6b6987; stroke-width: 2; stroke-linecap: round; }
.if-voice input {
  flex: 1; min-width: 0; padding: 8px 0;
  border: 0; outline: none; background: transparent;
  color: #f0eeff; font: 500 14px/1 system-ui, sans-serif;
}
.if-voice input::placeholder { color: #6b6987; }
.if-voice-mic {
  width: 36px; height: 36px; flex-shrink: 0;
  border: 0; cursor: pointer; background: transparent;
  border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  position: relative;
  transition: background .2s;
}
.if-voice-mic:hover { background: rgba(255,108,138,0.1); }
.if-voice-mic svg { width: 16px; height: 16px; fill: none; stroke: #a78bfa; stroke-width: 2; stroke-linecap: round; }
.if-voice-mic[aria-pressed="true"] svg { stroke: #ff6c8a; }
.if-voice-mic[aria-pressed="true"]::after {
  content: ''; position: absolute; inset: 4px;
  border-radius: 6px; border: 1.5px solid #ff6c8a;
  animation: if-voice-pulse 1.2s ease-out infinite;
}
@keyframes if-voice-pulse {
  0%   { transform: scale(0.8); opacity: 1; }
  100% { transform: scale(1.4); opacity: 0; }
}
<form class="if-voice" role="search" autocomplete="off">
  <label class="if-voice-label" for="if-voice-input">Voice search</label>
  <span class="if-voice-wrap">
    <svg viewBox="0 0 24 24" aria-hidden="true" class="if-voice-glass"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
    <input id="if-voice-input" type="search" name="q" inputmode="search" placeholder="Speak or type to search..." />
    <button type="button" class="if-voice-mic" aria-label="Start voice input" aria-pressed="false" data-if-voice>
      <svg viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="3" width="6" height="11" rx="3"/><path d="M5 11a7 7 0 0 0 14 0M12 18v3"/></svg>
    </button>
  </span>
</form>
// Voice button — toggle aria-pressed (mock recording state for the demo)
document.querySelectorAll('[data-if-voice]').forEach(function (btn) {
  btn.addEventListener('click', function () {
    var on = btn.getAttribute('aria-pressed') === 'true';
    btn.setAttribute('aria-pressed', on ? 'false' : 'true');
    btn.setAttribute('aria-label', on ? 'Start voice input' : 'Stop voice input');
    // Hook real Web Speech API here if you want actual voice input:
    // const rec = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
  });
});
21 / 28
Email with Suggestions
Light JS
When the user types `@`, common email domains are suggested in a keyboard-navigable dropdown. `aria-controls` + `aria-expanded` + `role="listbox"` make it a proper combobox.
.if-mail { display: grid; gap: 6px; width: 100%; max-width: 280px; }
.if-mail-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px; letter-spacing: 0.12em;
  color: #9d9bbf; text-transform: uppercase;
}
.if-mail-wrap { position: relative; display: block; }
.if-mail input {
  width: 100%; box-sizing: border-box;
  padding: 12px 14px;
  background: #1a1a22;
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 10px;
  color: #f0eeff; font: 500 14px/1 system-ui, sans-serif;
  outline: none;
  transition: border-color .2s, border-radius .15s;
}
.if-mail input:focus { border-color: #2eb88a; }
.if-mail input[aria-expanded="true"] {
  border-radius: 10px 10px 0 0;
  border-bottom-color: transparent;
}
.if-mail-list {
  position: absolute; top: 100%; left: 0; right: 0;
  margin: 0; padding: 4px;
  list-style: none;
  background: #1a1a22;
  border: 1px solid #2eb88a; border-top: 0;
  border-radius: 0 0 10px 10px;
  z-index: 10;
  max-height: 180px; overflow-y: auto;
}
.if-mail-list li {
  padding: 7px 10px;
  font: 500 13px/1.2 system-ui, sans-serif;
  color: #cbd5e1; cursor: pointer;
  border-radius: 6px;
}
.if-mail-list li[aria-selected="true"], .if-mail-list li:hover {
  background: rgba(46,184,138,0.15); color: #fff;
}
.if-mail-list li b { color: #2eb88a; font-weight: 600; }
<label class="if-mail">
  <span class="if-mail-label">Work email</span>
  <span class="if-mail-wrap">
    <input id="if-mail-input" type="email" name="email" autocomplete="email" placeholder="[email protected]" aria-controls="if-mail-list" aria-expanded="false" aria-autocomplete="list" />
    <ul id="if-mail-list" class="if-mail-list" role="listbox" hidden></ul>
  </span>
</label>
// Email Suggestions — show common domains after the user types '@'
(function () {
  var input = document.getElementById('if-mail-input');
  var list  = document.getElementById('if-mail-list');
  if (!input || !list) return;
  var DOMAINS = ['gmail.com', 'outlook.com', 'icloud.com', 'protonmail.com', 'fastmail.com'];
  var idx = -1;

  function close() { list.hidden = true; input.setAttribute('aria-expanded', 'false'); idx = -1; }
  function render(local) {
    list.innerHTML = '';
    DOMAINS.forEach(function (d) {
      var li = document.createElement('li');
      li.setAttribute('role', 'option');
      li.innerHTML = (local || '') + '<b>@' + d + '</b>';
      list.appendChild(li);
    });
  }
  function open(local) { render(local); list.hidden = false; input.setAttribute('aria-expanded', 'true'); }
  function select(n) {
    var items = list.querySelectorAll('li');
    items.forEach(function (li) { li.removeAttribute('aria-selected'); });
    idx = (n + items.length) % items.length;
    items[idx].setAttribute('aria-selected', 'true');
  }

  input.addEventListener('input', function () {
    var v = input.value, at = v.indexOf('@');
    if (at >= 0) open(v.substring(0, at + 1)); else close();
  });
  input.addEventListener('blur',    function () { setTimeout(close, 150); });
  input.addEventListener('keydown', function (e) {
    if (list.hidden) return;
    var items = list.querySelectorAll('li');
    if (e.key === 'ArrowDown')      { e.preventDefault(); select(idx + 1); }
    else if (e.key === 'ArrowUp')   { e.preventDefault(); select(idx - 1); }
    else if (e.key === 'Escape')    { close(); }
    else if (e.key === 'Enter' && idx >= 0) {
      e.preventDefault();
      input.value = items[idx].textContent;
      close();
    }
  });
  list.addEventListener('mousedown', function (e) {
    var li = e.target.closest('li[role="option"]');
    if (li) { input.value = li.textContent; close(); }
  });
})();
22 / 28
Auto-Grow Textarea
Pure CSS
A multi-line `<textarea>` that grows with its content using `field-sizing: content` — no JS observer needed. Falls back to a min-height on browsers without support.
.if-grow { display: grid; gap: 6px; width: 100%; max-width: 320px; }
.if-grow-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px; letter-spacing: 0.12em;
  color: #9d9bbf; text-transform: uppercase;
}
.if-grow textarea {
  width: 100%; box-sizing: border-box;
  padding: 12px 14px;
  background: #1a1a22;
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 10px;
  color: #f0eeff; font: 500 14px/1.55 system-ui, sans-serif;
  outline: none; resize: none;
  min-height: 64px;
  field-sizing: content;
  transition: border-color .2s, background .2s;
}
.if-grow textarea:focus { border-color: #a78bfa; background: #1f1f2a; }
.if-grow textarea::placeholder { color: #6b6987; }
.if-grow-hint {
  font-size: 11px; color: #6b6987;
}
<label class="if-grow">
  <span class="if-grow-label">Notes</span>
  <textarea name="notes" rows="2" placeholder="Type as much as you need..."></textarea>
  <span class="if-grow-hint">Field grows as you type</span>
</label>
23 / 28
Inline Edit
Light JS
Click-to-edit text that swaps between a static value and an editable input. Saves on Enter, cancels on Escape, blurs to commit — the canonical pattern from Notion and Linear.
.if-edit { display: grid; gap: 6px; width: 100%; max-width: 280px; }
.if-edit-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px; letter-spacing: 0.12em;
  color: #9d9bbf; text-transform: uppercase;
}
.if-edit-wrap {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 12px;
  background: transparent;
  border: 1px dashed transparent;
  border-radius: 8px;
  cursor: text;
  transition: background .15s, border-color .15s;
}
.if-edit-wrap:hover { background: rgba(124,108,255,0.06); border-color: rgba(124,108,255,0.2); }
.if-edit-display, .if-edit-input {
  font: 600 14px/1.3 system-ui, sans-serif;
  color: #f0eeff;
  flex: 1; min-width: 0;
}
.if-edit-display { outline: none; }
.if-edit-input {
  border: 0; outline: none; background: transparent;
  padding: 0;
}
.if-edit-pencil {
  font-size: 12px; color: #7a7899;
  opacity: 0; transition: opacity .15s;
}
.if-edit-wrap:hover .if-edit-pencil { opacity: 1; }
.if-edit-wrap.is-editing { background: #1a1a22; border-color: #7c6cff; border-style: solid; }
.if-edit-wrap.is-editing .if-edit-pencil { display: none; }
<label class="if-edit">
  <span class="if-edit-label">Project name</span>
  <span class="if-edit-wrap" data-if-edit data-value="Aurora Dashboard">
    <span class="if-edit-display" tabindex="0" role="button" aria-label="Edit project name">Aurora Dashboard</span>
    <input class="if-edit-input" type="text" value="Aurora Dashboard" hidden />
    <span class="if-edit-pencil" aria-hidden="true">✎</span>
  </span>
</label>
// Inline Edit — click to edit, Enter saves, Escape cancels, blur commits
document.querySelectorAll('[data-if-edit]').forEach(function (wrap) {
  var display = wrap.querySelector('.if-edit-display');
  var input   = wrap.querySelector('.if-edit-input');

  function enter() {
    wrap.classList.add('is-editing');
    display.hidden = true; input.hidden = false;
    input.value = display.textContent;
    input.focus(); input.select();
  }
  function commit() {
    var v = input.value.trim();
    if (v) { display.textContent = v; wrap.dataset.value = v; }
    else   { input.value = display.textContent; }
    exit();
  }
  function cancel() { input.value = display.textContent; exit(); }
  function exit() {
    wrap.classList.remove('is-editing');
    input.hidden = true; display.hidden = false;
  }

  display.addEventListener('click', enter);
  display.addEventListener('keydown', function (e) {
    if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); enter(); }
  });
  input.addEventListener('blur', commit);
  input.addEventListener('keydown', function (e) {
    if (e.key === 'Enter')  { e.preventDefault(); commit(); }
    if (e.key === 'Escape') { e.preventDefault(); cancel(); }
  });
});
24 / 28
Range Slider Pro
Light JS
A native `<input type="range">` with custom track/thumb styling and a live value bubble that follows the thumb. Honest accessibility — keyboard arrows, screen-reader announcement, real form value.
.if-range { display: grid; gap: 8px; width: 100%; max-width: 280px; }
.if-range-label {
  display: flex; justify-content: space-between; align-items: center;
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px; letter-spacing: 0.12em;
  color: #9d9bbf; text-transform: uppercase;
}
.if-range-bubble {
  font: 700 11px/1 'JetBrains Mono', monospace;
  color: #f0eeff;
  background: #7c6cff;
  padding: 3px 7px; border-radius: 999px;
  letter-spacing: 0;
}
.if-range input {
  width: 100%; height: 6px;
  -webkit-appearance: none; appearance: none;
  background: linear-gradient(90deg, #7c6cff 0%, #7c6cff var(--if-range-fill, 50%), rgba(255,255,255,0.08) var(--if-range-fill, 50%), rgba(255,255,255,0.08) 100%);
  border-radius: 999px; outline: none; cursor: pointer;
}
.if-range input::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 18px; height: 18px;
  background: #fff;
  border: 0;
  border-radius: 50%;
  box-shadow: 0 2px 6px rgba(124,108,255,0.5);
  cursor: pointer;
  transition: transform .15s;
}
.if-range input::-webkit-slider-thumb:hover { transform: scale(1.12); }
.if-range input::-moz-range-thumb {
  width: 18px; height: 18px;
  background: #fff; border: 0; border-radius: 50%;
  box-shadow: 0 2px 6px rgba(124,108,255,0.5);
  cursor: pointer;
}
<label class="if-range" for="if-range-input">
  <span class="if-range-label">Volume <span class="if-range-bubble" data-if-bubble>50</span></span>
  <input id="if-range-input" type="range" name="volume" min="0" max="100" value="50" data-if-range />
</label>
// Range Slider Pro — sync the live value bubble + paint the gradient fill via custom property
document.querySelectorAll('[data-if-range]').forEach(function (input) {
  var bubble = input.closest('.if-range').querySelector('[data-if-bubble]');
  function update() {
    var min = Number(input.min) || 0;
    var max = Number(input.max) || 100;
    var pct = ((Number(input.value) - min) / (max - min)) * 100;
    input.style.setProperty('--if-range-fill', pct + '%');
    if (bubble) bubble.textContent = String(input.value);
  }
  input.addEventListener('input', update);
  update();
});
25 / 28
URL with Protocol
Pure CSS
URL input with a non-editable `https://` prefix that visually integrates with the field. Submit value is the full URL; users only type the host portion.
.if-url { display: grid; gap: 6px; width: 100%; max-width: 320px; }
.if-url-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px; letter-spacing: 0.12em;
  color: #9d9bbf; text-transform: uppercase;
}
.if-url-wrap {
  display: inline-flex; align-items: stretch;
  background: #1a1a22;
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 10px;
  overflow: hidden;
  transition: border-color .2s;
}
.if-url-wrap:focus-within { border-color: #06b6d4; }
.if-url-prefix {
  display: inline-flex; align-items: center;
  padding: 0 12px;
  background: rgba(6,182,212,0.08);
  color: #06b6d4;
  font: 600 12px/1 'JetBrains Mono', monospace;
  border-right: 1px solid rgba(255,255,255,0.08);
}
.if-url input {
  flex: 1; min-width: 0;
  padding: 12px 14px;
  border: 0; outline: none; background: transparent;
  color: #f0eeff; font: 500 14px/1 system-ui, sans-serif;
}
.if-url input::placeholder { color: #6b6987; }
.if-url input:invalid:not(:placeholder-shown) ~ * { color: #ff6c8a; }
<label class="if-url">
  <span class="if-url-label">Website</span>
  <span class="if-url-wrap">
    <span class="if-url-prefix">https://</span>
    <input type="text" name="url-host" inputmode="url" placeholder="codefronts.com" pattern="[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}.*" />
  </span>
</label>
26 / 28
Credit Card
Light JS
Auto-formatted 16-digit card number with brand-aware accent (Visa, Mastercard, Amex). `inputmode="numeric"` on mobile; pattern validation; `autocomplete="cc-number"`.
.if-card { display: grid; gap: 6px; width: 100%; max-width: 320px; }
.if-card-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px; letter-spacing: 0.12em;
  color: #9d9bbf; text-transform: uppercase;
}
.if-card-wrap {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 0 12px 0 14px;
  background: #1a1a22;
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 10px;
  transition: border-color .2s;
}
.if-card-wrap:focus-within { border-color: #f5a84a; }
.if-card-brand {
  font-family: 'JetBrains Mono', monospace;
  font-size: 9px; font-weight: 700;
  letter-spacing: 0.08em;
  padding: 3px 7px;
  background: rgba(245,168,74,0.12);
  color: #f5a84a;
  border-radius: 4px;
  flex-shrink: 0;
  transition: background .2s, color .2s;
}
.if-card-wrap[data-brand="visa"]       .if-card-brand { background: rgba(67,127,193,0.18); color: #6aa3e0; }
.if-card-wrap[data-brand="mastercard"] .if-card-brand { background: rgba(255,90,90,0.16); color: #ff7a7a; }
.if-card-wrap[data-brand="amex"]       .if-card-brand { background: rgba(46,184,138,0.16); color: #2eb88a; }
.if-card input {
  flex: 1; min-width: 0;
  padding: 13px 0;
  border: 0; outline: none; background: transparent;
  color: #f0eeff;
  font: 600 14px/1 'JetBrains Mono', monospace;
  letter-spacing: 0.08em;
}
.if-card input::placeholder { color: #6b6987; }
<label class="if-card">
  <span class="if-card-label">Card number</span>
  <span class="if-card-wrap" data-if-card>
    <span class="if-card-brand" aria-hidden="true">CARD</span>
    <input type="text" name="cc" inputmode="numeric" autocomplete="cc-number" maxlength="19" placeholder="•••• •••• •••• ••••" />
  </span>
</label>
// Credit Card — auto-format groups of 4 + brand detection
document.querySelectorAll('[data-if-card] input').forEach(function (input) {
  var wrap  = input.closest('[data-if-card]');
  var brand = wrap.querySelector('.if-card-brand');

  function detect(digits) {
    if (/^4/.test(digits))                 return ['visa', 'VISA'];
    if (/^(5[1-5]|2[2-7])/.test(digits))   return ['mastercard', 'MC'];
    if (/^3[47]/.test(digits))             return ['amex', 'AMEX'];
    if (/^6(011|5)/.test(digits))          return ['discover', 'DISC'];
    return ['', 'CARD'];
  }

  input.addEventListener('input', function () {
    var digits = input.value.replace(/\D/g, '').slice(0, 19);
    input.value = (digits.match(/.{1,4}/g) || ['']).join(' ');
    var info = detect(digits);
    if (info[0]) wrap.setAttribute('data-brand', info[0]);
    else         wrap.removeAttribute('data-brand');
    brand.textContent = info[1];
  });
});
27 / 28
Toggle Password
Light JS
Password field with an eye toggle that swaps `type="password"` ↔ `type="text"`. `aria-pressed` reflects state for screen readers; `autocomplete="new-password"` for sign-up flows.
.if-pw { display: grid; gap: 6px; width: 100%; max-width: 280px; }
.if-pw-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px; letter-spacing: 0.12em;
  color: #9d9bbf; text-transform: uppercase;
}
.if-pw-wrap {
  display: inline-flex; align-items: center;
  padding: 0 6px 0 14px;
  background: #1a1a22;
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 10px;
  transition: border-color .2s;
}
.if-pw-wrap:focus-within { border-color: #7c6cff; }
.if-pw input {
  flex: 1; min-width: 0;
  padding: 12px 0;
  border: 0; outline: none; background: transparent;
  color: #f0eeff; font: 500 14px/1 system-ui, sans-serif;
  letter-spacing: 0.08em;
}
.if-pw input::placeholder { color: #6b6987; }
.if-pw-toggle {
  width: 32px; height: 32px;
  border: 0; cursor: pointer; background: transparent;
  border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
  transition: background .15s;
}
.if-pw-toggle:hover { background: rgba(255,255,255,0.05); }
.if-pw-toggle svg { width: 16px; height: 16px; fill: none; stroke: #9d9bbf; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.if-pw-eye-off { display: none; }
.if-pw-toggle[aria-pressed="true"] .if-pw-eye     { display: none; }
.if-pw-toggle[aria-pressed="true"] .if-pw-eye-off { display: block; }
.if-pw-toggle[aria-pressed="true"] svg { stroke: #7c6cff; }
<label class="if-pw">
  <span class="if-pw-label">Password</span>
  <span class="if-pw-wrap">
    <input type="password" name="pw" autocomplete="new-password" placeholder="At least 8 characters" minlength="8" data-if-pw />
    <button type="button" class="if-pw-toggle" aria-label="Show password" aria-pressed="false" data-if-pw-toggle>
      <svg class="if-pw-eye" viewBox="0 0 24 24" aria-hidden="true"><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>
      <svg class="if-pw-eye-off" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 3l18 18M9.9 5.1A10 10 0 0 1 22 12a16 16 0 0 1-2.7 3.7M6.7 6.7A16 16 0 0 0 2 12s4 7 10 7a10 10 0 0 0 4.1-.9"/></svg>
    </button>
  </span>
</label>
// Toggle Password — swap input type + reflect state via aria-pressed
document.querySelectorAll('[data-if-pw-toggle]').forEach(function (btn) {
  var input = btn.closest('.if-pw-wrap').querySelector('input');
  btn.addEventListener('click', function () {
    var on = btn.getAttribute('aria-pressed') === 'true';
    btn.setAttribute('aria-pressed', on ? 'false' : 'true');
    btn.setAttribute('aria-label', on ? 'Show password' : 'Hide password');
    input.type = on ? 'password' : 'text';
  });
});
28 / 28
Brutalist Input
Pure CSS
Bold offset-shadow stamp with hard edges and monospace type — a confident input that doesn't apologize for itself. Press collapses into the shadow on focus.
.if-brut { display: grid; gap: 8px; width: 100%; max-width: 280px; }
.if-brut-label {
  font-family: 'Courier New', monospace;
  font-size: 11px; font-weight: 800;
  letter-spacing: 0.12em;
  color: #f0eeff;
}
.if-brut input {
  width: 100%; box-sizing: border-box;
  padding: 12px 14px;
  background: #f0eeff;
  border: 2.5px solid #1a1a2e;
  border-radius: 0;
  color: #1a1a2e;
  font: 700 13px/1 'Courier New', monospace;
  letter-spacing: 0.06em;
  outline: none;
  box-shadow: 5px 5px 0 #ff6c8a;
  transition: transform .1s, box-shadow .1s;
}
.if-brut input::placeholder { color: rgba(26,26,46,0.5); }
.if-brut input:focus {
  transform: translate(2px, 2px);
  box-shadow: 3px 3px 0 #ff6c8a;
}
.if-brut input:invalid:not(:placeholder-shown) {
  box-shadow: 5px 5px 0 #ff5d6c;
}
<label class="if-brut">
  <span class="if-brut-label">USERNAME_</span>
  <input type="text" name="user-brut" placeholder="enter handle..." />
</label>
FAQ

Frequently asked questions

How do I style a CSS input field?
Style the native input element directly using its tag plus a class. Reset border, padding, background and font with a single rule, then add :focus and :focus-within states for the active look. Use :placeholder-shown to detect empty fields, :has() to react to descendant state, and :valid / :invalid for live validation feedback — all without JavaScript.
Are these CSS input fields accessible?
Yes. Every demo uses a real native input or textarea, paired with a real label (either visible or visually-hidden via .sr-only). Autocomplete attributes are set correctly (email, current-password, new-password, one-time-code, cc-number, tel) so password managers and SMS autofill work. Focus states are visible and keyboard navigation is the default browser behaviour.
What's the most accessible way to do a floating label?
Keep the label as a real label element associated with the input via for=id. Drive the floated state with the :placeholder-shown pseudo-class — the label moves up when the input is non-empty or focused. This way assistive tech still announces the label as the input's accessible name.
Do these inputs need JavaScript?
Most don't — 18 of the 28 are pure CSS using :focus-within, :placeholder-shown, :valid, :has() and adjacent-sibling selectors. The other 10 (OTP, tag input, inline edit, voice search, email suggestions, credit card formatter, etc.) include small self-contained JS snippets in the JS tab of the code panel.
Will autofill and password managers work with these inputs?
Yes. Each input keeps the native element with the correct type and autocomplete attribute. The custom styling never breaks 1Password, Bitwarden, Chrome autofill, or Safari's SMS auto-suggestion.

Related collections