androidx.compose.foundation.text.input

Interfaces

InputTransformation

A function that is ran after every change made to a TextFieldState by user input and can change or reject that input.

Cmn
KeyboardActionHandler
Cmn
OutputTransformation

A function (transformOutput) that transforms the text presented to a user by a BasicTextField.

Cmn
TextFieldBuffer.ChangeList

The ordered list of non-overlapping and discontinuous changes performed on a TextFieldBuffer during the current edit or filter operation.

Cmn
TextFieldDecorator

Composable interface that allows to add decorations around text field, such as icon, placeholder, helper messages or similar, and automatically increase the hit target area of the text field.

Cmn
TextFieldLineLimits

Values that specify the text wrapping, scrolling, and height measurement behavior for text fields.

Cmn
TextFieldTextStyles

Provides access to the styles applied to the text within a TextFieldState.

Cmn

Classes

ExpandPolicy

Defines how a TrackedRange expands when text is inserted exactly at its boundaries.

Cmn
TextFieldBuffer

A text buffer that can be edited, similar to StringBuilder.

Cmn
TextFieldLineLimits.MultiLine

The text field will be at least minHeightInLines tall, if the text overflows it will wrap, and if the text ends up being more than one line the field will grow until it is maxHeightInLines tall and then start scrolling vertically.

Cmn
TextFieldState

Manages editable text, selection, and cursor state for a text field.

Cmn
TextObfuscationMode

Defines how the text will be obscured in secure text fields.

Cmn
TrackedRange

A style applied on the text that is tracked by TextFieldBuffer, returned by TextFieldBuffer.addStyle.

Cmn
UndoState

Controls the undo and redo history for a TextFieldState.

Cmn

Objects

InputTransformation.Companion
Cmn
TextFieldLineLimits.SingleLine

The text field is always a single line tall, ignores newlines in the text, and scrolls horizontally when the text overflows.

Cmn
TextFieldState.Saver

Saves and restores a TextFieldState for rememberSaveable.

Cmn

Composables

rememberTextFieldState

Create and remember a TextFieldState.

Cmn

Extension functions summary

InputTransformation

Returns a InputTransformation that forces all text to be uppercase.

Cmn
InputTransformation
InputTransformation.byValue(
    transformation: (current: CharSequence, proposed: CharSequence) -> CharSequence
)

Creates an InputTransformation from a function that accepts both the current and proposed TextFieldCharSequence and returns the TextFieldCharSequence to use for the field.

Cmn
Unit

Deletes all the text in the state.

Cmn
Unit
TextFieldBuffer.delete(start: Int, end: Int)

Delete the text between start (inclusive) and end (exclusive).

Cmn
inline Unit
TextFieldBuffer.ChangeList.forEachChange(
    block: (range: TextRange, originalRange: TextRange) -> Unit
)

Iterates over all changes in this ChangeList in order of their appearance in the text (from the lowest character offset to the highest).

Cmn
inline Unit
TextFieldBuffer.ChangeList.forEachChangeReversed(
    block: (range: TextRange, originalRange: TextRange) -> Unit
)

Iterates over all changes in this ChangeList in reverse order of their appearance in the text (from the highest character offset down to the lowest).

Cmn
Unit
TextFieldBuffer.insert(index: Int, text: String)

Insert text at the given index in this value.

Cmn
InputTransformation

This function is deprecated. Use maxLengthTrim to truncate excess characters or maxLengthReject to reject input exceeding maxLength.

Cmn
InputTransformation

Rejects input changes that would cause the total length of the text field to exceed maxLength characters.

Cmn
InputTransformation

Limits the total length of the text field to maxLength characters by truncating inserted characters.

Cmn
Unit

Places the cursor at the end of the text.

Cmn
Unit

Places the selection around all the text.

Cmn
Unit

Sets the text in this TextFieldState to text, replacing any text that was previously there, and places the cursor at the end of the new text.

Cmn
Unit

Sets the text in this TextFieldState to text, replacing any text that was previously there, and selects all the text.

Cmn
InputTransformation

Creates a filter chain that will run next after this.

Cmn
TextFieldBuffer

Creates a temporary, mutable TextFieldBuffer representing the current state of this TextFieldState.

Cmn

Extension functions

InputTransformation.allCaps

fun InputTransformation.allCaps(locale: Locale): InputTransformation

Returns a InputTransformation that forces all text to be uppercase.

This transformation automatically configures the keyboard to capitalize all characters.

Parameters
locale: Locale

The Locale in which to perform the case conversion.

InputTransformation.byValue

