Styling Text Inputs and Textareas
Text inputs and textareas are the core of any form. Getting them right means consistent cross-browser appearance, clear focus states, and accessible interactions.
1. The Foundation: Reset and Base Styles
Start from a clean slate — browsers apply massively different defaults to inputs:
.input {
/* Reset */
appearance: none;
-webkit-appearance: none;
border: none;
background: transparent;
outline: none;
/* Layout */
display: block;
width: 100%;
padding: 0.75rem 1rem;
/* Visual */
background: var(--color-surface, #1a1a1a);
border: 1px solid var(--color-border, rgba(255,255,255,0.1));
border-radius: var(--radius-md, 8px);
color: var(--color-text, #f0f0f0);
font-family: var(--font-body, system-ui);
font-size: var(--text-base, 1rem);
line-height: 1.5;
/* Transition */
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
2. Focus State (The Most Important State)
Focus is the most critical state for accessibility:
/* ❌ Never do this alone */
.input:focus { outline: none; }
/* ✅ Replace outline with custom focus ring */
.input:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(108, 99, 255, 0.25);
}
/* ✅ Or use outline-offset for a proper outline */
.input:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
3. Complete Input Component
<div class="form-group">
<label class="form-label" for="email">Email Address</label>
<input class="form-input" type="email" id="email" name="email"
placeholder="you@example.com" required>
<span class="form-hint">We'll never share your email.</span>
</div>
/* Form group wrapper */
.form-group {
display: flex;
flex-direction: column;
gap: 0.4rem;
}
/* Label */
.form-label {
font-size: var(--text-sm, 0.875rem);
font-weight: 600;
color: var(--color-text, #f0f0f0);
cursor: pointer; /* Clicking label focuses input */
}
/* ★ Required asterisk */
.form-label[data-required]::after {
content: ' *';
color: var(--color-danger, #e53e3e);
}
/* Input */
.form-input {
appearance: none;
display: block;
width: 100%;
padding: 0.7rem 1rem;
background: var(--color-surface, #1a1a1a);
border: 1.5px solid var(--color-border, rgba(255,255,255,0.08));
border-radius: var(--radius-md, 8px);
color: var(--color-text, #f0f0f0);
font-size: var(--text-base, 1rem);
line-height: 1.5;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.form-input:focus {
outline: none;
border-color: var(--color-primary, #6c63ff);
box-shadow: 0 0 0 3px rgba(108, 99, 255, 0.2);
}
.form-input:disabled {
opacity: 0.5;
cursor: not-allowed;
background: rgba(255,255,255,0.03);
}
/* Hint / helper text */
.form-hint {
font-size: var(--text-sm, 0.875rem);
color: var(--color-text-muted, #888);
}
4. Placeholder Styling
/* Cross-browser placeholder */
.form-input::placeholder {
color: var(--color-text-muted, #666);
opacity: 1; /* Firefox reduces opacity by default */
}
/* Show/hide placeholder based on focus */
.form-input:focus::placeholder { opacity: 0.5; }
5. Input Sizes
/* Size modifier system */
.form-input--sm {
padding: 0.4rem 0.75rem;
font-size: var(--text-sm, 0.875rem);
border-radius: var(--radius-sm, 4px);
}
.form-input--lg {
padding: 1rem 1.25rem;
font-size: var(--text-lg, 1.125rem);
border-radius: var(--radius-lg, 12px);
}
6. Input with Icon (Left / Right)
<div class="input-wrapper">
<span class="input-icon input-icon--left">🔍</span>
<input class="form-input has-icon-left" type="search" placeholder="Search...">
</div>
.input-wrapper { position: relative; }
.input-icon {
position: absolute;
top: 50%; transform: translateY(-50%);
color: var(--color-text-muted);
pointer-events: none;
font-size: 1rem;
line-height: 1;
}
.input-icon--left { left: 0.875rem; }
.input-icon--right { right: 0.875rem; }
.form-input.has-icon-left { padding-left: 2.5rem; }
.form-input.has-icon-right { padding-right: 2.5rem; }
7. Textarea
<div class="form-group">
<label class="form-label" for="message">Message</label>
<textarea class="form-input form-textarea" id="message" rows="4"
placeholder="Tell us more..."></textarea>
</div>
.form-textarea {
resize: vertical; /* Allow vertical resize only */
min-height: 120px;
max-height: 400px;
}
/* Auto-growing textarea (modern CSS — no JS) */
.form-textarea--auto {
field-sizing: content; /* CSS4: grows with content */
min-height: 80px;
}
8. Floating Label Pattern
<div class="float-label-group">
<input class="float-input" type="text" id="name" placeholder=" " required>
<label class="float-label" for="name">Full Name</label>
</div>
.float-label-group { position: relative; }
.float-input {
width: 100%;
padding: 1.5rem 1rem 0.5rem;
border: 1.5px solid var(--color-border);
border-radius: var(--radius-md);
background: var(--color-surface);
color: var(--color-text);
font-size: 1rem;
outline: none;
transition: border-color 0.2s;
}
.float-input:focus { border-color: var(--color-primary); }
.float-label {
position: absolute;
left: 1rem;
top: 1rem;
font-size: 1rem;
color: var(--color-text-muted);
pointer-events: none;
transition: top 0.2s, font-size 0.2s, color 0.2s;
}
/* When input has content (placeholder=" " trick) or is focused */
.float-input:not(:placeholder-shown) + .float-label,
.float-input:focus + .float-label {
top: 0.4rem;
font-size: 0.75rem;
color: var(--color-primary);
}