Documentation

Input Components

Collect user input with TextField and OutlinedTextField components.

TextField

Material Design filled text field:

<TextField label="Name" />

OutlinedTextField

Material Design outlined text field:

<OutlinedTextField label="Email" />

Two-Way Binding

Use bind:value to sync input with state:

var email = ""

<TextField bind:value={email} label="Email" />
<Text>You typed: {email}</Text>

bind:value={email} transpiles to value = email, onValueChange = {{ email = it }}. Changes automatically update your state variable.

Input Types

Use the type prop to get appropriate keyboards and transformations:

<TextField type="password" label="Password" />
<TextField type="email" label="Email" />
<TextField type="phone" label="Phone" />
<TextField type="number" label="Age" />
<TextField type="url" label="Website" />
TypeEffect
passwordHides characters with dots
emailEmail keyboard
phonePhone number keyboard
numberNumeric keyboard
urlURL keyboard

Common Props

Label

<TextField label="Username" />

Placeholder

<TextField placeholder="Enter your name" />

Helper Text

<TextField label="Password" helperText="Must be at least 8 characters" />

Error State

var email = ""
val isValid = email.contains("@")

<TextField
  bind:value={email}
  label="Email"
  error={!isValid && email.isNotEmpty()}
  helperText={if (!isValid && email.isNotEmpty()) "Invalid email" else ""}
/>

Disabled

<TextField label="Username" value="admin" disabled />

Read Only

<TextField label="ID" value={userId} readOnly />

Single Line

<TextField label="Title" singleLine />

Multi-Line

<TextField label="Bio" maxLines={5} />

Leading and Trailing Icons

<TextField
  label="Search"
  leadingIcon="search"
/>

<TextField
  label="Email"
  leadingIcon="email"
  trailingIcon="check-circle"
/>

Character Counter

var bio = ""

<TextField
  bind:value={bio}
  label="Bio"
  maxLength={150}
  helperText="{bio.length}/150 characters"
/>

Form Validation

var email = ""
var password = ""

val emailValid = email.contains("@")
val passwordValid = password.length >= 8
val formValid = emailValid && passwordValid

<Column gap={16}>
  <TextField
    bind:value={email}
    label="Email"
    type="email"
    error={!emailValid && email.isNotEmpty()}
    helperText={if (!emailValid && email.isNotEmpty()) "Invalid email" else ""}
  />

  <TextField
    bind:value={password}
    label="Password"
    type="password"
    error={!passwordValid && password.isNotEmpty()}
    helperText={if (!passwordValid && password.isNotEmpty()) "At least 8 characters" else ""}
  />

  <Button enabled={formValid} onClick={submit} text="Sign Up" />
</Column>

Checkbox and Switch

Use bind:checked for boolean inputs:

var agreedToTerms = false
var notificationsEnabled = true

<Row gap={8} verticalAlignment="center">
  <Checkbox bind:checked={agreedToTerms} />
  <Text>I agree to the terms</Text>
</Row>

<Row gap={8} verticalAlignment="center">
  <Text>Notifications</Text>
  <Spacer weight={1} />
  <Switch bind:checked={notificationsEnabled} />
</Row>

Common Patterns

Login Form

var email = ""
var password = ""

suspend fun login() {
  val response = $fetch.post(
    url = "https://api.example.com/login",
    body = mapOf("email" to email, "password" to password)
  )
  $navigate("/home")
}

<Column gap={16} p={20}>
  <Text fontSize={24} fontWeight="bold">Sign In</Text>

  <TextField
    bind:value={email}
    label="Email"
    type="email"
    leadingIcon="email"
  />

  <TextField
    bind:value={password}
    label="Password"
    type="password"
    leadingIcon="lock"
  />

  <Button onClick={login} text="Sign In" fillMaxWidth />
</Column>

Search Bar

var query = ""

<OutlinedTextField
  bind:value={query}
  placeholder="Search..."
  leadingIcon="search"
  trailingIcon={if (query.isNotEmpty()) "close" else null}
  onTrailingIconClick={() => query = ""}
  singleLine
/>

Settings Form

var username = "alice"
var bio = ""
var emailNotifications = true
var pushNotifications = false

<Column scrollable p={16} gap={20}>
  <Text fontSize={20} fontWeight="bold">Profile Settings</Text>

  <TextField
    bind:value={username}
    label="Username"
    leadingIcon="person"
  />

  <TextField
    bind:value={bio}
    label="Bio"
    maxLines={4}
    helperText="{bio.length}/150"
  />

  <Text fontSize={18} fontWeight="medium">Notifications</Text>

  <Row verticalAlignment="center">
    <Column weight={1}>
      <Text>Email Notifications</Text>
      <Text fontSize={12} color="secondary">Receive updates via email</Text>
    </Column>
    <Switch bind:checked={emailNotifications} />
  </Row>

  <Row verticalAlignment="center">
    <Column weight={1}>
      <Text>Push Notifications</Text>
      <Text fontSize={12} color="secondary">Receive push notifications</Text>
    </Column>
    <Switch bind:checked={pushNotifications} />
  </Row>

  <Button text="Save Changes" fillMaxWidth />
</Column>

Prop Reference

PropTypeDescription
labelStringField label
placeholderStringPlaceholder text
typeStringInput type (password, email, etc.)
helperTextStringHelper text below field
errorBooleanError state
disabledBooleanDisable input
readOnlyBooleanRead-only mode
singleLineBooleanSingle line input
maxLinesNumberMaximum lines for multi-line
maxLengthNumberMaximum character count
leadingIconStringIcon at start of field
trailingIconStringIcon at end of field

See Also