fun InputTransformation.byValue(
    transformation: (current: CharSequence, proposed: CharSequence) -> CharSequence
): InputTransformation

Creates an InputTransformation from a function that accepts both the current and proposed TextFieldCharSequence and returns the TextFieldCharSequence to use for the field.

transformation can return either current, proposed, or a completely different value.

The selection or cursor will be updated automatically. For more control of selection implement InputTransformation directly.

import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.InputTransformation
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.byValue
import androidx.compose.material3.Text
import androidx.compose.runtime.remember
import androidx.compose.ui.unit.sp

val state = remember { TextFieldState() }
BasicTextField(
    state,
    // Reject whitespace.
    inputTransformation =
        InputTransformation.byValue { current, proposed ->
            if ("""\s""".toRegex() in proposed) current else proposed
        },
)
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.InputTransformation
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.byValue
import androidx.compose.material3.Text
import androidx.compose.runtime.remember
import androidx.compose.ui.unit.sp

val state = remember { TextFieldState() }
BasicTextField(
    state,
    // Convert tabs to spaces.
    inputTransformation =
        InputTransformation.byValue { _, proposed ->
            proposed.replace("""\t""".toRegex(), "  ")
        },
)

TextFieldState.clearText

fun TextFieldState.clearText(): Unit

Deletes all the text in the state.

To perform more complicated edits on the text, call TextFieldState.edit. This function is equivalent to calling:

edit {
delete(0, length)
placeCursorAtEnd()
}

TextFieldBuffer.delete

fun TextFieldBuffer.delete(start: Int, end: Int): Unit

Delete the text between start (inclusive) and end (exclusive). Pass 0 as start and TextFieldBuffer.length as end to delete everything in this buffer.

Parameters
start: Int

The character offset of the first character to delete.

end: Int

The character offset of the first character after the deleted range.

See also
replace
append
insert

TextFieldBuffer.ChangeList.forEachChange

inline fun TextFieldBuffer.ChangeList.forEachChange(
    block: (range: TextRange, originalRange: TextRange) -> Unit
): Unit

Iterates over all changes in this ChangeList in order of their appearance in the text (from the lowest character offset to the highest).

In each iteration, block receives range (the range of the change in the updated TextFieldBuffer) and originalRange (the corresponding range in the original text buffer before any changes).

Avoid modifying text before the current range. Changes are ordered by character offset, so modifying text earlier in the buffer shifts the current change to a higher index in the ChangeList. Because this function iterates forward by index, it will mistakenly visit the current change again and skip the newly inserted change.

For example, assume ChangeList initially has one change at 5..8. If block inserts text at index 0, a new change at 0..2 is placed at index 0, and the original change is shifted to index 1. As the loop advances to index 1, it visits the original change a second time.

import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.forEachChange
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.Text
import androidx.compose.runtime.remember
import androidx.compose.ui.text.substring

