Inputs
TextField, Checkbox and Switch collect input. A `var` made with `$state` and `bind:` is the whole binding — there is no separate form library.
TextField
<TextField label="Name" /> Two-way binding
Use bind:value to sync the field with state:
var email = $state("")
<TextField bind:value={email} label="Email" />
<Text>You typed: {email}</Text> bind:value={email} is value = email, onValueChange = { email = it }.
Typing writes back into email; anything else that reads email redraws.
See it happen
Switch between these to see binding and validation added one step at a time, compiled live.
<TextField label="Email" />
A TextField with no bind:value. It renders and accepts typing, but there is no var for it to write into — nothing else on the screen can react to what you type.
Input types
The type prop picks the keyboard and, for password, hides what is typed:
<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" /> | Type | Effect |
|---|---|
password | Hides what is typed |
email | Email keyboard |
phone | Phone keyboard |
number | Numeric keyboard |
url | URL keyboard |
Placeholder
<TextField placeholder="Enter your name" /> Disabled
disabled is bare — no value means true. There is no enabled prop:
<TextField label="Username" disabled />
<TextField label="Username" disabled={isSaving} /> Autofocus
autofocus takes focus and raises the keyboard, both — a field that focuses
without the keyboard looks ready and is not:
<TextField label="Search" autofocus /> Validation
There is no error prop. A validation message is an ordinary conditional
element below the field, driven by a $derived value over the bound state:
var email = $state("")
val isValid = $derived(email.isEmpty() || email.contains("@"))
<Column class="gap-4">
<TextField bind:value={email} label="Email" type="email" />
@if (!isValid) {
<Text class="text-destructive text-sm">Enter a valid email</Text>
}
</Column> Form validation
var email = $state("")
var password = $state("")
val emailValid = $derived(email.isEmpty() || email.contains("@"))
val passwordValid = $derived(password.isEmpty() || password.length >= 8)
val formValid = $derived(email.isNotEmpty() && password.isNotEmpty() && emailValid && passwordValid)
<Column class="gap-16">
<Column class="gap-4">
<TextField bind:value={email} label="Email" type="email" />
@if (!emailValid) {
<Text class="text-destructive text-sm">Enter a valid email</Text>
}
</Column>
<Column class="gap-4">
<TextField bind:value={password} label="Password" type="password" />
@if (!passwordValid) {
<Text class="text-destructive text-sm">At least 8 characters</Text>
}
</Column>
<Button disabled={!formValid} onClick={submit}>Sign Up</Button>
</Column> Checkbox and Switch
Use bind:checked for boolean inputs:
var agreedToTerms = $state(false)
var notificationsEnabled = $state(true)
<Row class="gap-8 items-center">
<Checkbox bind:checked={agreedToTerms} />
<Text>I agree to the terms</Text>
</Row>
<Row class="gap-8 items-center justify-between">
<Text>Notifications</Text>
<Switch bind:checked={notificationsEnabled} />
</Row> Common patterns
Login form
var email = $state("")
var password = $state("")
suspend fun login() {
$fetch.post(
url = "https://api.example.com/login",
body = { email: email, password: password }
)
$navigate("/home")
}
<Column class="gap-16 p-20">
<Text class="text-2xl font-bold">Sign In</Text>
<TextField bind:value={email} label="Email" type="email" />
<TextField bind:value={password} label="Password" type="password" />
<Button onClick={login} class="w-full">Sign In</Button>
</Column> Search field
var query = $state("")
<TextField bind:value={query} placeholder="Search..." autofocus /> Settings form
var username = $state("alice")
var emailNotifications = $state(true)
var pushNotifications = $state(false)
<Column class="gap-20 p-16" scrollable>
<Text class="text-xl font-bold">Profile Settings</Text>
<TextField bind:value={username} label="Username" />
<Text class="text-lg font-medium">Notifications</Text>
<Row class="items-center justify-between">
<Column class="flex-1 gap-4">
<Text>Email notifications</Text>
<Text class="text-sm text-muted-foreground">Receive updates by email</Text>
</Column>
<Switch bind:checked={emailNotifications} />
</Row>
<Row class="items-center justify-between">
<Column class="flex-1 gap-4">
<Text>Push notifications</Text>
<Text class="text-sm text-muted-foreground">Receive push notifications</Text>
</Column>
<Switch bind:checked={pushNotifications} />
</Row>
<Button class="w-full">Save changes</Button>
</Column> Prop reference
| Prop | Type | Description |
|---|---|---|
label | String | Field label |
placeholder | String | Placeholder text |
type | String | password · email · phone · number · url |
disabled | Boolean | Bare means true |
autofocus | Boolean | Takes focus and raises the keyboard |
bind:value | String | Two-way binding to a $state var |