Skip to content

TextField

TextField is a basic input component in COUI for receiving text input from users, styled after ColorOS COUIEditText. By default it renders the ColorOS Settings dialog form: bare 16sp text over a hairline underline that turns into an expanding accent line when focused, with the label acting as a plain placeholder. Stroke-only rectangle and fully undecorated (card) forms, an opt-in floating label, error shake, character counter, clear button and password toggle are also available.

Import

kotlin
import io.github.suqi8.coui.kmp.basic.TextField
import io.github.suqi8.coui.kmp.basic.TextFieldMode

Basic Usage

The TextField component can be used to get user input:

kotlin
var text by remember { mutableStateOf("") }

TextField(
    value = text,
    onValueChange = { text = it },
    label = "Username"
)

INFO

This TextField component now also supports the latest state-based version. Please refer to the State-based documentation for details.

Input Types

TextField with Label (Placeholder)

By default (useLabelAsPlaceholder = true, matching ColorOS where every input uses the HintDisable styles) the label is a plain placeholder: it is visible while the field is empty and disappears once text is entered:

kotlin
var text by remember { mutableStateOf("") }

TextField(
    value = text,
    onValueChange = { text = it },
    label = "Email Address"
)

Floating Label

Set useLabelAsPlaceholder = false to enable the COUI HintAnim floating label: the label shrinks to 10sp and floats up as soon as the field is focused or filled (200ms, COUI move ease curve):

kotlin
var text by remember { mutableStateOf("") }

TextField(
    value = text,
    onValueChange = { text = it },
    label = "Please enter content",
    useLabelAsPlaceholder = false
)

Component States

Disabled State

kotlin
var text by remember { mutableStateOf("") }
TextField(
    value = text,
    onValueChange = { text = it },
    label = "Disabled Input Field",
    enabled = false
)

Read-Only State

kotlin
var text by remember { mutableStateOf("This is read-only content") }

TextField(
    value = text,
    onValueChange = { text = it },
    label = "Read-Only Input Field",
    readOnly = true
)

Background Modes

TextField supports the three COUIEditText background modes via backgroundMode:

  • TextFieldMode.Line (default): no fill; a 0.33dp hairline underline plus a 1dp accent line that expands from the start edge when focused — the form ColorOS Settings uses for dialog and bottom-sheet inputs
  • TextFieldMode.Rectangle: stroke-only rounded rectangle (10dp corners, no fill); 0.33dp hairline stroke, 1dp accent stroke when focused; text is bold by default
  • TextFieldMode.None: no background decoration at all — bare text, the form used inside white input cards (see InputView)
kotlin
var text by remember { mutableStateOf("") }

TextField(
    value = text,
    onValueChange = { text = it },
    label = "Rectangle style",
    backgroundMode = TextFieldMode.Rectangle
)

Undecorated (None) Mode

TextFieldMode.None removes all background decoration — useful when the surrounding container (such as a white input card) already provides the visual frame:

kotlin
var text by remember { mutableStateOf("") }

Card {
    TextField(
        value = text,
        onValueChange = { text = it },
        label = "Bare input",
        backgroundMode = TextFieldMode.None,
        modifier = Modifier.padding(horizontal = 16.dp)
    )
}

Focus Line Only

In Line mode, justShowFocusLine = true hides the resting underline and keeps only the focused expanding line, mirroring the ColorOS Settings card-preference input (COUIInputPreference couiJustShowFocusLine, default true on device). Place the field inside a Card for the full Settings look:

kotlin
var text by remember { mutableStateOf("") }

Card {
    TextField(
        value = text,
        onValueChange = { text = it },
        label = "Device name",
        justShowFocusLine = true,
        modifier = Modifier.padding(horizontal = 16.dp)
    )
}

Error State

Setting isError = true tints the border / underline and label with the error color and plays a one-shot horizontal shake animation:

kotlin
var text by remember { mutableStateOf("") }

TextField(
    value = text,
    onValueChange = { text = it },
    label = "Digits only",
    isError = text.isNotEmpty() && !text.all { it.isDigit() }
)

Character Counter

Setting maxCount shows a "count/max" counter at the end of the field and truncates input beyond the limit. The counter turns red once the limit is reached:

kotlin
var text by remember { mutableStateOf("") }

TextField(
    value = text,
    onValueChange = { text = it },
    label = "Max 10 characters",
    maxCount = 10
)

Clear Button

Setting showClearButton = true shows a clear (fast delete) button while the field is focused and not empty. Tapping it clears the whole text:

kotlin
var text by remember { mutableStateOf("") }