// Print a log message every time the text is changed.
BasicTextField(
    state = rememberTextFieldState(),
    inputTransformation = {
        changes.forEachChange { sourceRange, replacedLength ->
            val newString = asCharSequence().substring(sourceRange)
            println("""$replacedLength characters were replaced with "$newString"""")
        }
    },
)
Parameters
block: (range: TextRange, originalRange: TextRange) -> Unit

The block to be invoked for each change.

TextFieldBuffer.ChangeList.forEachChangeReversed

inline fun TextFieldBuffer.ChangeList.forEachChangeReversed(
    block: (range: TextRange, originalRange: TextRange) -> Unit
): Unit

Iterates over all changes in this ChangeList in reverse order of their appearance in the text (from the highest character offset down to the lowest).

In each iteration, block receives range (the range of the change in the updated TextFieldBuffer) and originalRange (the corresponding range in the original text buffer before any changes).

Unlike forEachChange, you may safely make non-overlapping changes after the current range without triggering repeated iterations.

Because iteration proceeds backward by index, any new changes made after the current range are assigned greater indices in the ChangeList and fall beyond the current loop index, so they are cleanly skipped.

For example, suppose ChangeList has changes at index 0 (0..2) and index 1 (10..12). This function visits index 1 first. If you then insert text at index 15, the new change is appended at index 2. Since the loop next decrements to index 0, the new change at index 2 is skipped.

import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.delete
import androidx.compose.foundation.text.input.forEachChange
import androidx.compose.foundation.text.input.forEachChangeReversed
import androidx.compose.foundation.text.input.insert
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.Text
import androidx.compose.runtime.remember

// Make a text field behave in "insert mode" – inserted text overwrites the text ahead of it
// instead of being inserted.
BasicTextField(
    state = rememberTextFieldState(),
    inputTransformation = {
        changes.forEachChangeReversed { range, originalRange ->
            if (!range.collapsed && originalRange.collapsed) {
                // New text was inserted, delete the text ahead of it.
                delete(
                    range.end.coerceAtMost(length),
                    (range.end + range.length).coerceAtMost(length),
                )
            }
        }
    },
)
Parameters
block: (range: TextRange, originalRange: TextRange) -> Unit

The block to be invoked for each change.

See also
forEachChange

TextFieldBuffer.insert

fun TextFieldBuffer.insert(index: Int, text: String): Unit

Insert text at the given index in this value. Pass 0 to insert text at the beginning of this buffer, and pass TextFieldBuffer.length to insert text at the end of this buffer.

This is equivalent to calling replace(index, index, text).

Parameters
index: Int

The character offset at which to insert text.

text: String

The text to insert.

See also
replace
append
delete

InputTransformation.maxLength

fun InputTransformation.maxLength(maxLength: Int): InputTransformation

Returns InputTransformation that rejects input which causes the total length of the text field to be more than maxLength characters.

This transformation also sets the maximum text length for accessibility services. When using an OutputTransformation that adds decorating characters, the announced maximum length may need to be adjusted to account for those characters. See the linked sample for an example of a custom maxLength filter that does this.

import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.InputTransformation
import androidx.compose.foundation.text.input.OutputTransformation
import androidx.compose.foundation.text.input.TextFieldBuffer
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.delete
import androidx.compose.foundation.text.input.insert
import androidx.compose.material3.Text
import androidx.compose.runtime.remember
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.semantics.maxTextLength

val state = remember { TextFieldState() }
BasicTextField(
    state,
    inputTransformation =
        object : InputTransformation {
            override fun SemanticsPropertyReceiver.applySemantics() {
                // The output transformation formats "1234567890" to "(123) 456-7890",
                // which is 14 characters long. We set the accessibility maximum length
                // to 14 so screen readers announce the correct limit.
                maxTextLength = 14
            }

            override fun TextFieldBuffer.transformInput() {
                if (length > 10) {
                    delete(10, length)
                }
            }
        },
    outputTransformation =
        OutputTransformation {
            if (length > 0) insert(0, "(")
            if (length > 4) insert(4, ") ")
            if (length > 9) insert(9, "-")
        },
)

InputTransformation.maxLengthReject

fun InputTransformation.maxLengthReject(maxLength: Int): InputTransformation

Rejects input changes that would cause the total length of the text field to exceed maxLength characters.

When text is inserted (e.g. by typing or pasting) that would cause the total length to exceed maxLength, the entire edit operation is rejected and reverted, leaving the existing text unchanged. For example, pasting "12345" into a text field that already has 8 characters and a maxLength of 10 will reject the paste completely, leaving the original 8 characters unchanged.

This transformation sets the maximum text length for accessibility services. OutputTransformation does not affect this limit. When using an OutputTransformation that adds decorating characters, set SemanticsPropertyReceiver.maxTextLength manually in a custom InputTransformation to include those characters in the announced limit.

import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.InputTransformation
import androidx.compose.foundation.text.input.OutputTransformation
import androidx.compose.foundation.text.input.TextFieldBuffer
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.delete
import androidx.compose.foundation.text.input.insert
import androidx.compose.material3.Text
import androidx.compose.runtime.remember
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.semantics.maxTextLength

val state = remember { TextFieldState() }
BasicTextField(
    state,
    inputTransformation =
        object : InputTransformation {
            override fun SemanticsPropertyReceiver.applySemantics() {
                // The output transformation formats "1234567890" to "(123) 456-7890",
                // which is 14 characters long. We set the accessibility maximum length
                // to 14 so screen readers announce the correct limit.
                maxTextLength = 14
            }

            override fun TextFieldBuffer.transformInput() {
                if (length > 10) {
                    delete(10, length)
                }
            }
        },
    outputTransformation =
        OutputTransformation {
            if (length > 0) insert(0, "(")
            if (length > 4) insert(4, ") ")
            if (length > 9) insert(9, "-")
        },
)

InputTransformation.maxLengthTrim

fun InputTransformation.maxLengthTrim(maxLength: Int): InputTransformation

Limits the total length of the text field to maxLength characters by truncating inserted characters.

When text is inserted (e.g. by typing or pasting) that would cause the total length to exceed maxLength, only enough inserted characters are kept to fill the text field up to maxLength, and excess characters are trimmed and discarded. For example, pasting "12345" into a text field that already has 8 characters and a maxLength of 10 will insert only "12", resulting in 10 characters.

This transformation sets the maximum text length for accessibility services. OutputTransformation does not affect this limit. When using an OutputTransformation that adds decorating characters, set SemanticsPropertyReceiver.maxTextLength manually in a custom InputTransformation to include those characters in the announced limit.

import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.InputTransformation
import androidx.compose.foundation.text.input.OutputTransformation
import androidx.compose.foundation.text.input.TextFieldBuffer
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.delete
import androidx.compose.foundation.text.input.insert
import androidx.compose.material3.Text
import androidx.compose.runtime.remember
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.semantics.maxTextLength

val state = remember { TextFieldState() }
BasicTextField(
    state,
    inputTransformation =
        object : InputTransformation {
            override fun SemanticsPropertyReceiver.applySemantics() {
                // The output transformation formats "1234567890" to "(123) 456-7890",
                // which is 14 characters long. We set the accessibility maximum length
                // to 14 so screen readers announce the correct limit.
                maxTextLength = 14
            }

            override fun TextFieldBuffer.transformInput() {
                if (length > 10) {
                    delete(10, length)
                }
            }
        },
    outputTransformation =
        OutputTransformation {
            if (length > 0) insert(0, "(")
            if (length > 4) insert(4, ") ")
            if (length > 9) insert(9, "-")
        },
)

TextFieldBuffer.placeCursorAtEnd

fun TextFieldBuffer.placeCursorAtEnd(): Unit

Places the cursor at the end of the text.

TextFieldBuffer.selectAll

fun TextFieldBuffer.selectAll(): Unit

Places the selection around all the text.

TextFieldState.setTextAndPlaceCursorAtEnd

fun TextFieldState.setTextAndPlaceCursorAtEnd(text: String): Unit

Sets the text in this TextFieldState to text, replacing any text that was previously there, and places the cursor at the end of the new text.

To perform more complicated edits on the text, call TextFieldState.edit. This function is equivalent to calling:

edit {
replace(0, length, text)
placeCursorAtEnd()
}

TextFieldState.setTextAndSelectAll

fun TextFieldState.setTextAndSelectAll(text: String): Unit

Sets the text in this TextFieldState to text, replacing any text that was previously there, and selects all the text.

To perform more complicated edits on the text, call TextFieldState.edit. This function is equivalent to calling:

edit {
replace(0, length, text)
selectAll()
}

InputTransformation.then

fun InputTransformation.then(next: InputTransformation): InputTransformation

Creates a filter chain that will run next after this. Filters are applied sequentially, so any changes made by this filter will be visible to next.

The returned filter will use the KeyboardOptions from next if non-null, otherwise it will use the options from this transformation.

import androidx.compose.foundation.text.input.InputTransformation
import androidx.compose.foundation.text.input.then

val removeFirstEFilter = InputTransformation {
    val index = asCharSequence().indexOf('e')
    if (index != -1) {
        replace(index, index + 1, "")
    }
}
val printECountFilter = InputTransformation {
    println("found ${asCharSequence().count { it == 'e' }} 'e's in the string")
}

// Returns a filter that always prints 0 e's.
removeFirstEFilter.then(printECountFilter)

// Returns a filter that prints the number of e's before the first one is removed.
printECountFilter.then(removeFirstEFilter)
Parameters
next: InputTransformation

The InputTransformation that will be ran after this one.

TextFieldState.toTextFieldBuffer

fun TextFieldState.toTextFieldBuffer(): TextFieldBuffer

Creates a temporary, mutable TextFieldBuffer representing the current state of this TextFieldState.

Use a TextFieldBuffer to:

  • Apply transformations for testing purposes

  • Preview how the TextField would render with a specific OutputTransformation

This is similar to calling TextFieldState.edit, but without committing the changes back to the TextFieldState.

Important: A TextFieldBuffer is intended for short-term use. Let the garbage collector dispose of it when you're finished to avoid unnecessary memory usage.

import androidx.compose.foundation.text.input.OutputTransformation
import androidx.compose.foundation.text.input.TextFieldBuffer
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.insert
import androidx.compose.foundation.text.input.toTextFieldBuffer
import androidx.compose.material3.Text

val state = TextFieldState("Hello, World")
val outputTransformation = OutputTransformation { insert(0, "> ") }

val buffer = state.toTextFieldBuffer()
with(outputTransformation) { buffer.transformOutput() }

val transformedText = buffer.asCharSequence()
val transformedSelection = buffer.selection