Input
A native single-line control with explicit naming, constraints and truthful submission behavior.
Choose a single-line control
Use Input for a short value that fits on one line. Choose Textarea for paragraphs; NumberField for quantities with increments; Radio or Select for a constrained choice. Dates, passwords, email and search need task-specific native types and instructions. A numeric identifier such as a telephone number is text, not an arithmetic quantity.
This wrapper renders a native input, not the Base UI Input primitive. Inside Field, compose FieldControl render={<Input />} to register it; otherwise connect native label, help and error IDs explicitly.
Anatomy and specifications
| Part | Actual contract |
|---|---|
| Container | Native input, 100% width; min-width 0 and max-width 100%; token radius-md |
| Sizes | sm: 36px / text-sm / 8px inline padding; md: 44px / text-base / 12px; lg: 48px / text-lg / 16px. Compact height is not a universal 44px target claim. |
| Outline / filled | 2px readable boundary; filled adds muted surface. These are MobTranslate variants, not Material floating-label implementations. |
| Label / supporting text | Separate visible label and described-by help/error; wrapper has no label, prefix, suffix or clear-button prop. Compose controls alongside it. |
| Focus / error / success | Token ring and semantic border. Error sets default aria-invalid; explicit native ARIA props can override it. Success is visual only. |
Material text-field decisions and local field composition
Filled and outlined reference fields have equal task capabilities; choose a consistently discoverable treatment within a region. The local Input supports outline and filled variants with its own 2px boundary and separate visible labels. It does not implement a floating/notched label. Native 56dp height/target,16dp inline padding (12dp with icons),8dp filled vertical spacing and 4dp supporting gap are design references, not conversions of the local size props.
| Reference decision | Local web contract and application choice |
|---|---|
| Label and required state | Use a persistent associated label, clear required semantics and any necessary format explanation. Native guidance discourages multiline labels and uses an explained asterisk; this web guide preserves full wrapped labels at 200% text and can say Required explicitly. A disappearing placeholder or color-only asterisk is insufficient. |
| Text and multiline behavior | Single-line input scrolls its value horizontally as the caret moves. Long responses belong in Textarea; the reference specifically recommends web textareas over native expanding multiline fields. Preserve editing, autofill, IME composition, caret and actual submitted values; do not rewrite text simply to imitate native floating-label motion. |
| Affixes and actions | Prefix/suffix units need meaningful associated descriptions and a separate value model. Decorative icons are not buttons. Clear, show/hide password, picker or microphone actions need actual behavior, persistent names and usable targets; never imply voice capability from a glyph. The wrapper has no automatic affix/action API. |
| Supporting text and errors | The usage chapter recommends replacing help with error to limit movement, while accessibility allows help followed by error. Retain instructions essential to correction, connect all relevant IDs, apply aria-invalid and explain the actual failure. An error icon or border is supplementary; validation and success/persistence remain application-owned. |
| Counters and availability | Any counter must reflect the declared real limit and character/word policy, without announcing every keystroke intrusively. Read-only values remain readable and registered; disabled controls cannot be edited and are omitted from native submission. These states do not establish authorization or server success. |
| Contrast and adaptation | The reference calls for a3:1 outline boundary; local tokens supply readable control boundaries, text and focus/error treatment. Use bounded field widths for comfortable wide-screen scanning and full available width on compact screens. Avoid default density reduction or shrinking type/targets to preserve a desktop form layout. |
Working native value and availability
'use client';
import { useRef, useState } from 'react';
import { Button, Input, Textarea, type InputProps } from '@mobtranslate/ui';
export function NativeInputExample() {
const [value, setValue] = useState('');
const [variant, setVariant] = useState<NonNullable<InputProps['variant']>>('outline');
const [size, setSize] = useState<NonNullable<InputProps['size']>>('md');
const [readOnly, setReadOnly] = useState(false);
const [disabled, setDisabled] = useState(false);
const [error, setError] = useState('');
const [result, setResult] = useState('No local input submitted.');
const input = useRef<HTMLInputElement>(null);
return <form data-native-input-lab noValidate className="space-y-4" onSubmit={event => {
event.preventDefault();
if (!disabled && !value.trim()) { setError('Enter a local display label.'); input.current?.focus(); return; }
setError(''); setResult(`Local FormData: ${new FormData(event.currentTarget).get('displayLabel') ?? 'displayLabel omitted (disabled)'}. No data was sent.`);
}}>
<div className="flex flex-wrap gap-3"><label>Appearance <select data-input-variant className="mt-input" value={variant} onChange={event => setVariant(event.target.value as typeof variant)}><option>outline</option><option>filled</option></select></label><label>Density <select data-input-size className="mt-input" value={size} onChange={event => setSize(event.target.value as typeof size)}><option>sm</option><option>md</option><option>lg</option></select></label></div>
<label htmlFor="native-display-label">Local display label (required when available)</label>
<Input ref={input} id="native-display-label" name="displayLabel" value={value} onChange={event => { setValue(event.target.value); setError(''); }} variant={variant} size={size} readOnly={readOnly} disabled={disabled} required autoComplete="off" maxLength={80} aria-describedby={error ? 'native-input-help native-input-error' : 'native-input-help'} state={error ? 'error' : 'default'} />
<p id="native-input-help">English-only local example. A visible label remains when the value is filled.</p>
{error && <p id="native-input-error" role="alert">{error}</p>}
<div className="flex flex-wrap gap-3"><Button type="button" variant="outline" onClick={() => setReadOnly(current => !current)}>{readOnly ? 'Allow local input editing' : 'Make local input read-only'}</Button><Button type="button" variant="outline" onClick={() => setDisabled(current => !current)}>{disabled ? 'Enable local input' : 'Disable local input'}</Button><Button type="submit">Submit native input</Button></div>
<p role="status">{result}</p>
</form>;
}
export function NativeTextareaExample() {
const [value, setValue] = useState('');
const [readOnly, setReadOnly] = useState(false);
const [resize, setResize] = useState<'none' | 'vertical' | 'horizontal' | 'both'>('vertical');
const [error, setError] = useState('');
const [result, setResult] = useState('No local note submitted.');
const textarea = useRef<HTMLTextAreaElement>(null);
return <form data-textarea-lab noValidate className="space-y-4" onSubmit={event => {
event.preventDefault();
if (!value.trim()) { setError('Write a local example note before submitting.'); textarea.current?.focus(); return; }
setError(''); setResult(`Local note retained: ${String(new FormData(event.currentTarget).get('note'))}. No data was sent.`);
}}>
<label htmlFor="native-note">Local example note (required)</label>
<Textarea ref={textarea} id="native-note" name="note" value={value} onChange={event => { setValue(event.target.value); setError(''); }} rows={4} maxLength={120} required readOnly={readOnly} resize={resize} state={error ? 'error' : 'default'} aria-describedby={error ? 'native-note-help native-note-error' : 'native-note-help'} />
<p id="native-note-help">{value.length} of 120 UTF-16 code units. This browser limit is not a count of spoken words or graphemes.</p>
{error && <p id="native-note-error" role="alert">{error}</p>}
<label>Resize behavior <select data-textarea-resize className="mt-input" value={resize} onChange={event => setResize(event.target.value as typeof resize)}>{['none','vertical','horizontal','both'].map(item => <option key={item}>{item}</option>)}</select></label>
<div className="flex flex-wrap gap-3"><Button type="button" variant="outline" onClick={() => setReadOnly(current => !current)}>{readOnly ? 'Allow local note editing' : 'Make local note read-only'}</Button><Button type="submit">Submit native note</Button></div>
<p role="status">{result}</p>
</form>;
}
API reference
| Prop | Contract |
|---|---|
| type | Native input type; text default. Browser/type-specific behavior remains native. |
| size / htmlSize | Visual sm|md|lg (md default); htmlSize maps to native size attribute. Width styling can supersede native size. |
| variant / state | outline|filled (outline default); default|error|success. State is not validation or persistence. |
| value / defaultValue / onChange | Native controlled/uncontrolled contracts and React ChangeEvent; avoid switching ownership. |
| id / name / required / minLength / maxLength / pattern | Native naming and constraints; registered Field validation or application submission must actually inspect them. |
| disabled / readOnly | Native input enforcement; distinct successful-control behavior. |
| autoComplete / inputMode / enterKeyHint | Task-appropriate autofill/mobile hints; not security or validation. |
| className / style / ref / native ARIA | Native string class, style and HTMLInputElement ref; explicit label/description/error relationships. |
States and behavior
| State | Required behavior |
|---|---|
| Empty / populated | Keep the visible label and instruction. Placeholder is an optional example, never the accessible name. |
| Hover / focus | Readable boundary and token focus treatment. Focus should stay visible beneath persistent navigation. |
| Error | Name the problem and correction in text; connect it to the control and expose aria-invalid. A red border alone is insufficient. |
| Read-only | Focusable/selectable value; native readOnly suppresses edits and retains a successful form control. |
| Disabled | Unavailable interaction and exclusion from native FormData; preserve an explanation nearby. |
| Pending | Retain the draft. Prevent duplicate requests and make the requested versus retained outcome explicit. |
| Success | Show what was retained and where. A success appearance alone proves no save. |
Keyboard, mobile input and composition
- Tab focuses; arrows/Home/End edit text; Enter may submit its form. Native type changes do not replace a stable name or visible instructions.
- Use autoComplete tokens for real account/address tasks; support paste and password managers. The local example uses off for an arbitrary label and collects no credentials.
- Do not validate partially typed composition text as a final value. Preserve IME composition and React/native events in custom handlers. Use inputMode as a keyboard hint, then validate independently.
- For an adjacent reveal/clear action use a named type=button with its own reachable target and focus recovery; never insert an unnamed icon inside an input.
Failures and recovery
- A placeholder vanishes on entry; supply a label. A plain native Input does not automatically become FieldControl.
- NoValidate delegates validation to your implementation; it does not make invalid data acceptable. Native minLength checking has interaction-dependent behavior, so business rules still need authoritative checks.
- Disabled controls disappear from native FormData. Use read-only for a retained value that must remain included; use explicit server-held values for authorization.
- Do not silently normalize culturally significant spelling or claim a local result was remotely saved. Preserve the draft after rejection and offer a retry.
Verification and release scope
- Verify the actual label/control connection, description/error IDs, invalid state, focus order and corrected input with pointer and keyboard. Read the submitted payload rather than inferring it from visible text.
- Test blank, populated, invalid, disabled, read-only and pending/recovery states. Check both themes, long labels, user text spacing, 320px reflow, reduced motion, forced colors and zoom.
- Run the named input-family browser checks and source/type/build checks. Inspect mobile keyboards, autofill, password managers and a named screen-reader/browser pair separately. DOM tests do not prove their behavior.
- Client validation supports correction; it does not establish authorization, consent, source authority, language quality, identity, security or a successful server write.