Build apps faster with our new App builder! Check it out →

TooltipBox

Common

Component in Material 3 Compose

Material TooltipBox that wraps a composable with a tooltip.

tooltips provide a descriptive message for an anchor. It can be used to call the users attention to the anchor.

Tooltip that is invoked when the anchor is long pressed:

@sample androidx.compose.material3.samples.PlainTooltipSample

If control of when the tooltip is shown is desired please see

@sample androidx.compose.material3.samples.PlainTooltipWithManualInvocationSample

Plain tooltip with caret shown on long press:

@sample androidx.compose.material3.samples.PlainTooltipWithCaret

Plain tooltip shown on long press with a custom caret:

@sample androidx.compose.material3.samples.PlainTooltipWithCustomCaret

Tooltip that is invoked when the anchor is long pressed:

@sample androidx.compose.material3.samples.RichTooltipSample

If control of when the tooltip is shown is desired please see

@sample androidx.compose.material3.samples.RichTooltipWithManualInvocationSample

Rich tooltip with caret shown on long press:

@sample androidx.compose.material3.samples.RichTooltipWithCaretSample

Rich tooltip shown on long press with a custom caret

Last updated:

Installation

dependencies {
   implementation("androidx.compose.material3:material3:1.4.0-alpha02")
}

Overloads

@Composable
@ExperimentalMaterial3Api
fun TooltipBox(
    positionProvider: PopupPositionProvider,
    tooltip: @Composable TooltipScope.() -> Unit,
    state: TooltipState,
    modifier: Modifier = Modifier,
    focusable: Boolean = true,
    enableUserInput: Boolean = true,
    content: @Composable () -> Unit,
)

Parameters

namedescription
positionProvider[PopupPositionProvider] that will be used to place the tooltip relative to the anchor content.
tooltipthe composable that will be used to populate the tooltip's content.
statehandles the state of the tooltip's visibility.
modifierthe [Modifier] to be applied to the TooltipBox.
focusable[Boolean] that determines if the tooltip is focusable. When true, the tooltip will consume touch events while it's shown and will have accessibility focus move to the first element of the component. When false, the tooltip won't consume touch events while it's shown but assistive-tech users will need to swipe or drag to get to the first element of the component.
enableUserInput[Boolean] which determines if this TooltipBox will handle long press and mouse hover to trigger the tooltip through the state provided.
contentthe composable that the tooltip will anchor to.

Code Examples

PlainTooltipSample

@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun PlainTooltipSample() {
    TooltipBox(
        positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
        tooltip = { PlainTooltip { Text("Add to favorites") } },
        state = rememberTooltipState()
    ) {
        IconButton(onClick = { /* Icon button's click event */ }) {
            Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Localized Description")
        }
    }
}

PlainTooltipWithManualInvocationSample

@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun PlainTooltipWithManualInvocationSample() {
    val tooltipState = rememberTooltipState()
    val scope = rememberCoroutineScope()
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        TooltipBox(
            positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
            tooltip = { PlainTooltip { Text("Add to list") } },
            state = tooltipState
        ) {
            Icon(imageVector = Icons.Filled.AddCircle, contentDescription = "Localized Description")
        }
        Spacer(Modifier.requiredHeight(30.dp))
        OutlinedButton(onClick = { scope.launch { tooltipState.show() } }) {
            Text("Display tooltip")
        }
    }
}

PlainTooltipWithCaret

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PlainTooltipWithCaret() {
    TooltipBox(
        positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
        tooltip = {
            PlainTooltip(caretSize = TooltipDefaults.caretSize) { Text("Add to favorites") }
        },
        state = rememberTooltipState()
    ) {
        IconButton(onClick = { /* Icon button's click event */ }) {
            Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Localized Description")
        }
    }
}

PlainTooltipWithCustomCaret

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PlainTooltipWithCustomCaret() {
    TooltipBox(
        positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
        tooltip = { PlainTooltip(caretSize = DpSize(24.dp, 12.dp)) { Text("Add to favorites") } },
        state = rememberTooltipState()
    ) {
        IconButton(onClick = { /* Icon button's click event */ }) {
            Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Localized Description")
        }
    }
}

RichTooltipSample

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RichTooltipSample() {
    val tooltipState = rememberTooltipState(isPersistent = true)
    val scope = rememberCoroutineScope()
    TooltipBox(
        positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(),
        tooltip = {
            RichTooltip(
                title = { Text(richTooltipSubheadText) },
                action = {
                    TextButton(onClick = { scope.launch { tooltipState.dismiss() } }) {
                        Text(richTooltipActionText)
                    }
                }
            ) {
                Text(richTooltipText)
            }
        },
        state = tooltipState
    ) {
        IconButton(onClick = { /* Icon button's click event */ }) {
            Icon(imageVector = Icons.Filled.Info, contentDescription = "Localized Description")
        }
    }
}

RichTooltipWithManualInvocationSample

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RichTooltipWithManualInvocationSample() {
    val tooltipState = rememberTooltipState(isPersistent = true)
    val scope = rememberCoroutineScope()
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        TooltipBox(
            positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(),
            tooltip = {
                RichTooltip(
                    title = { Text(richTooltipSubheadText) },
                    action = {
                        TextButton(onClick = { scope.launch { tooltipState.dismiss() } }) {
                            Text(richTooltipActionText)
                        }
                    }
                ) {
                    Text(richTooltipText)
                }
            },
            state = tooltipState
        ) {
            Icon(imageVector = Icons.Filled.Info, contentDescription = "Localized Description")
        }
        Spacer(Modifier.requiredHeight(30.dp))
        OutlinedButton(onClick = { scope.launch { tooltipState.show() } }) {
            Text("Display tooltip")
        }
    }
}

RichTooltipWithCaretSample

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RichTooltipWithCaretSample() {
    val tooltipState = rememberTooltipState(isPersistent = true)
    val scope = rememberCoroutineScope()
    TooltipBox(
        positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(),
        tooltip = {
            RichTooltip(
                title = { Text(richTooltipSubheadText) },
                action = {
                    TextButton(onClick = { scope.launch { tooltipState.dismiss() } }) {
                        Text(richTooltipActionText)
                    }
                },
                caretSize = TooltipDefaults.caretSize
            ) {
                Text(richTooltipText)
            }
        },
        state = tooltipState
    ) {
        IconButton(onClick = { /* Icon button's click event */ }) {
            Icon(imageVector = Icons.Filled.Info, contentDescription = "Localized Description")
        }
    }
}

RichTooltipWithCustomCaretSample

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RichTooltipWithCustomCaretSample() {
    val tooltipState = rememberTooltipState(isPersistent = true)
    val scope = rememberCoroutineScope()
    TooltipBox(
        positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(),
        tooltip = {
            RichTooltip(
                title = { Text(richTooltipSubheadText) },
                action = {
                    TextButton(onClick = { scope.launch { tooltipState.dismiss() } }) {
                        Text(richTooltipActionText)
                    }
                },
                caretSize = DpSize(32.dp, 16.dp)
            ) {
                Text(richTooltipText)
            }
        },
        state = tooltipState
    ) {
        IconButton(onClick = { /* Icon button's click event */ }) {
            Icon(imageVector = Icons.Filled.Info, contentDescription = "Localized Description")
        }
    }
}
by @alexstyl