Pantrix UI

Text Field

A text field: one line of text or several that the user types, under a label, with help or an error's message below it, icons at either end, a button that clears it and a count of its characters.

import com.pantrixui.components.inputs.textfield.PantrixTextField

Examples

At its plainest

The least there is to write: a TextFieldState and a label. The state is created with rememberTextFieldState(), and what is typed is read from it; the line below shows name.text as it is typed.

The label sits above the box and stays there; a press on the label puts the caret in the box too. The box's border is what shows the focus. tagKey sets the field's test tag: TextField_<tagKey>.

val name = rememberTextFieldState()
PantrixTextField(
    state = name,
    modifier = Modifier.fillMaxWidth(),
    label = "Name",
    tagKey = "name"
)
PantrixText(
    text = if (name.text.isEmpty()) "Nothing typed yet" else "Typed: ${name.text}",
    color = PantrixTheme.colors.textSecondary
)

A search field without a label

A search field that has no label: contentDescription tells a screen reader its name. The placeholder says the same, so the screen reader does not read it as well: the field is not named twice while it is empty.

leadingIcon stands before the text. A slot does not size what is put in it; the icon takes the size in the field's style: PantrixTheme.style(TextFieldStyle).iconSize. While the field is not empty, showClearButton shows the button that empties it; the button leaves the caret in the field.

keyboardOptions makes the keyboard's action key "Search", and onKeyboardAction is called when that key is pressed. Type something and press the keyboard's "Search" key, or Enter: the app's snackbar says what was searched for.

val snackbars = LocalPantrixSnackbars.current
val scope = rememberCoroutineScope()
val query = rememberTextFieldState()
PantrixTextField(
    state = query,
    modifier = Modifier.fillMaxWidth(),
    placeholder = "Search orders",
    leadingIcon = {
        PantrixIcon(
            imageVector = PantrixIcons.Search,
            contentDescription = null,
            size = PantrixTheme.style(TextFieldStyle).iconSize
        )
    },
    showClearButton = true,
    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
    onKeyboardAction = { scope.launch { snackbars?.show(message = "Searched for: ${query.text}", tagKey = "searched") } },
    contentDescription = "Search orders",
    tagKey = "search"
)

Leading and trailing icons

leadingIcon stands before the text and trailingIcon after it: in a right-to-left layout they change places. Both take the secondary text colour; a text put in a slot, like the "kg" in the second field, is drawn in that colour too.

The clear button stands before trailingIcon and is no stop for Tab: a stop after every field would double the presses it takes to get through a form. A screen reader still reaches the button as a button.

val iconSize = PantrixTheme.style(TextFieldStyle).iconSize
PantrixTextField(
    state = rememberTextFieldState(),
    modifier = Modifier.fillMaxWidth(),
    label = "Username",
    leadingIcon = { PantrixIcon(imageVector = PantrixIcons.Person, contentDescription = null, size = iconSize) },
    tagKey = "username"
)
PantrixTextField(
    state = rememberTextFieldState("72"),
    modifier = Modifier.fillMaxWidth(),
    label = "Weight",
    trailingIcon = { PantrixText(text = "kg") },
    showClearButton = true,
    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
    tagKey = "weight"
)

Supporting text and error

supportingText stands under the box, in a small type. With error = true the border, the label, the supporting text and the caret turn to the danger role; the supporting text is then the message that says what to put right, and a screen reader announces it as the error. Type a few letters: as long as the password is shorter than eight characters the field is in error, and says how many characters are missing.

This field keeps its text as a String, not in a TextFieldState: value is shown, and onValueChange is asked for every change. The password is drawn as dots by visualTransformation. It is kept with remember and not with rememberSaveable: saved state may be written to disk. The password field that has a button to show its text is PantrixSecureTextField.

var password by remember { mutableStateOf("") }
val missing = 8 - password.length
val tooShort = password.isNotEmpty() && missing > 0
PantrixTextField(
    value = password,
    onValueChange = { password = it },
    modifier = Modifier.fillMaxWidth(),
    label = "Password",
    supportingText = if (tooShort) "Characters missing: $missing" else "At least 8 characters",
    error = tooShort,
    showClearButton = true,
    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
    visualTransformation = PasswordVisualTransformation(),
    contentType = ContentType.NewPassword,
    tagKey = "password"
)

A field whose text is held as a String

