Skip to main content
Tooltips display contextual information when users interact with an element. They provide supplementary details without cluttering the interface and are triggered by hover, focus, or long-press actions.

Usage

import com.nomanr.lumo.ui.components.TooltipBox
import com.nomanr.lumo.ui.components.Tooltip
import com.nomanr.lumo.ui.components.rememberTooltipState

val tooltipState = rememberTooltipState()

TooltipBox(
    tooltip = {
        Tooltip {
            Text("This is a tooltip")
        }
    },
    state = tooltipState
) {
    IconButton(onClick = { }) {
        Icon(Icons.Default.Info, "Info")
    }
}

Basic Tooltip

Simple tooltip with text:
TooltipBox(
    tooltip = {
        Tooltip {
            Text("Save your changes")
        }
    }
) {
    IconButton(onClick = { /* Save */ }) {
        Icon(Icons.Default.Save, "Save")
    }
}

Tooltip with Caret

Default tooltip includes a caret (arrow) pointing to the anchor element:
val tooltipState = rememberTooltipState(isPersistent = true)

TooltipBox(
    tooltip = {
        Tooltip(
            caretSize = DpSize(12.dp, 6.dp)
        ) {
            Text("Tooltip with arrow")
        }
    },
    state = tooltipState
) {
    Box(
        modifier = Modifier
            .size(40.dp)
            .background(Color.Blue)
    )
}

Tooltip without Caret

Remove the caret by setting caretSize to DpSize.Unspecified:
TooltipBox(
    tooltip = {
        Tooltip(
            caretSize = DpSize.Unspecified
        ) {
            Text("Simple tooltip without arrow")
        }
    }
) {
    Text("Hover me")
}

Persistent Tooltip

Keep tooltip visible until explicitly dismissed:
val tooltipState = rememberTooltipState(
    isPersistent = true
)

TooltipBox(
    tooltip = {
        Tooltip {
            Text("This tooltip stays visible")
        }
    },
    state = tooltipState
) {
    Button(
        onClick = {
            scope.launch { 
                if (tooltipState.isVisible) {
                    tooltipState.dismiss()
                } else {
                    tooltipState.show()
                }
            }
        }
    ) {
        Text("Toggle Tooltip")
    }
}

Programmatic Control

Manually show and hide tooltips:
val tooltipState = rememberTooltipState()
val scope = rememberCoroutineScope()

TooltipBox(
    tooltip = {
        Tooltip { Text("Programmatically shown") }
    },
    state = tooltipState
) {
    Button(
        onClick = {
            scope.launch {
                tooltipState.show()
                delay(2000)
                tooltipState.dismiss()
            }
        }
    ) {
        Text("Show Tooltip")
    }
}

Parameters

TooltipBox

tooltip
@Composable TooltipScope.() -> Unit
required
The tooltip content to display
modifier
Modifier
default:"Modifier"
Modifier for the tooltip box
state
TooltipState
default:"rememberTooltipState()"
State object to control the tooltip visibility
positionProvider
PopupPositionProvider
default:"rememberTooltipPositionProvider()"
Provides the position of the tooltip relative to the anchor
focusable
Boolean
default:"true"
Whether the tooltip can receive focus
enableUserInput
Boolean
default:"true"
Whether user input is enabled while tooltip is shown
content
@Composable () -> Unit
required
The anchor content that triggers the tooltip

Tooltip

modifier
Modifier
default:"Modifier"
Modifier for the tooltip surface
caretSize
DpSize
default:"DpSize(12.dp, 6.dp)"
Size of the caret (arrow). Use DpSize.Unspecified to hide
maxWidth
Dp
default:"300.dp"
Maximum width of the tooltip
shape
Shape
default:"RoundedCornerShape(4.dp)"
Shape of the tooltip container
containerColor
Color
default:"AppTheme.colors.surface"
Background color of the tooltip
shadowElevation
Dp
default:"4.dp"
Shadow elevation of the tooltip
content
@Composable () -> Unit
required
The content to display inside the tooltip

rememberTooltipState

