Design System
Components

Input

Text field with a floating label — text, dropdown, picker and comment types — plus a six-cell OTP entry.

Figma description · 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.

Email address
We’ll never share it.
Props
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). Use type="comment" for multi-line text, and dropdown or picker for a read-only field that opens a chooser.
  • Use OtpInput for 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.
  • error draws a 1px danger stroke and a red status line. helper is neutral and success is green. statusIcon={false} hides the info glyph before the message.
  • clearable shows a 24px Dismiss while the field is active and has a value.
  • inverse turns 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

PropTypeDefaultDescription
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?booleantrueShow 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'textFigma 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.
PropTypeDefaultDescription
defaultValue?string—
disabled?boolean—
error?ReactNode—
inverse?boolean—
length?number6Number 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/md inline padding.
  • The label is body/lg at rest and body/sm once floated; the value is body/lg.
  • Strokes are drawn inside, so the field never shifts: active border/interactive/accent/default, error border/interactive/danger/subtle, both border/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/md with a backdrop blur; disabled surface/glass/white/sm.
  • OTP cells are 48×56, spread with spacing/lg side padding.

On this page