The PantrixTextField that takes a value and an onValueChange: the field shows value, and asks onValueChange for every change the user makes. A change the callback does not take is not shown: filtering is done there, and this overload takes no inputTransformation. The text asked for has to come back as value by the next frame, or the field shows the old text and the caret jumps: keep the text in state that the callback writes at once; where the text lives in a flow, pick the overload that takes a state.

This overload takes its lines through singleLine, minLines and maxLines, and is one line unless told otherwise. The field here is given an address of two lines, as if pasted from somewhere else: the field shows the line break as a space, and the second line is not cut off; the text that is held stays as it is.

var address by remember { mutableStateOf("Bağdat Cad. No: 12\nKat 3, Daire 8") }
PantrixTextField(
    value = address,
    onValueChange = { address = it },
    modifier = Modifier.fillMaxWidth(),
    label = "Address (one line)",
    supportingText = "The line break in the text shows as a space",
    showClearButton = true,
    tagKey = "address_line"
)

Lines and the action key in the String overload

The overload that keeps its text as a String takes its lines through singleLine, minLines and maxLines, not through lineLimits. The first field is two lines tall and grows to four; past that, the text scrolls inside it.

The keyboard's action key, too, is answered by keyboardActions and not by onKeyboardAction: it says what to do for the action keyboardOptions names. Type something in the second field and press the keyboard's action key: the app's snackbar says what was typed.

val snackbars = LocalPantrixSnackbars.current
val scope = rememberCoroutineScope()
var message by remember { mutableStateOf("") }
var nickname by remember { mutableStateOf("") }
PantrixTextField(
    value = message,
    onValueChange = { message = it },
    modifier = Modifier.fillMaxWidth(),
    label = "Message",
    singleLine = false,
    minLines = 2,
    maxLines = 4,
    tagKey = "message"
)
PantrixTextField(
    value = nickname,
    onValueChange = { nickname = it },
    modifier = Modifier.fillMaxWidth(),
    label = "Nickname",
    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
    keyboardActions = KeyboardActions(
        onDone = { scope.launch { snackbars?.show(message = "Nickname: $nickname") } }
    ),
    tagKey = "nickname"
)

Several lines and a character count

lineLimits = TextFieldLineLimits.MultiLine(minHeightInLines = 3) starts the box three lines tall; the box grows with its text, and Enter starts a new line. Tab does not type a tab, it moves the focus on: otherwise a keyboard would have no way forward out of the field.

With maxLength = 120 an edit that would pass the limit is turned down whole: typing stops at the limit, and a paste that does not fit is not put in at all. Characters are counted as String.length counts them, so an emoji can count as two. The count is written under the box, opposite the supporting text. Text set in code is not cut: where it is over the limit, the user can still make it shorter.

PantrixTextField(
    state = rememberTextFieldState(),
    modifier = Modifier.fillMaxWidth(),
    label = "Note for the courier",
    placeholder = "The doorbell does not work, please knock",
    supportingText = "Passed on to the courier",
    maxLength = 120,
    lineLimits = TextFieldLineLimits.MultiLine(minHeightInLines = 3),
    tagKey = "note"
)

A field given a height

A height given to the field is the whole field's: Modifier.height(160.dp). The label stays at the top, and the supporting text and the count at the bottom; the box fills the height they leave, and text that does not fit scrolls inside the box. No part of the field runs outside the height it is given.

Press the clear button: with its text gone, the box is still the same height.

PantrixTextField(
    state = rememberTextFieldState(INSTRUCTIONS),
    modifier = Modifier.fillMaxWidth().height(160.dp),
    label = "Delivery instructions",
    supportingText = "The field's height is fixed: the text scrolls inside the box",
    showClearButton = true,
    maxLength = 400,
    lineLimits = TextFieldLineLimits.MultiLine(),
    tagKey = "instructions"
)

private const val INSTRUCTIONS = "The building's street door is kept locked: ring the bell twice.\n" +
    "If nobody answers, leave it at the guard's booth.\n" +
    "If the parcel is heavy, take the lift: third floor.\n" +
    "There is a dog at the door: it barks but does not bite.\n" +
    "I am at home after six in the evening.\n" +
    "If you cannot reach me, call the caretaker."

Input and output transformations

inputTransformation sees every change the user makes before it enters the field: the one here turns a change down whole if it holds anything but digits. With maxLength, the transformation runs first and the limit applies to what it lets through.