initialIsVisible
Boolean
default:"false"
Initial visibility state of the tooltip
isPersistent
Boolean
default:"false"
Whether the tooltip should persist until manually dismissed
mutatorMutex
MutatorMutex
default:"BasicTooltipDefaults.GlobalMutatorMutex"
Mutex to ensure only one tooltip is shown at a time

Custom Styling

Custom Colors

TooltipBox(
    tooltip = {
        Tooltip(
            containerColor = Color(0xFF323232),
            content = {
                Text(
                    text = "Dark tooltip",
                    color = Color.White
                )
            }
        )
    }
) {
    Icon(Icons.Default.Help, "Help")
}

Custom Shape and Size

TooltipBox(
    tooltip = {
        Tooltip(
            shape = RoundedCornerShape(8.dp),
            maxWidth = 200.dp,
            caretSize = DpSize(16.dp, 8.dp)
        ) {
            Column(modifier = Modifier.padding(8.dp)) {
                Text("Custom Tooltip", style = AppTheme.typography.h4)
                Text("With multiple lines", style = AppTheme.typography.body2)
            }
        }
    }
) {
    Button(onClick = {}) { Text("Hover") }
}

Rich Content

TooltipBox(
    tooltip = {
        Tooltip {
            Row(
                modifier = Modifier.padding(8.dp),
                horizontalArrangement = Arrangement.spacedBy(8.dp),
                verticalAlignment = Alignment.CenterVertically
            ) {
                Icon(
                    imageVector = Icons.Default.Info,
                    contentDescription = null,
                    tint = AppTheme.colors.primary
                )
                Column {
                    Text("Feature Name", style = AppTheme.typography.h4)
                    Text("Description here", style = AppTheme.typography.body2)
                }
            }
        }
    }
) {
    IconButton(onClick = {}) {
        Icon(Icons.Default.Info, "Info")
    }
}

Positioning

Custom position provider:
val customPositionProvider = remember {
    object : PopupPositionProvider {
        override fun calculatePosition(
            anchorBounds: IntRect,
            windowSize: IntSize,
            layoutDirection: LayoutDirection,
            popupContentSize: IntSize
        ): IntOffset {
            // Custom positioning logic
            return IntOffset(x = anchorBounds.right, y = anchorBounds.top)
        }
    }
}

TooltipBox(
    tooltip = { Tooltip { Text("Custom position") } },
    positionProvider = customPositionProvider
) {
    Icon(Icons.Default.Star, "Star")
}

Defaults

  • Caret Size: 12.dp × 6.dp
  • Max Width: 300.dp
  • Min Height: 24.dp
  • Min Width: 40.dp
  • Shadow Elevation: 4.dp
  • Vertical Padding: 4.dp
  • Horizontal Padding: 8.dp
  • Shape: RoundedCornerShape(4.dp)
  • Spacing Between Tooltip and Anchor: 4.dp

Animations

Tooltips include smooth fade and scale animations:
  • Scale Animation: 100ms with FastOutLinearInEasing
    • Hidden: 0.8x scale
    • Visible: 1.0x scale
  • Alpha Animation: 50ms with FastOutSlowInEasing
    • Hidden: 0.0 alpha
    • Visible: 1.0 alpha

Best Practices

  1. Keep it Brief: Tooltips should contain short, helpful text
  2. Don’t Repeat Labels: Don’t show the same text that’s already visible
  3. Accessibility: Essential information shouldn’t only be in tooltips
  4. Mobile Considerations: On touch devices, use long-press to show tooltips
  5. Consistent Styling: Maintain consistent tooltip styling across your app
  6. Positioning: Ensure tooltips don’t cover critical UI elements
  7. Timing: Use persistent tooltips sparingly, most should auto-dismiss

Accessibility

  • Tooltips automatically include proper semantics
  • Content descriptions are preserved from anchor elements
  • Focus management is handled automatically
  • Screen readers announce tooltip content appropriately

Source Reference

See the full implementation in Tooltip.kt:66-98 (TooltipBox) and Tooltip.kt:101-148 (Tooltip).

Build docs developers (and LLMs) love