suiss/uim · React Native Component
Input
A single-line text field with focus, invalid and disabled states, in three heights.
Input is an open-source mobile component from suiss/uim for React Native and Expo, with SwiftUI and Jetpack Compose versions built from the same design tokens. The @esuiss/uim CLI copies its source into your project, so you own the code.


import { Input } from "@/components/ui/input"
export function InputExample() {
return (
<Input placeholder="Email" />
)
}import EsuissUI
import SwiftUI
struct InputExample: View {
var body: some View {
InputDemo(placeholder: "Email")
}
}
struct InputDemo: View {
let placeholder: LocalizedStringKey
var size: InputStyle.Size = .default
@State private var text: String
init(placeholder: LocalizedStringKey, size: InputStyle.Size = .default, initial: String = "") {
self.placeholder = placeholder
self.size = size
_text = State(initialValue: initial)
}
var body: some View {
TextField(placeholder, text: $text)
.textFieldStyle(.input(size: size))
}
}import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.esuiss.ui.components.Input
@Composable
fun InputExample() {
InputSingle(initial = "", placeholder = "Email")
}
@Composable
fun InputSingle(initial: String, placeholder: String, enabled: Boolean = true) {
var value by rememberSaveable { mutableStateOf(initial) }
Input(value, { value = it }, Modifier.fillMaxWidth(), enabled = enabled, placeholder = placeholder)
}Input wraps a TextInput through the field control primitive. It tracks its own focused, filled, invalid and disabled state and styles itself from that state: a focus border while editing, a danger border and tint when invalid, and muted colors when disabled.
It comes in three sizes: default (44 pt), md and sm. Use the default size for forms on a full screen, and the smaller sizes inside dense layouts such as toolbars or sheets.
Installation
$ pnpm dlx @esuiss/uim@latest add input$ pnpm dlx @esuiss/uim@latest add input --platform swift$ pnpm dlx @esuiss/uim@latest add input --platform kotlinManual
- Install the dependencies:
npx expo install cn-
Add the pieces it builds on: the primitives
components/primitives/input.tsx,components/primitives/use-render.tsx. -
Copy the source into
components/ui/input.tsx:
import { cn } from "cn"
import * as InputPrimitive from "@/components/primitives/input"
import { resolveClassName } from "@/components/primitives/use-render"
type InputSize = "sm" | "md" | "default"
function Input({
className,
size = "default",
...props
}: React.ComponentProps<typeof InputPrimitive.Root> & {
size?: InputSize
}) {
return (
<InputPrimitive.Root
className={(state) =>- Update the import paths to match your project.
- Install the dependencies:
npx expo install cn nativewind-
Add the pieces it builds on: the primitives
components/primitives/input.tsx,components/primitives/use-render.tsx. -
Copy the source into
components/ui/input.tsx:
import { cn } from "cn"
import { cssInterop } from "nativewind"
import { TextInput } from "react-native"
import * as InputPrimitive from "@/components/primitives/input"
import { resolveClassName } from "@/components/primitives/use-render"
declare module "react-native" {
interface TextInputProps {
cursorColorClassName?: string
selectionColorClassName?: string
}
}
cssInterop(TextInput, {
className: { target: "style", nativeStyleToProp: { textAlign: true } },
placeholderClassName: {- Update the import paths to match your project.
-
Add the pieces it builds on: the primitives
components/primitives/input.tsx,components/primitives/use-render.tsx. -
Copy the source into
components/ui/input.tsx:
import { StyleSheet } from "react-native"
import * as InputPrimitive from "@/components/primitives/input"
import { fontSize, radius, useTheme } from "@/components/ui/theme"
type InputSize = "sm" | "md" | "default"
function withAlpha(color: string, alpha: number) {
if (!color.startsWith("#") || color.length !== 7) return color
const r = Number.parseInt(color.slice(1, 3), 16)
const g = Number.parseInt(color.slice(3, 5), 16)
const b = Number.parseInt(color.slice(5, 7), 16)
return `rgba(${r}, ${g}, ${b}, ${alpha})`
}
const styles = StyleSheet.create({
root: {- Update the import paths to match your project.
-
Target iOS 17 or later.
-
Add the Swift package to your app:
.package(url: "https://github.com/phosphor-icons/swift", from: "2.1.0")- Copy the theme files every view uses:
import SwiftUI
import UIKit
struct RGBA: Sendable {
let red: CGFloat
let green: CGFloat
let blue: CGFloat
let alpha: CGFloat
init(_ hex: UInt32, _ alpha: CGFloat = 1) {
red = CGFloat((hex >> 16) & 0xFF) / 255
green = CGFloat((hex >> 8) & 0xFF) / 255
blue = CGFloat(hex & 0xFF) / 255
self.alpha = alpha
}import SwiftUI
extension Color {
public static let surface = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x000000))
public static let surfaceSecondary = Color.adaptive(light: .init(0xF5F5F7), dark: .init(0x161617))
public static let surfaceTertiary = Color.adaptive(light: .init(0xE8E8ED), dark: .init(0x2C2C2E))
public static let surfaceRaised = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x1D1D1F))
public static let label = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
public static let labelSecondary = Color.adaptive(light: .init(0x6E6E73), dark: .init(0xA1A1A6))
public static let labelTertiary = Color.adaptive(light: .init(0x86868B), dark: .init(0x86868B))
public static let labelQuaternary = Color.adaptive(light: .init(0xD2D2D7), dark: .init(0x424245))
public static let control = Color.adaptive(light: .init(0x000000, 0.04), dark: .init(0xFFFFFF, 0.1))
public static let controlPressed = Color.adaptive(light: .init(0x000000, 0.12), dark: .init(0xFFFFFF, 0.22))
public static let controlSelected = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
public static let controlDisabled = Color.adaptive(light: .init(0x000000, 0.03), dark: .init(0xFFFFFF, 0.05))
public static let onControlSelected = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x000000))- Copy the source and every file it uses:
import SwiftUI
public struct InputStyle: TextFieldStyle {
public enum Size: CaseIterable, Sendable {
case `default`, md, sm
var height: CGFloat {
switch self {
case .default: 44
case .md: 36
case .sm: 32
}
}
var horizontalPadding: CGFloat {
self == .sm ? 10 : 12import SwiftUI
public struct VariantButtonStyle: ButtonStyle {
public enum Variant: CaseIterable, Sendable {
case `default`, outline, secondary, tinted, ghost, plain, destructive, link
}
public enum Size: CaseIterable, Sendable {
case `default`, xs, sm, lg, xl, icon, iconXs, iconSm, iconLg, iconXl
var height: CGFloat {
switch self {
case .xs, .iconXs: 24
case .sm, .iconSm: 32
case .default, .icon: 36
case .lg, .iconLg: 44import SwiftUI
extension EnvironmentValues {
@Entry var buttonGroup: Axis? = nil
@Entry var buttonGroupSize: CGSize = .zero
}
enum ButtonGroupSpace: Hashable {
case group
}
struct ButtonGroupFrame {
let bounds: Anchor<CGRect>
let outlined: Bool
}import PhosphorSwift
import SwiftUI
extension Shadow {
static let control = Shadow(color: .black.opacity(0.08), radius: 0.5, y: 0.5)
static let thumb = Shadow(color: .black.opacity(0.12), radius: 1.5, y: 1)
}
struct SelectionIndicator<Mark: View>: View {
let checked: Bool
let pressed: Bool
let cornerRadius: CGFloat
let mark: Mark
@Environment(\.isEnabled) private var isEnabled
@Environment(\.isInvalid) private var invalidimport SwiftUI
extension EnvironmentValues {
@Entry public var isInvalid = false
@Entry var fieldInset = false
@Entry var fieldGroupInset = false
@Entry var fieldSetInset = false
}
extension View {
public func invalid(_ isInvalid: Bool = true) -> some View {
environment(\.isInvalid, isInvalid)
}
}
public enum FieldOrientation: CaseIterable, Sendable {import SwiftUI
extension EnvironmentValues {
@Entry public var hapticsEnabled = true
}
extension View {
public func haptics(_ enabled: Bool) -> some View {
environment(\.hapticsEnabled, enabled)
}
func hapticFeedback<Trigger: Equatable>(_ feedback: SensoryFeedback, trigger: Trigger) -> some View {
modifier(HapticFeedbackModifier(feedback: feedback, trigger: trigger) { _, _ in true })
}
func hapticFeedback<Trigger: Equatable>(import SwiftUI
public struct Separator: View {
let orientation: Axis
public init(_ orientation: Axis = .horizontal) {
self.orientation = orientation
}
public var body: some View {
Rectangle()
.fill(Color.separator)
.frame(width: orientation == .vertical ? 1 : nil, height: orientation == .horizontal ? 1 : nil)
.frame(maxWidth: orientation == .horizontal ? .infinity : nil, maxHeight: orientation == .vertical ? .infinity : nil)
.accessibilityHidden(true)
}import SwiftUI
public enum TextVariant: CaseIterable, Sendable {
case `default`, h1, h2, h3, h4, p, blockquote, code, lead, large, small, muted
var style: TextStyle {
switch self {
case .h1: .xl3
case .h2: .xl2
case .h3: .xl
case .h4, .lead, .large: .lg
case .code, .small, .muted: .sm
default: .base
}
}- Add the files to your app target.
- Set
minSdkto 24 or higher and add the dependencies:
implementation(platform("androidx.compose:compose-bom:2026.09.00"))
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.ui:ui")- Copy the theme files every view uses:
package com.esuiss.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.dropShadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.shadow.Shadow as DropShadow
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.LineHeightStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffsetpackage com.esuiss.ui.theme
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Immutable
data class Colors(
val surface: Color,
val surfaceSecondary: Color,
val surfaceTertiary: Color,
val surfaceRaised: Color,
val label: Color,
val labelSecondary: Color,
val labelTertiary: Color,
val labelQuaternary: Color,- Copy the source and every file it uses:
package com.esuiss.ui.components
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProviderpackage com.esuiss.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Immutablepackage com.esuiss.ui.components
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
val LocalContentStyle = compositionLocalOf { TextStyle.Default }
val LocalContentColor = compositionLocalOf { Color.Unspecified }
val LocalIconSize = compositionLocalOf { 20.dp }package com.esuiss.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.LayoutScopeMarker
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextpackage com.esuiss.ui.components
import android.provider.Settings
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Sizepackage com.esuiss.ui.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.esuiss.ui.theme.EsuissTheme
import com.esuiss.ui.theme.Typography
@Composablepackage com.esuiss.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.esuiss.ui.theme.EsuissTheme
@Composable
fun Separator(- Update the
com.esuiss.uipackage names to match your project.
Usage
import { Input } from "@/components/ui/input"<Input placeholder="Email" />Pass any TextInput prop, such as placeholder, secureTextEntry, keyboardType or inputMode, to get the matching field type.
The value works controlled with value and onValueChange(value, details), or uncontrolled with defaultValue. Inside a Field, the input picks up the field's label, description, invalid and disabled state, so you usually set invalid on the Field instead of on the input.
className can be a function that receives the state (focused, invalid, disabled, filled, …) and returns classes.
Examples
Types
Text, email, password, number and search fields set through standard TextInput props.


import { View } from "react-native"
import { Input } from "@/components/ui/input"
import { Text } from "@/components/ui/text"
export function InputTypesExample() {
return (
<View className="gap-3">
<Input placeholder="Text" />
<Input
placeholder="Email"
keyboardType="email-address"
autoCapitalize="none"
/>
<Input placeholder="Password" secureTextEntry />
<Input placeholder="Number" keyboardType="number-pad" />
<Input placeholder="Search" inputMode="search" />
</View>
)
}import EsuissUI
import SwiftUI
struct InputTypesDemo: View {
@State private var text = ""
@State private var email = ""
@State private var password = ""
@State private var number = ""
@State private var search = ""
var body: some View {
VStack(alignment: .leading, spacing: 12) {
TextField("Text", text: $text)
.textFieldStyle(.input)
TextField("Email", text: $email)
.textFieldStyle(.input)
.keyboardType(.emailAddress)
.textInputAutocapitalization(.never)
SecureField("Password", text: $password)
.textFieldStyle(.input)
TextField("Number", text: $number)
.textFieldStyle(.input)
.keyboardType(.numberPad)
TextField("Search", text: $search)
.textFieldStyle(.input)
.submitLabel(.search)
}
}
}import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.Input
import com.esuiss.ui.components.Text
val InputEmailKeyboard = KeyboardOptions(
capitalization = KeyboardCapitalization.None,
keyboardType = KeyboardType.Email,
)
@Composable
fun InputTypes() {
var text by rememberSaveable { mutableStateOf("") }
var email by rememberSaveable { mutableStateOf("") }
var password by rememberSaveable { mutableStateOf("") }
var number by rememberSaveable { mutableStateOf("") }
var search by rememberSaveable { mutableStateOf("") }
Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
Input(text, { text = it }, placeholder = "Text")
Input(email, { email = it }, placeholder = "Email", keyboardOptions = InputEmailKeyboard)
Input(
password,
{ password = it },
placeholder = "Password",
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
visualTransformation = PasswordVisualTransformation(),
)
Input(number, { number = it }, placeholder = "Number", keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number))
Input(search, { search = it }, placeholder = "Search", keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search))
}
}Sizes
The sm, md and default sizes.


import { View } from "react-native"
import { Input } from "@/components/ui/input"
export function InputSizesExample() {
return (
<View className="gap-3">
<Input size="sm" placeholder="Small" />
<Input size="md" placeholder="Medium" />
<Input placeholder="Default" />
</View>
)
}import EsuissUI
import SwiftUI
struct InputSizesExample: View {
var body: some View {
VStack(alignment: .leading, spacing: 12) {
InputDemo(placeholder: "Small", size: .sm)
InputDemo(placeholder: "Medium", size: .md)
InputDemo(placeholder: "Default")
}
}
}
struct InputDemo: View {
let placeholder: LocalizedStringKey
var size: InputStyle.Size = .default
@State private var text: String
init(placeholder: LocalizedStringKey, size: InputStyle.Size = .default, initial: String = "") {
self.placeholder = placeholder
self.size = size
_text = State(initialValue: initial)
}
var body: some View {
TextField(placeholder, text: $text)
.textFieldStyle(.input(size: size))
}
}import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.Input
import com.esuiss.ui.components.InputSize
@Composable
fun InputSizes() {
var small by rememberSaveable { mutableStateOf("") }
var medium by rememberSaveable { mutableStateOf("") }
var default by rememberSaveable { mutableStateOf("") }
Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
Input(small, { small = it }, size = InputSize.Sm, placeholder = "Small")
Input(medium, { medium = it }, size = InputSize.Md, placeholder = "Medium")
Input(default, { default = it }, placeholder = "Default")
}
}With value
An input that starts with a defaultValue.


import { Input } from "@/components/ui/input"
export function InputWithValueExample() {
return (
<Input placeholder="Email" defaultValue="[email protected]" />
)
}import EsuissUI
import SwiftUI
struct InputWithValueExample: View {
var body: some View {
InputDemo(placeholder: "Email", initial: "[email protected]")
}
}
struct InputDemo: View {
let placeholder: LocalizedStringKey
var size: InputStyle.Size = .default
@State private var text: String
init(placeholder: LocalizedStringKey, size: InputStyle.Size = .default, initial: String = "") {
self.placeholder = placeholder
self.size = size
_text = State(initialValue: initial)
}
var body: some View {
TextField(placeholder, text: $text)
.textFieldStyle(.input(size: size))
}
}import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.esuiss.ui.components.Input
@Composable
fun InputWithValueExample() {
InputSingle(initial = "[email protected]", placeholder = "Email")
}
@Composable
fun InputSingle(initial: String, placeholder: String, enabled: Boolean = true) {
var value by rememberSaveable { mutableStateOf(initial) }
Input(value, { value = it }, Modifier.fillMaxWidth(), enabled = enabled, placeholder = placeholder)
}With label
An input inside a Field with a label and description.


import { Field, FieldDescription, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
export function InputWithLabelExample() {
return (
<Field>
<FieldLabel>Email</FieldLabel>
<Input
placeholder="[email protected]"
keyboardType="email-address"
autoCapitalize="none"
/>
<FieldDescription>We will never share your email.</FieldDescription>
</Field>
)
}import EsuissUI
import SwiftUI
struct InputWithLabelDemo: View {
@State private var email = ""
var body: some View {
Field {
FieldLabel("Email")
.accessibilityHidden(true)
TextField(text: $email, prompt: Text(verbatim: "[email protected]")) {
Text("Email")
}
.textFieldStyle(.input)
.keyboardType(.emailAddress)
.textInputAutocapitalization(.never)
.accessibilityLabel(Text("Email"))
FieldDescription("We will never share your email.")
}
}
}import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import com.esuiss.ui.components.Field
import com.esuiss.ui.components.FieldDescription
import com.esuiss.ui.components.FieldLabel
import com.esuiss.ui.components.Input
val InputEmailKeyboard = KeyboardOptions(
capitalization = KeyboardCapitalization.None,
keyboardType = KeyboardType.Email,
)
@Composable
fun InputWithLabel() {
var value by rememberSaveable { mutableStateOf("") }
Field {
FieldLabel("Email")
Input(value, { value = it }, placeholder = "[email protected]", keyboardOptions = InputEmailKeyboard)
FieldDescription("We will never share your email.")
}
}Invalid
An input inside an invalid Field with an error message.


import { Field, FieldError, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
export function InputInvalidExample() {
return (
<Field invalid>
<FieldLabel>Username</FieldLabel>
<Input defaultValue="x" />
<FieldError>Username must be at least 3 characters.</FieldError>
</Field>
)
}import EsuissUI
import SwiftUI
struct InputInvalidDemo: View {
@State private var username = "x"
var body: some View {
Field(invalid: true) {
FieldLabel("Username")
.accessibilityHidden(true)
TextField("", text: $username)
.textFieldStyle(.input)
.accessibilityLabel(Text("Username"))
FieldError("Username must be at least 3 characters.")
}
}
}import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import com.esuiss.ui.components.Field
import com.esuiss.ui.components.FieldError
import com.esuiss.ui.components.FieldLabel
import com.esuiss.ui.components.Input
@Composable
fun InputInvalid() {
var value by rememberSaveable { mutableStateOf("x") }
Field(invalid = true) {
FieldLabel("Username")
Input(value, { value = it })
FieldError("Username must be at least 3 characters.")
}
}Controlled
A controlled input that mirrors its value below.


import * as React from "react"
import { View } from "react-native"
import { Input } from "@/components/ui/input"
import { Text } from "@/components/ui/text"
function ControlledDemo() {
const [value, setValue] = React.useState("")
return (
<View className="gap-2">
<Input
placeholder="Lowercase only"
autoCapitalize="none"
value={value}
onValueChange={(next) => setValue(next.toLowerCase())}
/>
<Text variant="muted">Value: {value}</Text>
</View>
)
}
export function InputControlledExample() {
return (
<ControlledDemo />
)
}import EsuissUI
import SwiftUI
struct InputControlledDemo: View {
@State private var value = ""
var body: some View {
VStack(alignment: .leading, spacing: 8) {
TextField(
"Lowercase only",
text: Binding(get: { value }, set: { value = $0.lowercased() })
)
.textFieldStyle(.input)
.textInputAutocapitalization(.never)
Text(verbatim: "Value: \(value)")
.textVariant(.muted)
}
}
}import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.Input
import com.esuiss.ui.components.Text
import com.esuiss.ui.components.TextVariant
@Composable
fun InputControlled() {
var value by rememberSaveable { mutableStateOf("") }
Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Input(
value,
{ value = it.lowercase() },
placeholder = "Lowercase only",
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.None),
)
Text("Value: $value", variant = TextVariant.Muted)
}
}Disabled
A disabled input.


import { Input } from "@/components/ui/input"
export function InputDisabledExample() {
return (
<Input placeholder="Disabled" />
)
}import EsuissUI
import SwiftUI
struct InputDisabledExample: View {
var body: some View {
InputDemo(placeholder: "Disabled")
.disabled(true)
}
}
struct InputDemo: View {
let placeholder: LocalizedStringKey
var size: InputStyle.Size = .default
@State private var text: String
init(placeholder: LocalizedStringKey, size: InputStyle.Size = .default, initial: String = "") {
self.placeholder = placeholder
self.size = size
_text = State(initialValue: initial)
}
var body: some View {
TextField(placeholder, text: $text)
.textFieldStyle(.input(size: size))
}
}import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.esuiss.ui.components.Input
@Composable
fun InputDisabledExample() {
InputSingle(initial = "", placeholder = "Disabled", enabled = false)
}
@Composable
fun InputSingle(initial: String, placeholder: String, enabled: Boolean = true) {
var value by rememberSaveable { mutableStateOf(initial) }
Input(value, { value = it }, Modifier.fillMaxWidth(), enabled = enabled, placeholder = placeholder)
}API Reference
Input
React.ComponentProps<typeof InputPrimitive.Root>- About
- Height and text size:
default,mdorsm. - Type
InputSize- Default
- "default"
Accessibility
- Inside a
Field, the input is linked to the field label throughaccessibilityLabelledByand gets the label asaccessibilityLabeland the description asaccessibilityHint. disabledis set inaccessibilityStateand makes the input non-editable.- Outside a
Field, give the input anaccessibilityLabel; a placeholder alone is not a label.
Notes
- Uses the
bg-control,border-focus,border-dangerandbg-control-disabledtokens. - Placeholder, cursor and selection colors come from the
label-secondaryandaccenttokens. - Light and dark colors follow the system appearance.