Component
Stable v1.0.0 Form

Input

Text input fields collect single-line data from users. They are the foundation of every form in the system. All input types — text, email, password, search, number — share the same base style via the .input class.

Overview

When to Use

Use Input when
  • Collecting short, single-line text data
  • Email, password, name, phone number fields
  • Search boxes and filter inputs
  • Number or date input
  • Any free-form text under ~100 characters
Don't use Input when
  • Multi-line content needed — use Textarea instead
  • Choosing from predefined options — use Select
  • Binary true/false — use Checkbox or Toggle
  • Long content (>100 chars) — use Textarea
  • Searching with autocomplete — use a combobox pattern

Anatomy

Icon
Container
Enter your work email address
Hint text
1
Label
UPPERCASE 12px fw-700, color:#64748B, letter-spacing:0.5px. Always a <label> element linked to input via for/id. Never omit label — use visually-hidden label if label must be hidden.
2
Container
The <input> element with class .input. padding:10px 14px, border-radius:10px, border:1px solid #E2E8F0, font-size:13px. Width is 100% by default.
3
Icon (optional)
Absolute-positioned SVG inside .input-wrap. Left icon uses .input-icon-left + adds .has-icon-left to input (padding-left:36px). Right icon uses .input-icon-right + .has-icon-right.
4
Hint Text
Optional helper text below the input. Class .form-hint. 11px, #94A3B8. Visible in default state. Replaced by error/success message when validation fires.
5
Validation Message
Error: .form-error-msg — 11px #F05252 fw-600, includes warning icon. Success: .form-success-msg — 11px #0E9F6E. Replaces hint text when shown.
6
Focus Ring
3px soft ring via box-shadow. Primary: 0 0 0 3px rgba(59,91,219,.1) with border-color:#3B5BDB. Error: 0 0 0 3px rgba(240,82,82,.1). Never outline:none.

States

Default
border:#E2E8F0, bg:#fff
Hover
border-color:#CBD5E1
Focus
border:#3B5BDB + 3px ring
Invalid email
Error
border:#F05252 + error msg
Email available
Success
border:#0E9F6E + success msg
Disabled
bg:#F8FAFC, opacity:.75
Read Only
bg:#F8FAFC, no edit cursor

Properties

PropertyTypeRequiredDefaultDescription
typestringoptional'text'HTML input type. Always set explicitly.
'text' | 'email' | 'password' | 'number' | 'search' | 'tel' | 'url' | 'date'
placeholderstringoptionalundefinedGhost text shown when empty. Should hint at the expected format, not repeat the label.
valuestringoptionalundefinedControlled value. Use with onChange for controlled inputs.
disabledbooleanoptionalfalsePrevents all interaction. bg:#F8FAFC, opacity:.75, cursor:not-allowed.
readOnlybooleanoptionalfalsePrevents editing but allows focus and selection. bg:#F8FAFC. Different from disabled — still focusable.
errorstringoptionalundefinedError message text. When set, adds .input-error to input and renders .form-error-msg below.
hintstringoptionalundefinedHelper text shown below input. Hidden when error is active.
labelstringrequiredLabel text. Always required — use visually hidden label if design requires no visible label.
iconLeftReactNodeoptionalundefinedSVG icon displayed on the left. Adds padding-left:36px to input automatically.
iconRightReactNodeoptionalundefinedSVG icon displayed on the right. Adds padding-right:36px to input. Often used for clear or toggle actions.
onChangefunctionoptionalundefinedChange handler for controlled inputs.
(e: ChangeEvent<HTMLInputElement>) => void
maxLengthnumberoptionalundefinedMax character count. When set, show a character counter (.input-char-count) below the input.

Accessibility

KeyAction
TabMove focus to input
Shift+TabMove focus away
EscClear search inputs (type="search") or blur
TypeEnters character into input
Ctrl+ASelect all text
RequirementImplementation
Label associationAlways use <label for="inputId"> linked to <input id="inputId">. Never use placeholder as substitute for label.
Error announcementAdd aria-invalid="true" to input in error state. Error message div gets role="alert" for immediate announcement.
Required fieldsAdd aria-required="true" and visual * indicator after label.
DescriptionLink hint text via aria-describedby="hintId" on input.
Password visibility toggleRight-icon button must have aria-label="Show password" / aria-label="Hide password".
Focus ringNever remove. 3px soft box-shadow ring, visible on all focus states including keyboard and mouse.