outputTransformation says how the text is shown, without changing it: the number's spaces are only drawn. Type "5321234567": the box shows "532 123 45 67", while phone.text, which the line below reads, has no spaces.

val phone = rememberTextFieldState()
PantrixTextField(
    state = phone,
    modifier = Modifier.fillMaxWidth(),
    label = "Mobile phone",
    placeholder = "5XX XXX XX XX",
    supportingText = "Ten digits, without the leading zero",
    maxLength = 10,
    showCharacterCount = false,
    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone),
    inputTransformation = DigitsOnly,
    outputTransformation = PhoneSpaces,
    tagKey = "phone"
)
PantrixText(text = "Text held: \"${phone.text}\"", color = PantrixTheme.colors.textSecondary)

private val DigitsOnly = InputTransformation {
    if (!asCharSequence().all(Char::isDigit)) revertAllChanges()
}

private val PhoneSpaces = OutputTransformation {
    // From the end to the start: each space added later stands to the left of the ones added before it, and does not shift their places.
    listOf(8, 6, 3).forEach { at -> if (length > at) insert(at, " ") }
}

A field of numbers and the action key

A field a number is typed in, with something to do once it is typed, asks for its keyboard with numberKeyboardOptions: it gives the keyboard's type and its action key together. On Android the keyboard of numbers has an action key. An iPhone's number pad has none: ten digits and a key that takes one back. There a number typed in a field that asked for KeyboardType.Number cannot be sent; numberKeyboardOptions asks for the keyboard of numbers and punctuation in its place: it opens on its digits and has an action key.

Type a quantity and press the keyboard's action key, or Enter: the app's snackbar says the quantity. The keyboard on an iPhone also has signs and, a press away, letters: the field turns those down (inputTransformation).

val snackbars = LocalPantrixSnackbars.current
val scope = rememberCoroutineScope()
val quantity = rememberTextFieldState()
PantrixTextField(
    state = quantity,
    modifier = Modifier.fillMaxWidth(),
    label = "Quantity",
    maxLength = 4,
    showCharacterCount = false,
    keyboardOptions = numberKeyboardOptions(ImeAction.Done),
    onKeyboardAction = {
        scope.launch { snackbars?.show(message = "Quantity: ${quantity.text}", tagKey = "quantity") }
    },
    inputTransformation = DigitsOnly,
    tagKey = "quantity"
)

private val DigitsOnly = InputTransformation {
    if (!asCharSequence().all(Char::isDigit)) revertAllChanges()
}

Fields bound to a form

The PantrixTextField that takes a field gets its value, its error and its name from its field in the form. No tagKey is given: the field's id is the automation key (TextField_<id>). When the focus leaves an empty field, the rule's message takes the place of the supporting text; that is when the error first shows.

In a field given form, the keyboard's action key submits the form. In the first field the "Next" key moves the focus to the next field; in the second the action key submits the form through the "Save" button, and where a field is in error the focus goes to it. Leave both fields empty and press "Save".

val snackbars = LocalPantrixSnackbars.current
val scope = rememberCoroutineScope()
val form = rememberFormState()
PantrixTextField(
    field = form.field("full_name", initial = "", Rules.notBlank("Enter your name")),
    modifier = Modifier.fillMaxWidth(),
    form = form,
    label = "Full name",
    supportingText = "As it appears on your card",
    keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Words, imeAction = ImeAction.Next),
    contentType = ContentType.PersonFullName
)
PantrixTextField(
    field = form.field("email", initial = "", Rules.matches(EMAIL, "Enter a valid email address")),
    modifier = Modifier.fillMaxWidth(),
    form = form,
    label = "Email",
    placeholder = "name@example.com",
    showClearButton = true,
    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email),
    contentType = ContentType.EmailAddress
)
PantrixButton(
    text = "Save",
    onClick = {
        scope.launch { form.submit { snackbars?.show(message = "Details saved", tagKey = "profile_saved") } }
    },
    form = form,
    tagKey = "save_profile"
)

private val EMAIL = Regex("""\S+@\S+\.\S+""")

Disabled and read-only

enabled = false draws the field in the theme's disabled colours: the field takes no focus, cannot be typed into, and a screen reader is told it is disabled.

readOnly = true only stops the text being changed: the field can be focused, and its text selected and copied.

