Input
Text field with a floating label — text, dropdown, picker and comment types — plus a six-cell OTP entry.
input-fieldOpen in Figma ↗Field for collecting or selecting a user value. Use this set rather than building inputs by hand.
Anatomy
Label above, surface with optional left/right addons, value, status message below.
Categories (type)
text - free-form entry
dropdown - selection from a list, opens inline
picker - selection through a dedicated picker surface
comment - longer-form, multi-line
otp - one-time passcode
Not for search (use searchbar) or filtering (use filter-pill, chips).
Choosing a state
Decide in order: type, then empty or filled, then one interaction state.
Empty is default=true. Filled is filled=true. They are complementary - never set both the same.
Then at most one of: active (focused or engaged), disabled, error.
Resting is active=false, disabled=false, error=false.
Never combine disabled=true with active=true or error=true.
text, dropdown, picker and comment support all eight combinations. otp has no empty-error and no filled-active variant. Do not construct unsupported combinations - use a published variant.
Fixed internally, not selectable
Addons, status message, inverse and RTL are set inside the component and are not exposed as properties. Do not attempt to select them.
Addons appear left or right: an icon for context, a copy control for reusable values, dropdown tools for list controls.
The status message carries validation - error after failed validation, success on confirmation, neutral for hints.
Designer and AI usage
Preserve the instance and select variants. Never detach or restyle manually.
Infer filled from whether a value exists. Use active only for current focus, disabled only for genuinely unavailable controls, error only after validation fails.
Always label the field - placeholder text is not a label.
Always pair an error state with error status text. Colour alone is not sufficient.
import SwiftUI import EandTokens // V1.2 input-field: 56pt min, radius 14, padding 12 inline. No stroke at rest; a 1pt // accent stroke while active, 1pt danger on error — drawn inside, so the field never // shifts. Floating label body/sm over a body/lg value. struct EandInput: View { let label: String; @Binding var value: String var error = false @FocusState private var focused: Bool var body: some View { VStack(alignment: .leading, spacing: EandSpacing.spacing2xs) { Text(label).font(.system(size: EandTypography.bodySm.size)) .foregroundColor(EandColor.inputFieldTextDefaultLabelDefault) TextField("", text: $value).focused($focused) .font(.system(size: EandTypography.bodyLg.size)) .foregroundColor(EandColor.inputFieldTextDefaultValueDefault) .tint(EandColor.textBrandDefault) } .padding(.horizontal, EandSpacing.md).padding(.vertical, EandSpacing.sm) .frame(minHeight: EandScale.scale56) .background(EandColor.inputFieldSurfaceDefaultDefault) .overlay { if error || focused { RoundedRectangle(cornerRadius: EandRadius.radius5).strokeBorder( error ? EandColor.borderInteractiveDangerSubtle : EandColor.borderInteractiveAccentDefault, lineWidth: EandBorder.sm) } } .clipShape(RoundedRectangle(cornerRadius: EandRadius.radius5)) } }
When to use
- Use for values the user types: names, numbers, email, passwords (set
htmlType). Usetype="comment"for multi-line text, anddropdownorpickerfor a read-only field that opens a chooser. - Use
OtpInputfor one-time codes. - Don't use for search — that is the Searchbar. To pick from a few visible options, use a Picker or Radio group.
States
- At rest, filled and disabled the field has no stroke. While active it gains a 1px accent stroke.
errordraws a 1px danger stroke and a red status line.helperis neutral andsuccessis green.statusIcon={false}hides the info glyph before the message.clearableshows a 24px Dismiss while the field is active and has a value.inverseturns the surface to white glass for brand-red and midnight backgrounds.
OtpInput defaults to six cells. Codes read left to right, so the cells do not mirror on
Arabic pages; masked shows dots instead of digits. variant is a deprecated V1.0 prop and
is ignored.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
clearable? | boolean | — | Show the 24px dismiss (clear) button while the field is active and has a value. |
error? | ReactNode | — | Error hint below the field (Figma status=error); drives the error stroke. |
helper? | ReactNode | — | Neutral hint below the field (Figma status=neutral). |
htmlType? | HTMLInputTypeAttribute | — | Underlying HTML input type (password, email, …) for type="text". |
inverse? | boolean | — | Inverse scheme for dark (midnight/brand) surfaces. |
label? | ReactNode | — | Floating label inside the field. |
leadingIcon? | ReactNode | — | Leading slot: a 24px icon-size — pass a 20px glyph. |
onClear? | (() => void) | — | |
statusIcon? | boolean | true | Show the info glyph before the status text (Figma info-icon, default true). |
success? | ReactNode | — | Success hint below the field (Figma status=success). |
trailingIcon? | ReactNode | — | Trailing slot: a 24px icon-size — pass a 20px glyph. |
type? | 'text' | 'dropdown' | 'picker' | 'comment' | text | Figma type axis. dropdown/picker render read-only with a trailing chevron. |
variant? | 'outlined' | 'filled' | — | Deprecated. V1.0 axis — V1.1 has a single filled style; ignored. |
| Prop | Type | Default | Description |
|---|---|---|---|
defaultValue? | string | — | |
disabled? | boolean | — | |
error? | ReactNode | — | |
inverse? | boolean | — | |
length? | number | 6 | Number of cells. Figma publishes 6. |
masked? | boolean | — | Render entered characters as dots. |
onValueChange? | ((value: string) => void) | — | |
value? | string | — |
Design
Figma source: input-field — etisalat Consumer App DSL V1.2 · otp
- 56px minimum height (
scale/56),border-radius/5(14),spacing/mdinline padding. - The label is
body/lgat rest andbody/smonce floated; the value isbody/lg. - Strokes are drawn inside, so the field never shifts: active
border/interactive/accent/default, errorborder/interactive/danger/subtle, bothborder/sm. - Slots: leading and trailing 24px icon boxes (pass a 20px glyph) with 4px spacing; the dropdown chevron is 12px in a 16px box.
- Inverse:
surface/glass/white/mdwith a backdrop blur; disabledsurface/glass/white/sm. - OTP cells are 48×56, spread with
spacing/lgside padding.