UI Components Easy
Input Variants
A complete set of text input styles — default, with prefix/suffix icons, floating label, input group with helper text, and validation states.
Open in Lab
MCP
css vanilla-js
Targets: JS HTML
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Inter, system-ui, sans-serif;
background: #050910;
color: #f2f6ff;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.demo {
width: 100%;
max-width: 560px;
}
.demo-title {
font-size: 1.5rem;
font-weight: 800;
margin-bottom: 0.375rem;
}
.demo-sub {
color: #475569;
font-size: 0.875rem;
margin-bottom: 2rem;
}
.inputs-grid {
display: flex;
flex-direction: column;
gap: 1.25rem;
}
/* ── Form group ── */
.form-group {
display: flex;
flex-direction: column;
gap: 0.375rem;
}
.form-label {
font-size: 0.8rem;
font-weight: 500;
color: #94a3b8;
}
.form-helper {
font-size: 0.75rem;
color: #475569;
}
.form-error {
font-size: 0.75rem;
color: #ef4444;
}
/* ── Base input ── */
.input {
width: 100%;
padding: 0.625rem 0.875rem;
background: #0d1117;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 0.625rem;
color: #f2f6ff;
font-size: 0.875rem;
font-family: inherit;
outline: none;
transition: border-color 0.15s, box-shadow 0.15s;
resize: vertical;
}
.input::placeholder {
color: #334155;
}
.input:focus {
border-color: #38bdf8;
box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.15);
}
/* ── Icon wrapper ── */
.input-wrap {
position: relative;
display: flex;
align-items: center;
}
.input-icon {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
color: #475569;
pointer-events: none;
}
.input-icon--left {
left: 0.75rem;
}
.input-icon--right {
right: 0.75rem;
}
.input-icon--btn {
pointer-events: auto;
background: none;
border: none;
cursor: pointer;
transition: color 0.15s;
}
.input-icon--btn:hover {
color: #94a3b8;
}
.input--icon-left {
padding-left: 2.5rem;
}
.input--icon-right {
padding-right: 2.5rem;
}
/* ── States ── */
.input--valid {
border-color: #22c55e;
}
.input--invalid {
border-color: #ef4444;
}
.input--valid:focus {
box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.15);
}
.input--invalid:focus {
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.15);
}
/* ── Floating label ── */
.float-wrap {
position: relative;
}
.input--float {
padding-top: 1.25rem;
padding-bottom: 0.375rem;
}
.float-label {
position: absolute;
top: 50%;
left: 0.875rem;
transform: translateY(-50%);
font-size: 0.875rem;
color: #475569;
pointer-events: none;
transition: top 0.15s, font-size 0.15s, color 0.15s;
}
.input--float:not(:placeholder-shown) + .float-label,
.input--float:focus + .float-label {
top: 0.5rem;
transform: none;
font-size: 0.7rem;
color: #38bdf8;
}
/* ── Textarea ── */
.input--textarea {
resize: vertical;
min-height: 80px;
}
/* ── Character count ── */
.input-wrap--count {
flex-direction: column;
align-items: flex-end;
gap: 0.25rem;
}
.input-wrap--count .input {
width: 100%;
}
.char-count {
font-size: 0.72rem;
color: #334155;
}(function () {
// Password toggle
var pwToggle = document.getElementById("pw-toggle");
var pwInput = document.getElementById("i-pass");
var eyeShow = document.getElementById("eye-show");
var eyeHide = document.getElementById("eye-hide");
if (pwToggle && pwInput) {
pwToggle.addEventListener("click", function () {
var isText = pwInput.type === "text";
pwInput.type = isText ? "password" : "text";
eyeShow.style.display = isText ? "" : "none";
eyeHide.style.display = isText ? "none" : "";
pwToggle.setAttribute("aria-label", isText ? "Show password" : "Hide password");
});
}
// Character counter
var bioArea = document.getElementById("i-bio");
var bioCount = document.getElementById("bio-count");
if (bioArea && bioCount) {
bioArea.addEventListener("input", function () {
bioCount.textContent = bioArea.value.length;
});
}
})();<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Input Variants</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="demo">
<h1 class="demo-title">Input Variants</h1>
<p class="demo-sub">The complete input system — labels, icons, validation, and floating labels.</p>
<div class="inputs-grid">
<!-- Default -->
<div class="form-group">
<label class="form-label" for="i-default">Full name</label>
<input id="i-default" type="text" class="input" placeholder="Jane Doe" />
</div>
<!-- Icon prefix -->
<div class="form-group">
<label class="form-label" for="i-search">Search</label>
<div class="input-wrap">
<span class="input-icon input-icon--left" aria-hidden="true">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
</span>
<input id="i-search" type="search" class="input input--icon-left" placeholder="Search resources…" />
</div>
</div>
<!-- Password toggle -->
<div class="form-group">
<label class="form-label" for="i-pass">Password</label>
<div class="input-wrap">
<input id="i-pass" type="password" class="input input--icon-right" placeholder="Enter password" />
<button class="input-icon input-icon--right input-icon--btn" id="pw-toggle" aria-label="Show password" type="button">
<svg id="eye-show" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
<svg id="eye-hide" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display:none"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
</button>
</div>
</div>
<!-- Floating label -->
<div class="form-group">
<div class="float-wrap">
<input id="i-float" type="email" class="input input--float" placeholder=" " />
<label class="float-label" for="i-float">Email address</label>
</div>
</div>
<!-- With helper text -->
<div class="form-group">
<label class="form-label" for="i-username">Username</label>
<input id="i-username" type="text" class="input" placeholder="johndoe" />
<p class="form-helper">Must be 3–20 characters. Letters, numbers, underscores only.</p>
</div>
<!-- Character count -->
<div class="form-group">
<label class="form-label" for="i-bio">Bio</label>
<div class="input-wrap input-wrap--count">
<textarea id="i-bio" class="input input--textarea" rows="3" maxlength="160" placeholder="Tell us about yourself…"></textarea>
<span class="char-count"><span id="bio-count">0</span> / 160</span>
</div>
</div>
<!-- Valid state -->
<div class="form-group">
<label class="form-label" for="i-valid">Email (valid)</label>
<div class="input-wrap">
<input id="i-valid" type="email" class="input input--valid input--icon-right" value="jane@example.com" />
<span class="input-icon input-icon--right" aria-hidden="true">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>
</span>
</div>
</div>
<!-- Invalid state -->
<div class="form-group">
<label class="form-label" for="i-invalid">Email (invalid)</label>
<div class="input-wrap">
<input id="i-invalid" type="email" class="input input--invalid input--icon-right" value="not-an-email" aria-describedby="i-invalid-err" />
<span class="input-icon input-icon--right" aria-hidden="true">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#ef4444" stroke-width="2.5"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
</span>
</div>
<p class="form-error" id="i-invalid-err">Please enter a valid email address.</p>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>Input Variants
A complete input component system covering the most common UI patterns.
Variants
| Variant | Description |
|---|---|
| Default | Label above, full-width |
| Icon prefix | Leading icon inside input |
| Icon suffix | Trailing icon / button |
| Floating label | Label animates up on focus |
| Input group | Label + input + helper text |
| Valid state | Green border + check icon |
| Invalid state | Red border + error message |
| Character count | Live counter (e.g. 48 / 200) |
| Password toggle | Show/hide password button |
Accessibility
All inputs have associated <label> elements. Error states use aria-describedby to link the error message.