PantrixTextField(
    state = rememberTextFieldState("Pantrix A.Ş."),
    modifier = Modifier.fillMaxWidth(),
    enabled = false,
    label = "Disabled",
    supportingText = "Set by your organisation",
    tagKey = "company"
)
PantrixTextField(
    state = rememberTextFieldState("SIP-2026-0914"),
    modifier = Modifier.fillMaxWidth(),
    readOnly = true,
    label = "Read-only",
    supportingText = "Cannot be changed; can be selected and copied",
    tagKey = "order_no"
)

Its own colours

The colours given with colors are used, and those left out stay the theme's roles. The first field's colours are not written as fixed values but taken from the theme's success role: they are right in the dark theme too. The box's fill is the role's soft tone; the text and the icon are in the colour that reads on that tone; the border and the caret are the role's own colour. The label, the supporting text and the error colour are not given: they stay as the theme has them.

The second field is given only two colours, both the app's own purple: the border while the field has the focus, and the caret. Press the field, type something and select it: the selection's handles and its highlight take the caret's colour too.

val success = PantrixTheme.colors.success
PantrixTextField(
    state = rememberTextFieldState("PANTRIX10"),
    modifier = Modifier.fillMaxWidth(),
    label = "Discount code",
    supportingText = "Code valid: 10% off",
    leadingIcon = {
        PantrixIcon(
            imageVector = PantrixIcons.Check,
            contentDescription = null,
            size = PantrixTheme.style(TextFieldStyle).iconSize
        )
    },
    showClearButton = true,
    colors = TextFieldColors(
        container = success.container,
        border = success.color,
        hoveredBorder = success.color,
        focusedBorder = success.color,
        text = success.onContainer,
        icon = success.onContainer,
        cursor = success.color
    ),
    tagKey = "coupon"
)
val own = ownColors()
PantrixTextField(
    state = rememberTextFieldState(),
    modifier = Modifier.fillMaxWidth(),
    label = "Invitation code",
    placeholder = "PX-0000",
    colors = TextFieldColors(focusedBorder = own.color, cursor = own.color),
    tagKey = "own_colors"
)

Its own style

style is this call's own style: only the fields it sets change, and the rest comes from the theme's and the platform's style.

The first field is a fully round box: TextFieldStyle(shape = ShapeRole.Full). The second takes the same style but holds several lines: the curve of a fully round box would run into the first line of its text, so the field is drawn with ShapeRole.Large. In the third, height raises the box's least height; the one line stands in the middle of the box.

PantrixTextField(
    state = rememberTextFieldState(),
    modifier = Modifier.fillMaxWidth(),
    placeholder = "Search products",
    showClearButton = true,
    style = TextFieldStyle(shape = ShapeRole.Full),
    contentDescription = "Search products",
    tagKey = "pill"
)
PantrixTextField(
    state = rememberTextFieldState(),
    modifier = Modifier.fillMaxWidth(),
    label = "Comment",
    lineLimits = TextFieldLineLimits.MultiLine(minHeightInLines = 2),
    style = TextFieldStyle(shape = ShapeRole.Full),
    tagKey = "pill_comment"
)
PantrixTextField(
    state = rememberTextFieldState(),
    modifier = Modifier.fillMaxWidth(),
    label = "Title",
    style = TextFieldStyle(height = 56.dp),
    tagKey = "tall"
)

Watching the interactions from outside

interactionSource hoists the field's interactions: its focus, its hover and its presses. Without one, the field keeps a source of its own. The line below reads from the same source: press the field, then bring the pointer over the box.

It is the box that reports the pointer: a pointer over the label or over the supporting text does not count.

val interactions = remember { MutableInteractionSource() }
val focused by interactions.collectIsFocusedAsState()
val hovered by interactions.collectIsHoveredAsState()
PantrixTextField(
    state = rememberTextFieldState(),
    modifier = Modifier.fillMaxWidth(),
    label = "Subject",
    supportingText = "The pointer counts only while it is over the box",
    interactionSource = interactions,
    tagKey = "observed"
)
PantrixText(
    text = "Focused: ${if (focused) "yes" else "no"} · Pointer over it: ${if (hovered) "yes" else "no"}",
    color = PantrixTheme.colors.textSecondary
)

API

state

A text field: one line of text or several that the user types, under a label, with help or an error's message below it, icons at either end, a button that clears it and a count of its characters.

