---
title: "TrackedRange"
description: "A style applied on the text that is tracked by TextFieldBuffer, returned by TextFieldBuffer.addStyle."
type: "class"
lastmod: "2026-05-20T01:13:53.347116Z"
---
## API Reference

> Source set: Common

```kotlin
class TrackedRange<T>
internal constructor(internal val creatorId: Any, internal var intervalHandle: IntervalHandle)
```

A style applied on the text that is tracked by [TextFieldBuffer](/jetpack-compose/androidx.compose.foundation/foundation/classes/TextFieldBuffer), returned by
[TextFieldBuffer.addStyle](/jetpack-compose/androidx.compose.foundation/foundation/classes/TextFieldBuffer).

`TrackedRange` acts as a unique handle to a specific style range. Its properties (such as the
style object, its range, and expand policy) can be queried and updated using extension properties
on [TextFieldBuffer](/jetpack-compose/androidx.compose.foundation/foundation/classes/TextFieldBuffer):
- `TrackedRange<*>.textRange`
- `TrackedRange<*>.expandPolicy`
- `TrackedRange<*>.exists`
- `TrackedRange<SpanStyle>.spanStyle`
- `TrackedRange<ParagraphStyle>.paragraphStyle`

All the extension properties reflect the up-to-date state of the style range. e.g. The
`textRange` of this [TrackedRange](/jetpack-compose/androidx.compose.foundation/foundation/classes/TrackedRange) will automatically update when the text is edited. If the
style's range collapses to zero length due to text edits, the style will cease to exist and
`exists` will return false.

This object's lifecycle is bound to the [TextFieldBuffer](/jetpack-compose/androidx.compose.foundation/foundation/classes/TextFieldBuffer) which is returned by
[TextFieldState.edit](/jetpack-compose/androidx.compose.foundation/foundation/classes/TextFieldState), [InputTransformation.transformInput](/jetpack-compose/androidx.compose.foundation/foundation/interfaces/InputTransformation) and
[OutputTransformation.transformOutput](/jetpack-compose/androidx.compose.foundation/foundation/interfaces/OutputTransformation). Do not keep a reference of it.

## Code Examples

### BasicTextFieldTrackedRangeSample
```kotlin
@Composable
fun BasicTextFieldTrackedRangeSample() {
    // This sample demonstrates how to use the `TrackedRange` API to track and modify text ranges
    // dynamically. It implements a basic Markdown-like behavior where text typed inside double
    // asterisks (e.g., **bold**) is automatically bolded, and the asterisks are removed.
    val state = rememberTextFieldState("")
    fun IntRange.toTextRange(): TextRange {
        // Unlike IntRange, TextRange is exclusive at the end.
        return TextRange(first, last + 1)
    }
    val inputTransformation = remember {
        InputTransformation {
            val text = asCharSequence().toString()
            val matches = "\\*\\*([^*]+)\\*\\*".toRegex().findAll(text).toList()
            matches
                .map { match ->
                    val contentRange = match.groups[0]!!.range
                    // Apply bold style to the text inside asterisks (including the
                    // asterisks for now).
                    addStyle(
                        SpanStyle(fontWeight = FontWeight.Bold),
                        contentRange.toTextRange(),
                        ExpandPolicy.InsideOnly,
                    )
                }
                .forEach { trackedRange ->
                    // Remove the asterisks here.
                    // `trackedRange` simplifies this logic: normally, deleting characters at
                    // the start would shift the end index. However, because `trackedRange`
                    // automatically tracks text updates and adjusts its offsets
                    // dynamically, we can safely delete the target range without having to
                    // calculate the offset manually.
                    delete(trackedRange.textRange.start, trackedRange.textRange.start + 2)
                    delete(trackedRange.textRange.end - 2, trackedRange.textRange.end)
                }
        }
    }
    Column {
        Text("Type **text** below to automatically bold it.")
        BasicTextField(
            state = state,
            textStyle = LocalTextStyle.current,
            inputTransformation = inputTransformation,
        )
    }
}
```