Behavior

TriggerBehavior
Focusborder-color:#3B5BDB + box-shadow: 0 0 0 3px rgba(59,91,219,.1). Transition: 0.2s.
Hover (unfocused)border-color:#CBD5E1. Subtle darkening signals interactivity.
BlurTriggers validation if field has been touched. Reverts to default border if no error. Never validate on keystroke unless explicitly needed.
Input (type)Updates value. Character counter updates if maxLength set. No validation on keystroke by default.
DisabledNo hover, focus, or click response. opacity:.75, cursor:not-allowed, bg:#F8FAFC.
Error stateborder-color:#F05252, focus ring rgba(240,82,82,.1). Error message rendered with icon below. Clears on successful re-validation.
Password toggleRight eye-icon toggles type between "password" and "text". Icon changes to reflect current state.
Clear (search)X button in right icon clears value and re-focuses input.

Composition

Allowed
Inside .form-group with .form-label above
Inside Modal body for forms
Inside filter bar with flex:1 min-width
With .input-wrap for icon variants
With .form-error-msg or .form-hint below
Two-column grid for paired fields (name, address)
Forbidden
Without a label (use aria-label if label hidden)
Placeholder used as label substitute
Bare input without .form-group wrapper
Inline in body text or table cells
Nested inside another input
Validation error without .form-error-msg message

Content Rules

Email Address
Noun phrase. UPPERCASE. Specific.
Enter your email here
Sentence case. Too verbose for a label.
you@company.com
Shows expected format, not the label.
Email Address
Duplicates the label. Disappears on type.
Please enter a valid email address
Specific, tells user what to fix.
Invalid input
Vague, provides no guidance.

AI Implementation Guide

RuleDetail
Always wrap in .form-groupEvery input must be inside <div class="form-group"> with margin-bottom:16px.
Label is mandatoryAlways include <label class="form-label"> linked via for/id. UPPERCASE 12px fw-700 letter-spacing:0.5px.
Base classAll form fields (input, select, textarea) use .input class. Never custom-style individual fields.
Error stateAdd .input-error to input AND render <div class="form-error-msg"> with warning SVG icon below.
Focus ringNever override. box-shadow: 0 0 0 3px rgba(59,91,219,.1) on focus. For error state: rgba(240,82,82,.1).
Icon patternWrap in .input-wrap. Add .has-icon-left or .has-icon-right to input. Place icon as sibling inside .input-wrap.
Never validate on keystrokeValidate on blur (when user leaves field) or on form submit. Never show errors while user is typing.
Placeholder vs labelPlaceholder shows example format (e.g., "user@company.com"). Label names the field (e.g., "EMAIL ADDRESS"). Never use placeholder as label.

Design Decisions

Why 10px border-radius instead of a sharper or fully rounded style?
10px is the standard radius for interactive controls (inputs, selects, textareas, dropdown items). It feels modern without being too casual. It differs from cards (20px) to signal "this is a control, not a container" and from buttons (12px) to avoid visual confusion between the two.
Why use box-shadow for focus ring instead of outline?
box-shadow respects the border-radius, creating a soft ring that hugs the shape of the input. The native outline is rectangular and ignores border-radius. The 3px soft ring at 10% opacity adds a halo effect that is subtle on default state and visible enough for accessibility without being jarring.
Why validate on blur, not on keystroke?
Showing errors while the user is still typing is disruptive and frustrating — the input is in a valid in-progress state that shouldn't be flagged. Blur validation respects the user's flow: it checks when they signal "I'm done with this field" by moving away. This is standard UX practice and reduces false error states.

Changelog

v1.0.0July 2026Added
  • Initial release — all HTML input types supported via .input class
  • 5 states: default, hover, focus, error, success, disabled, read-only
  • Icon support (left and right) via .input-wrap pattern
  • Validation messages (.form-error-msg, .form-success-msg)
  • Character counter support via .input-char-count

Best Practices

Do
Enter your work email.
Specific label, contextual placeholder showing format, helpful hint below.
Don't
No label, placeholder used as label substitute — disappears on type, inaccessible.
Do
Required indicator, type=password for security, placeholder shows constraint.
Don't
Error
type=text for password (exposes value), vague error message, no guidance.