TextField(
    value = text,
    onValueChange = { text = it },
    label = "Quick delete",
    showClearButton = true
)

Password Toggle

Setting showPasswordToggle = true shows an eye button that switches the password visibility. While hidden, the text is masked with bullets:

kotlin
var password by remember { mutableStateOf("") }

TextField(
    value = password,
    onValueChange = { password = it },
    label = "Password",
    showPasswordToggle = true,
    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
)

Properties

TextField Properties

Property NameTypeDescriptionDefault ValueRequired
valueString or TextFieldValueText value of the input field-Yes
onValueChange(String) -> Unit or (TextFieldValue) -> UnitCallback when text changes-Yes
modifierModifierModifier applied to the input fieldModifierNo
backgroundModeTextFieldModeBackground decoration modeTextFieldMode.LineNo
insideMarginDpSizeInternal padding of input fieldTextFieldDefaults.insideMargin(backgroundMode)No
colorsTextFieldColorsColors used by the fieldTextFieldDefaults.textFieldColors()No
cornerRadiusDpCorner radius (Rectangle mode)TextFieldDefaults.CornerRadiusNo
labelStringLabel / placeholder text""No
useLabelAsPlaceholderBooleanPlain placeholder (true) or floating label (false)trueNo
justShowFocusLineBooleanLine mode: hide the resting underlinefalseNo
enabledBooleanWhether input field is enabledtrueNo
readOnlyBooleanWhether input field is read-onlyfalseNo
isErrorBooleanError state (red tint + shake)falseNo
maxCountInt?Max characters; shows a counternullNo
showClearButtonBooleanShow clear button when focusedfalseNo
showPasswordToggleBooleanShow password visibility togglefalseNo
textStyleTextStyleText styleTextFieldDefaults.textStyle(backgroundMode)No
keyboardOptionsKeyboardOptionsKeyboard optionsKeyboardOptions.DefaultNo
keyboardActionsKeyboardActionsKeyboard actionsKeyboardActions.DefaultNo
leadingIcon@Composable (() -> Unit)?Leading iconnullNo
trailingIcon@Composable (() -> Unit)?Trailing iconnullNo
singleLineBooleanSingle line inputfalseNo
maxLinesIntMaximum linesIf singleLine is true then 1, else Int.MAX_VALUENo
minLinesIntMinimum lines1No
visualTransformationVisualTransformationVisual transformationVisualTransformation.NoneNo
onTextLayout(TextLayoutResult) -> UnitText layout callback{}No
interactionSourceMutableInteractionSource?Interaction sourcenullNo
cursorBrushBrushCursor brushSolidColor(colors.borderColor)No

TextField (state-based) Properties

Property NameTypeDescriptionDefault ValueRequired
stateTextFieldStateState object holding text and selection-Yes
modifierModifierModifier applied to the input fieldModifierNo
backgroundModeTextFieldModeBackground decoration modeTextFieldMode.LineNo
insideMarginDpSizeInternal padding of input fieldTextFieldDefaults.insideMargin(backgroundMode)No
colorsTextFieldColorsColors used by the fieldTextFieldDefaults.textFieldColors()No
cornerRadiusDpCorner radius (Rectangle mode)TextFieldDefaults.CornerRadiusNo
labelStringLabel / placeholder text""No
useLabelAsPlaceholderBooleanPlain placeholder (true) or floating label (false)trueNo
justShowFocusLineBooleanLine mode: hide the resting underlinefalseNo
enabledBooleanWhether input field is enabledtrueNo
readOnlyBooleanWhether input field is read-onlyfalseNo
isErrorBooleanError state (red tint + shake)falseNo
maxCountInt?Max characters; shows a counternullNo
showClearButtonBooleanShow clear button when focusedfalseNo
showPasswordToggleBooleanShow password visibility togglefalseNo
inputTransformationInputTransformation?Input transformationnullNo
textStyleTextStyleText styleTextFieldDefaults.textStyle(backgroundMode)No
keyboardOptionsKeyboardOptionsKeyboard optionsKeyboardOptions.DefaultNo
onKeyboardActionKeyboardActionHandler?Keyboard action handlernullNo
lineLimitsTextFieldLineLimitsLine limitsTextFieldLineLimits.DefaultNo
leadingIcon@Composable (() -> Unit)?Leading iconnullNo
trailingIcon@Composable (() -> Unit)?Trailing iconnullNo
onTextLayoutDensity.(getResult: () -> TextLayoutResult?) -> UnitText layout callback with density receivernullNo
interactionSourceMutableInteractionSource?Interaction sourcenullNo
cursorBrushBrushCursor brushSolidColor(colors.borderColor)No
outputTransformationOutputTransformation?Output transformationnullNo
scrollStateScrollStateScroll staterememberScrollState()No