### BasicTextFieldTrackedRangeTextRangeSetterSample
```kotlin
@Composable
fun BasicTextFieldTrackedRangeTextRangeSetterSample() {
    // Wipe the bold style on a given range using the TrackedRange.textRange API
    val state = TextFieldState("Hello World")
    state.edit {
        // Assume we want to "wipe" all bold styles from the first 5 characters.
        val rangeToWipe = TextRange(0, 5)
        // Get all span styles that intersect with the wipe range.
        getSpanStyles(rangeToWipe.start, rangeToWipe.end).forEach { trackedRange ->
            if (trackedRange.spanStyle.fontWeight == FontWeight.Bold) {
                val current = trackedRange.textRange
                if (rangeToWipe.start <= current.start && current.end <= rangeToWipe.end) {
                    // Case 1: The bold style is entirely within the wipe range, remove it.
                    removeStyle(trackedRange)
                } else if (current.start < rangeToWipe.start && rangeToWipe.end < current.end) {
                    // Case 2: The wipe range is in the middle: split the style into two parts.
                    val oldEnd = current.end
                    // Truncate the original style to end at the start of the wipe range.
                    trackedRange.textRange = TextRange(current.start, rangeToWipe.start)
                    // Add a new bold style starting after the wipe range.
                    addStyle(trackedRange.spanStyle, rangeToWipe.end, oldEnd)
                } else if (current.start < rangeToWipe.start) {
                    // Case 3: Overlap at the start of wipe: truncate the style's end.
                    trackedRange.textRange = TextRange(current.start, rangeToWipe.start)
                } else {
                    // Case 4: Overlap at the end of wipe: truncate the style's start.
                    trackedRange.textRange = TextRange(rangeToWipe.end, current.end)
                }
            }
        }
    }
}
```

### BasicTextFieldTrackedRangeToggleBoldSample
```kotlin
@Composable
fun BasicTextFieldTrackedRangeToggleBoldSample() {
    // This sample demonstrates a realistic rich-text editor scenario using the `TrackedRange` and
    // `TextFieldTextStyles` APIs. It implements a "Toggle Bold" formatting function on the current
    // selection.
    // For simplicity, this sample keeps bold styles non-overlapping and contiguous, assuming they
    // are
    // applied exclusively through this method.
    val state = rememberTextFieldState("Hello World")
    // This derived state calculates whether the current selection is completely covered by
    // bold text styles. This ensures the "Bold" toggle button accurately reflects the
    // state of the selected text.
    val isSelection100PercentBold by derivedStateOf {
        val selection = state.selection
        if (selection.collapsed) {
            false
        } else {
            val spanStyles = state.textStyles.getSpanStyles(selection.min, selection.max)
            var boldCoverage = 0
            for (style in spanStyles) {
                if (style.item.fontWeight == FontWeight.Bold) {
                    val overlapStart = maxOf(style.start, selection.min)
                    val overlapEnd = minOf(style.end, selection.max)
                    if (overlapEnd > overlapStart) {
                        boldCoverage += (overlapEnd - overlapStart)
                    }
                }
            }
            boldCoverage == selection.length
        }
    }
    fun TextFieldBuffer.unBoldSelection() {
        // Query existing bold styles in the selection
        val intersectingStyles =
            getSpanStyles(selection.min, selection.max).filter {
                it.spanStyle.fontWeight == FontWeight.Bold
            }
        // We modify or remove existing styles to exclude the selected range
        for (style in intersectingStyles) {
            val range = style.textRange
            if (range.start >= selection.min && range.end <= selection.max) {
                // The style is fully inside the selection. Remove it.
                removeStyle(style)
            } else if (range.start < selection.min && range.end > selection.max) {
                // The style completely covers the selection. We need to split it.
                val oldEnd = range.end
                // Truncate the start part
                style.textRange = TextRange(range.start, selection.min)
                // Add a new style for the end part
                addStyle(
                    SpanStyle(fontWeight = FontWeight.Bold),
                    TextRange(selection.max, oldEnd),
                    ExpandPolicy.AtEnd,
                )
            } else if (range.start < selection.min) {
                // The style overlaps with the start of the selection. Truncate it.
                style.textRange = TextRange(range.start, selection.min)
            } else {
                // The style overlaps with the end of the selection. Truncate it.
                style.textRange = TextRange(selection.max, range.end)
            }
        }
    }
    fun TextFieldBuffer.boldSelection() {
        // Query existing bold styles in the selection
        val intersectingStyles =
            getSpanStyles(selection.min, selection.max).filter {
                it.spanStyle.fontWeight == FontWeight.Bold
            }
        // To keep bold styles non-overlapping, we merge any intersecting bold
        // styles with the new selection range into a single contiguous bold style.
        var mergedStart = selection.min
        var mergedEnd = selection.max
        for (style in intersectingStyles) {
            mergedStart = minOf(mergedStart, style.textRange.start)
            mergedEnd = maxOf(mergedEnd, style.textRange.end)
            // Remove the fragmented style
            removeStyle(style)
        }
        addStyle(
            SpanStyle(fontWeight = FontWeight.Bold),
            TextRange(mergedStart, mergedEnd),
            ExpandPolicy.AtEnd,
        )
    }
    Column {
        Button(
            onClick = {
                state.edit {
                    val selection = this.selection
                    if (selection.collapsed) return@edit
                    if (isSelection100PercentBold) {
                        unBoldSelection()
                    } else {
                        boldSelection()
                    }
                }
            }
        ) {
            Text(
                "B",
                fontWeight = if (isSelection100PercentBold) FontWeight.Bold else FontWeight.Normal,
            )
        }
        BasicTextField(state = state, textStyle = LocalTextStyle.current)
    }
}
```