ParameterTypeDefaultWhat it is
stateTextFieldStatethe text and the selection. Create it with rememberTextFieldState(), and read what was typed from it: state.text where the text is used, or snapshotFlow { state.text.toString() } to follow it as it changes.
modifierModifierModifierthe modifier of the whole field: its label, its box and what is under it. fillMaxWidth() makes the box as wide as its parent. A height is the whole field's as well: the box fills what the label and the supporting text leave of it, with one line in its middle and several from its top, and a text too long for the box scrolls in it.
enabledBooleantruewhether the field can be typed into. One that cannot takes no focus, is drawn in the disabled colours and says so.
readOnlyBooleanfalsewhether the text can only be read: it can still be focused, selected and copied.
labelString?nullwhat the field is for, written above the box: the field's name for a screen reader.
placeholderString?nullshown in the box while it is empty. It is no label: it goes as soon as the user types.
supportingTextString?nullhelp under the box, in a smaller type. With error it is the message that says what to put right, which a screen reader announces as the error.
errorBooleanfalsewhether the text is wrong: the border, the label, the supporting text and the caret turn to the danger role, and a screen reader is told.
leadingIcon(@Composable () -> Unit)?nullwhat stands before the text, in the secondary text colour. A slot does not size what is put in it: PantrixIcon(size = PantrixTheme.style(TextFieldStyle).iconSize) is the platform's size.
trailingIcon(@Composable () -> Unit)?nullwhat stands after the text, and after the clear button.
showClearButtonBooleanfalseshows a button that empties the field, while the field can be typed into and is not empty. It leaves the caret in the field. It is no stop for Tab, since a stop after every field doubles the presses through a form, and a screen reader still reaches it as a button.
maxLengthInt?nullthe most characters the user can enter, counted as String.length counts them, so that an emoji can count as two. An edit that would pass the limit is turned down whole: typing stops at the limit, and a paste that does not fit is not put in. Text set in code is not cut, and the user can still make it shorter. It has to be positive.
showCharacterCountBooleantrueshows count/maxLength under the box while maxLength is set.
lineLimitsTextFieldLineLimitsTextFieldLineLimits.SingleLineone line, or TextFieldLineLimits.MultiLine, in which the box grows with its text and Enter starts a new line. Past its most lines the text scrolls, and a thin bar at the box's end shows how far. Its icons and its clear button stand at its first line. Tab moves the focus on from either: a field of several lines does not type a tab, which would leave a keyboard no way forward out of it.
keyboardOptionsKeyboardOptionsKeyboardOptions.Defaultthe keyboard's type, its capitals, its corrections and its action key. A field of numbers that has an action takes numberKeyboardOptions(imeAction): an iPhone's number pad has no key for one. A field of several lines that names an action loses the key for a new line on Android, whose keyboard shows the action in its place: such a field leaves the action unset, or is a PantrixTextArea.
onKeyboardActionKeyboardActionHandler?nullcalled for the keyboard's action key: the key a software keyboard shows for the action keyboardOptions names, and Enter on a hardware keyboard in a field of one line. It is called only for a named action, since the default one never reaches a handler. Null keeps what the platform does: Next moves the focus on, and Done closes the keyboard.
inputTransformationInputTransformation?nullwhat is done to the text as the user types it. With maxLength, it comes first and the limit applies to what it lets through.
outputTransformationOutputTransformation?nullhow the text is shown, without changing it: a phone number's spaces.
contentTypeContentType?nullwhat the field holds, for the platform to fill it in: ContentType.EmailAddress and the like.
styleTextFieldStyle?nullthis call's own style: over every enclosing one and under the density. Only the fields it sets change, on every platform.
colorsTextFieldColors?nullthis call's own colours, in place of the theme's roles: the ones it sets are used, and the rest stay the theme's. A disabled field is drawn in the theme's disabled colours whatever is given.
contentDescriptionString?nullwhat a screen reader says for the field: its name where it has no label, as a search field has none. A placeholder that says the same is then left to the eye, so that the field is not named twice while it is empty.
tagKeyString?nullthe field's automation key: the field is TextField_<tagKey>, and its clear button TextFieldClear_<tagKey>. The field reports that it took the focus and that it was cleared, and never its text.
interactionSourceMutableInteractionSource?nullthe field's interactions, hoisted: its focus, its hover and its presses. It keeps a source of its own when this is null.

value

A text field whose text is a String its caller holds: value is what it shows, and onValueChange is asked for every change the user makes.