TextFieldDefaults Object

The TextFieldDefaults object provides default values for TextField components.

Constants

Constant NameTypeDescriptionDefault Value
CornerRadiusDpCorner radius of the field10.dp
InsideMarginDpSizeInternal padding in Rectangle modeDpSize(16.dp, 12.dp)
LineInsideMarginDpSizeInternal padding in Line modeDpSize(0.dp, 15.dp)
NoneInsideMarginDpSizeInternal padding in None modeDpSize(0.dp, 9.dp)
CounterFontSizeTextUnitFont size of the character counter10.sp

insideMargin() function

TextFieldDefaults.insideMargin(mode: TextFieldMode): DpSize returns the default internal padding for the given background mode.

textStyle() function

TextFieldDefaults.textStyle(mode: TextFieldMode): TextStyle returns the default COUI input text style: 16sp regular, bold in Rectangle mode.

textFieldColors() factory

Builds a [TextFieldColors] instance. Override any subset; unspecified params fall back to the COUI theme defaults.

ParameterTypeDefault
backgroundColorColorColor.Transparent (COUI rect mode is stroke-only)
labelColorColorCOUITheme.colorScheme.onSurfaceSecondary
borderColorColorCOUITheme.colorScheme.primary
unfocusedBorderColorColorCOUITheme.colorScheme.dividerLine
errorColorColorCOUITheme.colorScheme.error
counterColorColorCOUITheme.colorScheme.onSurfaceContainerHigh
iconColorColorCOUITheme.colorScheme.onSurfaceSecondary
disabledTextColorColorCOUITheme.colorScheme.disabledOnSurface

Advanced Usage

TextField with Icons

kotlin
var text by remember { mutableStateOf("") }

TextField(
    value = text,
    onValueChange = { text = it },
    label = "Search",
    leadingIcon = {
        Icon(
            imageVector = COUIIcons.Search,
            contentDescription = "Search Icon",
            modifier = Modifier.padding(horizontal = 12.dp)
        )
    }
)

Password Input Field

kotlin
var password by remember { mutableStateOf("") }
var passwordVisible by remember { mutableStateOf(false) }

TextField(
    value = password,
    onValueChange = { password = it },
    label = "Password",
    visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
    trailingIcon = {
        IconButton(
            onClick = { passwordVisible = !passwordVisible },
            modifier = Modifier.padding(end = 12.dp)
        ) {
            Icon(
                imageVector = COUIIcons.Rename,
                tint = if (passwordVisible) COUITheme.colorScheme.primary else COUITheme.colorScheme.onSurfaceSecondary,
                contentDescription = if (passwordVisible) "Hide Password" else "Show Password"
            )
        }
    }
)

Input Field with Validation

kotlin
var email by remember { mutableStateOf("") }
var isError by remember { mutableStateOf(false) }
val errorColor = Color.Red.copy(0.3f)
val emailPattern = remember { Regex("[a-zA-Z0-9._-]+@[a-z]+\\.+[a-z]+") }

Column {
    TextField(
        value = email,
        onValueChange = {
            email = it
            isError = email.isNotEmpty() && !emailPattern.matches(email)
        },
        label = "Email",
        colors = TextFieldDefaults.textFieldColors(
            labelColor = if (isError) errorColor else COUITheme.colorScheme.onSurfaceSecondary,
        ),
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email)
    )
    if (isError) {
        Text(
            text = "Please enter a valid email address",
            color = errorColor,
            style = COUITheme.textStyles.body2,
            modifier = Modifier.padding(start = 16.dp, top = 4.dp)
        )
    }
}

Custom Styles

kotlin
var text by remember { mutableStateOf("") }

TextField(
    value = text,
    onValueChange = { text = it },
    label = "Custom Input Field",
    cornerRadius = 8.dp,
    colors = TextFieldDefaults.textFieldColors(
        backgroundColor = COUITheme.colorScheme.primary.copy(alpha = 0.1f),
    ),
    textStyle = TextStyle(
        fontWeight = FontWeight.Medium,
        fontSize = 16.sp,
        color = COUITheme.colorScheme.primary
    )
)

Using TextFieldValue

When you need more fine-grained control over text selection and cursor position:

kotlin
var textFieldValue by remember { mutableStateOf(TextFieldValue("")) }

TextField(
    value = textFieldValue,
    onValueChange = { textFieldValue = it },
    label = "Advanced Input Control",
    // TextFieldValue provides control over text, selection range, and cursor position
)

Changelog

Released under the Apache-2.0 License