MobTranslate styleguide

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

PartActual contract
ContainerNative input, 100% width; min-width 0 and max-width 100%; token radius-md
Sizessm: 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 / filled2px readable boundary; filled adds muted surface. These are MobTranslate variants, not Material floating-label implementations.
Label / supporting textSeparate visible label and described-by help/error; wrapper has no label, prefix, suffix or clear-button prop. Compose controls alongside it.
Focus / error / successToken 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 decisionLocal web contract and application choice
Label and required stateUse 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 behaviorSingle-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 actionsPrefix/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 errorsThe 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 availabilityAny 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 adaptationThe 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

English-only local example. A visible label remains when the value is filled.

No local input submitted.

tsx
'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

PropContract
typeNative input type; text default. Browser/type-specific behavior remains native.
size / htmlSizeVisual sm|md|lg (md default); htmlSize maps to native size attribute. Width styling can supersede native size.
variant / stateoutline|filled (outline default); default|error|success. State is not validation or persistence.
value / defaultValue / onChangeNative controlled/uncontrolled contracts and React ChangeEvent; avoid switching ownership.
id / name / required / minLength / maxLength / patternNative naming and constraints; registered Field validation or application submission must actually inspect them.
disabled / readOnlyNative input enforcement; distinct successful-control behavior.
autoComplete / inputMode / enterKeyHintTask-appropriate autofill/mobile hints; not security or validation.
className / style / ref / native ARIANative string class, style and HTMLInputElement ref; explicit label/description/error relationships.

States and behavior

StateRequired behavior
Empty / populatedKeep the visible label and instruction. Placeholder is an optional example, never the accessible name.
Hover / focusReadable boundary and token focus treatment. Focus should stay visible beneath persistent navigation.
ErrorName the problem and correction in text; connect it to the control and expose aria-invalid. A red border alone is insufficient.
Read-onlyFocusable/selectable value; native readOnly suppresses edits and retains a successful form control.
DisabledUnavailable interaction and exclusion from native FormData; preserve an explanation nearby.
PendingRetain the draft. Prevent duplicate requests and make the requested versus retained outcome explicit.
SuccessShow 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.