ParameterTypeDefaultWhat it is
valueStringthe text the field shows.
onValueChange(String) -> Unitasked for each change the user makes, with the whole new text, and by the clear button with an empty one.
modifierModifierModifierthe modifier of the whole field: its label, its box and what is under it.
enabledBooleantruewhether the field can be typed into. One that cannot takes no focus, is drawn in the disabled colours and says so.
readOnlyBooleanfalsewhether the text can only be read: it can still be focused, selected and copied.
labelString?nullwhat the field is for, written above the box: the field's name for a screen reader.
placeholderString?nullshown in the box while it is empty.
supportingTextString?nullhelp under the box. With error it is the message that says what to put right, which a screen reader announces as the error.
errorBooleanfalsewhether the text is wrong: the border, the label, the supporting text and the caret turn to the danger role, and a screen reader is told.
leadingIcon(@Composable () -> Unit)?nullwhat stands before the text. A slot does not size what is put in it.
trailingIcon(@Composable () -> Unit)?nullwhat stands after the text, and after the clear button.
showClearButtonBooleanfalseshows a button that asks for an empty text, while the field can be typed into and is not empty. It leaves the caret in the field, and is no stop for Tab.
maxLengthInt?nullthe most characters the user can enter, counted as String.length counts them. An edit that would pass the limit is turned down whole, before onValueChange is asked. A value over the limit is shown as it is, and the user can still make it shorter. It has to be positive.
showCharacterCountBooleantrueshows count/maxLength under the box while maxLength is set.
singleLineBooleantruewhether the field holds one line, which scrolls sideways. It does unless told otherwise, as the TextFieldState overload does, where Foundation's own text field starts with several. A line break in value is then shown as a space, as that overload shows it, and value keeps it.
minLinesInt1the least lines the box is tall, in a field of several.
maxLinesIntif (singleLine) 1 else Int.MAX_VALUEthe most lines the box grows to before its text scrolls, in a field of several.
keyboardOptionsKeyboardOptionsKeyboardOptions.Defaultthe keyboard's type, its capitals, its corrections and its action key.
keyboardActionsKeyboardActionsKeyboardActions.Defaultwhat the keyboard's action key does, for the action keyboardOptions names. Without one for that action the platform does what it does: Next moves the focus on, and Done closes the keyboard.
visualTransformationVisualTransformationVisualTransformation.Nonehow the text is shown, without changing it: PasswordVisualTransformation() draws a dot for each character.
contentTypeContentType?nullwhat the field holds, for the platform to fill it in: ContentType.Password and the like.
styleTextFieldStyle?nullthis call's own style: over every enclosing one and under the density. Only the fields it sets change, on every platform.
colorsTextFieldColors?nullthis call's own colours, in place of the theme's roles: the ones it sets are used, and the rest stay the theme's. A disabled field is drawn in the theme's disabled colours whatever is given.
contentDescriptionString?nullwhat a screen reader says for the field: its name where it has no label. A placeholder that says the same is then left to the eye.
tagKeyString?nullthe field's automation key: the field is TextField_<tagKey>, and its clear button TextFieldClear_<tagKey>. The field reports that it took the focus and that it was cleared, and never its text.
interactionSourceMutableInteractionSource?nullthe field's interactions, hoisted: its focus, its hover and its presses. It keeps a source of its own when this is null.

field

A text field bound to a form's field: its text, its error and its name come from field.

ParameterTypeDefaultWhat it is
fieldFormField<String>the field the text field shows and changes.
modifierModifierModifierthe modifier of the whole field.
formFormState?nullgiven, the keyboard's action key submits the form through the button registered with it: the form is checked, and then submitted or the focus sent to its first field in error. An action of Next or Previous moves the focus as it always does. A field of one line that names no action gets ImeAction.Done, since the default action never reaches a handler and Enter would submit nothing. A field of several lines is left alone: there Enter starts a new line. An onKeyboardAction of the caller's own takes the place of the submit.
enabledBooleantruewhether the field can be typed into.
readOnlyBooleanfalsewhether the text can only be read.
labelString?nullwhat the field is for, written above the box: the field's name for a screen reader.
placeholderString?nullshown in the box while it is empty.
supportingTextString?nullhelp under the box, while the field has no error to show there.
leadingIcon(@Composable () -> Unit)?nullwhat stands before the text.
trailingIcon(@Composable () -> Unit)?nullwhat stands after the text, and after the clear button.
showClearButtonBooleanfalseshows a button that empties the field, while it can be typed into and is not empty.
maxLengthInt?nullthe most characters the user can enter. It has to be positive.
showCharacterCountBooleantrueshows count/maxLength under the box while maxLength is set.
lineLimitsTextFieldLineLimitsTextFieldLineLimits.SingleLineone line, or TextFieldLineLimits.MultiLine. Tab moves the focus on from either.
keyboardOptionsKeyboardOptionsKeyboardOptions.Defaultthe keyboard's type, its capitals, its corrections and its action key.
onKeyboardActionKeyboardActionHandler?nullcalled for the keyboard's action key, in place of the form's submit.
inputTransformationInputTransformation?nullwhat is done to the text as the user types it.
outputTransformationOutputTransformation?nullhow the text is shown, without changing it.
contentTypeContentType?nullwhat the field holds, for the platform to fill it in.
styleTextFieldStyle?nullthis call's own style: over every enclosing one and under the density. Only the fields it sets change, on every platform.
colorsTextFieldColors?nullthis call's own colours, in place of the theme's roles: the ones it sets are used, and the rest stay the theme's. A disabled field is drawn in the theme's disabled colours whatever is given.
contentDescriptionString?nullwhat a screen reader says for the field where it has no label.
interactionSourceMutableInteractionSource?nullthe field's interactions, hoisted. It keeps a source of its own when this is null.

TextFieldStyle

PantrixTextField's box: its shape, how tall and how wide it is at the least, the room around its text, its icons and its border.

ParameterTypeDefaultWhat it is
shapeShapeSpecShapeSpec.UnspecifiedThe box's shape. A fully round one, ShapeRole.Full, is a pill only while the field holds one line: a field of several lines takes ShapeRole.Large instead, since a capsule's curve runs into the first line of its text.
heightDpDp.UnspecifiedThe least the box is tall.
minWidthDpDp.UnspecifiedThe least the box is wide, which keeps an empty field from growing as the user types into it.
horizontalPaddingDpDp.UnspecifiedThe room between the box's edge and its text, where no icon is.
verticalPaddingDpDp.UnspecifiedThe room above the first line of the text and below the last.
iconPaddingDpDp.UnspecifiedThe room around an icon in its slot.
iconSizeDpDp.UnspecifiedThe size an icon in a slot should take. A slot does not size what is put in it.
borderWidthDpDp.UnspecifiedHow thick the box's border is.
focusedBorderWidthDpDp.UnspecifiedHow thick the border is while the field has the focus: the border is what shows it.
textRoleTypeRole?nullThe role of the type scale the text is typed in, and its placeholder set in: the body's, 16sp, on Android and iOS, as Material 3 and iOS type a field, and the label's, 14sp, on the desktop and the web, as a page under a pointer does. A select's box and the fields made on this style take it as well.

TextFieldColors

What PantrixTextField is painted with, for a field that does not take the theme's: a colour that is set is used, and one that is left out stays the theme's role. A product gives the colours it changes and no others:

ParameterTypeDefaultWhat it is
containerColorColor.UnspecifiedThe box's fill. The theme's surface.
borderColorColor.UnspecifiedThe box's border at rest. The theme's border.
hoveredBorderColorColor.UnspecifiedThe border under a pointer. The theme's borderHover, a notch stronger than border.
focusedBorderColorColor.UnspecifiedThe border while the field has the focus. The theme's primary.
errorColorColor.UnspecifiedWhat says the text is wrong: the border, the label, the message under the box, the caret, and the count once it is past its limit. The theme's danger.
textColorColor.UnspecifiedThe typed text. The theme's textPrimary.
labelColorColor.UnspecifiedThe label above the box. Left out, it is drawn as any text is where the field stands, since it is outside the box on whatever the field is put on: in textPrimary on the theme's own ground, and in the content colour of a fill.
placeholderColorColor.UnspecifiedThe placeholder. The theme's textSecondary.
supportingTextColorColor.UnspecifiedThe help under the box, and the count. The theme's textSecondary.
iconColorColor.UnspecifiedWhat an icon in a slot is drawn in unless it sets a tint of its own, and the clear button's cross. The theme's textSecondary.
cursorColorColor.UnspecifiedThe caret, and with it the handles and the highlight of a selection. The theme's primary.
scrollBarColorColor.UnspecifiedThe bar that shows how far the text of a box of several lines is scrolled. The theme's borderStrong: it is a mark beside the text, and the box's soft edge would not be seen there.

Accessibility