
Polished coach marks, product tours and spotlight overlays with smart arrows, instruction cards, rounded/circular highlights, automatic scrolling, multi-step navigation and extensive styling/customization.
Beautiful coach marks for Compose Multiplatform
Build polished product tours, onboarding flows, spotlight overlays, smart arrows, instruction cards, and automatic scrolling in Kotlin Compose.
Website · Installation · Quick Start · Customization · Features · Platform Support
Add GuideKit to the Compose Multiplatform source set where you want to show tours.
implementation("io.github.tharukack:guidekit:1.1.0")For Kotlin Multiplatform projects:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.tharukack:guidekit:1.1.0")
}
}
}Add GuideKit to the main composable that occupies the full screen. It does not matter whether your screen uses a Scaffold, a Box, or another layout. The guide should be placed at the screen's top level so it can draw above all of its content.
Store the measured bounds of the composable you want to highlight. This minimal example uses rememberSaveable for guide visibility, but you can use any persistence approach you prefer, such as a ViewModel backed by DataStore or a database, to prevent completed guides from appearing again.
var targetBounds by remember { mutableStateOf<Rect?>(null) }
var showGuide by rememberSaveable { mutableStateOf(true) } // Replace with your persistence update if needed.Pass a bounds callback into the child composable that contains the target. Attach onGloballyPositioned to the target itself and send its measured bounds back through that callback.
@Composable
fun MainScreenContent(
onTargetBoundsChanged: (Rect?) -> Unit,
modifier: Modifier = Modifier,
) {
Column(modifier) {
Button(
onClick = { /* Your action */ },
modifier = Modifier.onGloballyPositioned { coordinates ->
onTargetBoundsChanged(coordinates.boundsInRoot())
},
) {
Text("Create item")
}
}
}Place GuideKit in the full-size parent composable, after the screen content. The parent owns targetBounds and passes its update callback into MainScreenContent. This lets a target inside any nested composable send its bounds to the parent without moving the target itself.
@Composable
fun MainScreen() {
var targetBounds by remember { mutableStateOf<Rect?>(null) }
var showGuide by rememberSaveable { mutableStateOf(true) }
Box(Modifier.fillMaxSize()) {
MainScreenContent(
onTargetBoundsChanged = { bounds -> targetBounds = bounds },
modifier = Modifier.fillMaxSize(),
)
if (showGuide) {
GuideKit(
steps = listOf(
GuideKitStep(
targetBounds = targetBounds,
title = "Create your first item",
description = "Tap here to start a new workflow.",
arrowConfigOverride = GuideKitArrowConfigOverride(
from = GuideKitAnchor.TopRight, // instruction box connection point
to = GuideKitAnchor.BottomCenter, // target connection point
),
),
),
onSkipped = { showGuide = false },
onFinished = { showGuide = false },
modifier = Modifier.fillMaxSize(),
)
}
}
}See the sample app for full configuration, styling, auto-scroll, arrow variants, rounded highlights, and circular highlights.
| Area | What you can configure |
|---|---|
| Multiple steps | Add targets and control the order of the tour. |
| Global and step-specific styles | Set shared visual defaults and override individual steps. |
| Step text | Customize titles, descriptions, highlighted phrases, and button labels. |
| Arrows | Configure anchors, line patterns, curves, strokes, and arrowheads. |
| Target highlights | Configure shapes, cutouts, padding, glows, and borders. |
| Instruction cards | Configure placement, size, shape, colors, borders, and shadows. |
| Automatic scrolling | Keep highlighted targets visible around the instruction card. |
| Navigation and callbacks | Configure progress, starting step, skip, finish, and analytics callbacks. |
Add one GuideKitStep for each composable you want to highlight. Every step receives the bounds measured from its own target composable, and the order of the list determines the order of the tour. GuideKit automatically handles next, previous, and finish navigation.
The following example creates a three-step guide. createButtonBounds, activityCardBounds, and profileButtonBounds are measured and sent to the parent using the same callback pattern shown in the Quick Start.
val guideSteps = listOf(
GuideKitStep(
targetBounds = createButtonBounds,
title = "Create your first item",
description = "Tap here to start a new workflow.",
arrowConfigOverride = GuideKitArrowConfigOverride(
from = GuideKitAnchor.TopRight,
to = GuideKitAnchor.BottomCenter,
),
),
GuideKitStep(
targetBounds = activityCardBounds,
title = "Track your progress",
description = "Your latest activity and important updates appear here.",
arrowConfigOverride = GuideKitArrowConfigOverride(
from = GuideKitAnchor.BottomLeft,
to = GuideKitAnchor.TopCenter,
),
),
GuideKitStep(
targetBounds = profileButtonBounds,
title = "Make it yours",
description = "Open your profile to customize your experience.",
arrowConfigOverride = GuideKitArrowConfigOverride(
from = GuideKitAnchor.TopRight,
to = GuideKitAnchor.CenterLeft,
),
),
)
GuideKit(
steps = guideSteps,
onSkipped = { showGuide = false },
onFinished = { showGuide = false },
modifier = Modifier.fillMaxSize(),
)Use GuideKitStyle to define the visual defaults for the entire guide. A step inherits the global arrow, target highlight, and instruction box styles when it does not provide its own override.
val guideStyle = GuideKitStyle(
accentColor = Color(0xFF7C4DFF),
overlayColor = Color.Black.copy(alpha = 0.72f),
titleColor = Color.White,
descriptionColor = Color.White.copy(alpha = 0.78f),
highlightedDescriptionColor = Color(0xFFFFC857),
stepIndicatorActiveColor = Color(0xFF7C4DFF),
stepIndicatorInactiveColor = Color.White.copy(alpha = 0.30f),
primaryButtonContainerColor = Color(0xFF7C4DFF),
primaryButtonContentColor = Color.White,
skipIconTint = Color.White,
arrowConfig = GuideKitArrowConfig(
lineStyle = GuideKitArrowLineStyle.SpacedDash,
),
targetHighlight = GuideKitTargetHighlightStyle(
shape = GuideKitTargetHighlightShape.RoundedRect,
),
instructionBox = GuideKitInstructionBoxStyle(
containerColor = Color(0xFF17132B),
contentColor = Color.White,
),
)
GuideKit(
steps = guideSteps,
style = guideStyle,
onFinished = { showGuide = false },
)| Global option | Default | Purpose |
|---|---|---|
accentColor |
Color(0xFF5ED5B3) |
Default color for arrows, highlight borders and glows, highlighted text, progress, and the primary button. |
overlayColor |
Color.Black.copy(alpha = 0.68f) |
Color and opacity of the full-screen dimming layer. |
titleColor |
null → MaterialTheme.colorScheme.onSurface
|
Title color inside the instruction card. |
descriptionColor |
null → MaterialTheme.colorScheme.onSurfaceVariant
|
Description color inside the instruction card. |
highlightedDescriptionColor |
null → accentColor
|
Color of phrases selected by descriptionHighlights. |
stepIndicatorActiveColor |
null → accentColor
|
Active progress indicator color. |
stepIndicatorInactiveColor |
null → MaterialTheme.colorScheme.outlineVariant
|
Inactive progress indicator color. |
primaryButtonContainerColor |
null → accentColor
|
Primary navigation button background. |
primaryButtonContentColor |
Color(0xFF062D25) |
Primary navigation button content color. |
skipIconTint |
null → MaterialTheme.colorScheme.onSurfaceVariant
|
Color of the optional close button. |
arrowConfig |
GuideKitArrowConfig() |
Default arrow configuration inherited by steps. |
targetHighlight |
GuideKitTargetHighlightStyle() |
Default target highlight inherited by steps. |
instructionBox |
GuideKitInstructionBoxStyle() |
Default instruction card inherited by steps. |
Override only the properties a step needs. Every property omitted from an override continues to use its global value:
GuideKitStep(
targetBounds = profileButtonBounds,
title = "Open your profile",
description = "Manage your preferences here.",
arrowConfigOverride = GuideKitArrowConfigOverride(
from = GuideKitAnchor.BottomCenter,
to = GuideKitAnchor.CenterRight,
lineStyle = GuideKitArrowLineStyle.Solid,
),
targetHighlightOverride = GuideKitTargetHighlightStyleOverride(
shape = GuideKitTargetHighlightShape.Circle,
),
)GuideKit resolves styles in this order: library defaults → global GuideKitStyle → explicit step overrides. A step override never resets an unrelated global color, shape, stroke, padding, or shadow.
All ordinary override properties default to null, meaning “inherit the global value.” For properties where null is itself meaningful, the matching ...Override property uses GuideKitOverride.Inherit by default and accepts GuideKitOverride.Value(...) for an explicit value—including null.
| Configuration | Global default | Step-specific override |
|---|---|---|
| Colors and overlay | GuideKitStyle |
Not overridden per step |
| Arrow | GuideKitStyle.arrowConfig |
GuideKitStep.arrowConfigOverride |
| Target highlight | GuideKitStyle.targetHighlight |
GuideKitStep.targetHighlightOverride |
| Instruction card | GuideKitStyle.instructionBox |
GuideKitStep.instructionBoxOverride |
| Text and button label | — | GuideKitStep |
| Auto-scroll | — | GuideKitStep.autoScroll |
Each step requires a title and description. You can highlight one or more exact phrases and change the primary button label. When primaryButtonText is not provided, GuideKit uses Next and changes the final step to Got it.
GuideKitStep(
targetBounds = activityCardBounds,
title = "Never miss an update",
description = "Important activity and urgent alerts appear here.",
descriptionHighlights = listOf(
"Important activity",
"urgent alerts",
),
primaryButtonText = "Continue",
)Use descriptionHighlights = listOf("phrase") for one highlighted phrase or add more list entries for several. Every phrase must exactly match text inside description. Highlighted text uses GuideKitStyle.highlightedDescriptionColor, falling back to the guide's accentColor.
| Step option | Default | Purpose |
|---|---|---|
targetBounds |
Required | Bounds of the composable being highlighted. |
title |
Required | Instruction card title. |
description |
Required | Instruction card description. |
primaryButtonText |
null → Next, or Got it on the last step |
Overrides the primary button label. |
descriptionHighlights |
emptyList() |
Highlights one or more exact phrases from description. |
instructionBottomPadding |
104.dp |
Bottom padding used by the default instruction-card placement. |
arrowConfigOverride |
null → inherit global arrow |
Overrides only supplied arrow properties. |
targetHighlightOverride |
null → inherit global highlight |
Overrides only supplied highlight properties. |
instructionBoxOverride |
null → inherit global instruction card |
Overrides only supplied card properties. |
autoScroll |
GuideKitAutoScrollConfig() |
Controls automatic scrolling for this step. |
The arrow starts from the instruction card's from anchor and points to the highlighted target's to anchor. All nine positions in GuideKitAnchor can be used on either side:
TopLeft TopCenter TopRight
CenterLeft Center CenterRight
BottomLeft BottomCenter BottomRight
Set a global arrow style in GuideKitStyle, then override placement or appearance for individual steps when the layout changes.
val guideStyle = GuideKitStyle(
arrowConfig = GuideKitArrowConfig(
lineStyle = GuideKitArrowLineStyle.MediumDash,
arrowHead = GuideKitArrowHead.TargetSide,
strokeCap = StrokeCap.Round,
),
)
val specialStep = GuideKitStep(
targetBounds = actionButtonBounds,
title = "Take action",
description = "Tap here when you are ready.",
arrowConfigOverride = GuideKitArrowConfigOverride(
from = GuideKitAnchor.TopRight,
to = GuideKitAnchor.CenterLeft,
curveSeed = 3,
lineStyle = GuideKitArrowLineStyle.Solid,
arrowHead = GuideKitArrowHead.BothSides,
strokes = listOf(
GuideKitArrowStroke(
width = 9,
color = Color.Black.copy(alpha = 0.22f),
),
GuideKitArrowStroke(
width = 5,
color = Color(0xFF7C4DFF),
),
),
),
)GuideKit translates the selected lineStyle into its internal drawing pattern, so applications do not need to manage raw dash lengths or gaps.
| Arrow option | Default | Purpose |
|---|---|---|
enabled |
true |
Shows or hides the arrow. |
from |
GuideKitAnchor.TopCenter |
Connection anchor on the instruction card. |
to |
GuideKitAnchor.BottomCenter |
Connection anchor on the highlighted target. |
curveSeed |
0 |
Produces a different deterministic curve for the same anchors. |
minVisibleDistance |
40.dp |
Hides the arrow when the card and target are too close. |
lineStyle |
GuideKitArrowLineStyle.SpacedDash |
Selects the arrow's line pattern. |
strokes |
Widths 9, 6, 2; black, accent, white |
Draws layered arrow-body strokes. A null color uses the global accent color. |
strokeCap |
StrokeCap.Round |
Controls the ends of arrow-body strokes. |
arrowHead |
GuideKitArrowHead.TargetSide |
Uses None, TargetSide, InstructionBoxSide, or BothSides. |
arrowHeadLength |
38 |
Controls arrowhead length. |
arrowHeadAngleDegrees |
30 |
Controls arrowhead angle. |
arrowHeadStrokes |
Widths 9, 6, 2; black, accent, white |
Draws layered arrowhead strokes. |
| Line style | Appearance |
|---|---|
Solid |
One continuous line. |
SpacedDash |
Medium-long dashes with clearly visible spacing between them. |
Dotted |
Closely spaced round dots when used with the default round stroke cap. |
ShortDash |
Short, frequent dashes for compact paths. |
MediumDash |
Medium-length dashes with tighter spacing than SpacedDash. |
LongDash |
Long, widely spaced dashes for larger layouts. |
DashDot |
Alternates a long dash with a dot. |
Target highlights can be rounded rectangles or circles. Define the common highlight globally and override it for targets such as avatars or floating action buttons.
val guideStyle = GuideKitStyle(
targetHighlight = GuideKitTargetHighlightStyle(
shape = GuideKitTargetHighlightShape.RoundedRect,
padding = 12,
cornerRadius = 24.dp,
borderColor = Color(0xFF7C4DFF),
),
)
val avatarStep = GuideKitStep(
targetBounds = avatarBounds,
title = "This is your profile",
description = "Tap your avatar to manage your account.",
targetHighlightOverride = GuideKitTargetHighlightStyleOverride(
shape = GuideKitTargetHighlightShape.Circle,
padding = 16,
glowStrokes = listOf(
GuideKitTargetHighlightStroke(
width = 30,
alpha = 0.14f,
color = Color(0xFF7C4DFF),
),
GuideKitTargetHighlightStroke(
width = 8,
alpha = 0.55f,
color = Color(0xFF7C4DFF),
),
),
),
)| Highlight option | Default | Purpose |
|---|---|---|
enabled |
true |
Enables or disables the target highlight. |
shape |
GuideKitTargetHighlightShape.RoundedRect |
Uses RoundedRect or Circle. |
cutoutEnabled |
true |
Clears the dimmed overlay inside the target when enabled. |
padding |
10 |
Adds space around the measured target bounds. |
cornerRadius |
28.dp |
Controls rounded-rectangle corners. |
glowStrokes |
Width/alpha: 30/0.11f, 22/0.18f, 14/0.30f, 8/0.45f
|
Adds layered glow strokes. Each default null color resolves to the global accent. |
borderColor |
null → accentColor
|
Controls the main target border color. |
borderWidth |
3 |
Controls the main target border width. |
innerBorderColor |
Color.White.copy(alpha = 0.7f) |
Controls the inner border color. |
innerBorderWidth |
1 |
Controls the inner border width. |
innerBorderInset |
2 |
Moves the inner border inward from the highlight edge. |
For a custom GuideKitTargetHighlightStroke, width and alpha are required, while color defaults to null (the global accent).
Use GuideKitInstructionBoxStyle to define the global instruction card. Use GuideKitInstructionBoxStyleOverride to change selected properties for one step while retaining every other global value.
val guideStyle = GuideKitStyle(
instructionBox = GuideKitInstructionBoxStyle(
alignment = Alignment.BottomCenter,
outerPadding = PaddingValues(20.dp),
contentPadding = PaddingValues(
horizontal = 24.dp,
vertical = 20.dp,
),
fillMaxWidth = false,
maxWidth = 420.dp,
shape = RoundedCornerShape(28.dp),
containerColor = Color(0xFF17132B),
contentColor = Color.White,
border = BorderStroke(
1.dp,
Color(0xFF7C4DFF).copy(alpha = 0.45f),
),
shadow = GuideKitInstructionBoxShadow(
elevation = 32.dp,
ambientColor = Color.Black.copy(alpha = 0.34f),
spotColor = Color.Black.copy(alpha = 0.34f),
),
),
)For a single step:
GuideKitStep(
targetBounds = wideCardBounds,
title = "Your weekly summary",
description = "Everything important appears in this section.",
instructionBottomPadding = 72.dp,
instructionBoxOverride = GuideKitInstructionBoxStyleOverride(
alignment = Alignment.BottomCenter,
fillMaxWidth = false,
maxWidth = 300.dp,
shape = RoundedCornerShape(20.dp),
),
)| Instruction card option | Default | Purpose |
|---|---|---|
alignment |
Alignment.BottomCenter |
Positions the card inside the full-screen GuideKit container. |
outerPadding |
null → start/end 18.dp, bottom instructionBottomPadding
|
Adds space around the card. |
contentPadding |
Horizontal 22.dp, vertical 24.dp
|
Adds space inside the card. |
fillMaxWidth |
true |
Makes the card use the available width. |
minWidth, maxWidth, minHeight, maxHeight
|
null |
Constrains the card size when provided. |
shape |
RoundedCornerShape(30.dp) |
Controls the card shape. |
containerColor |
null → MaterialTheme.colorScheme.surface
|
Controls the card background. |
contentColor |
null → MaterialTheme.colorScheme.onSurface
|
Controls the Surface content color. |
border |
BorderStroke(1.dp, Color(0xFF5ED5B3).copy(alpha = 0.28f)) |
Adds or removes the card border. Use null for no border. |
tonalElevation |
0.dp |
Controls Material tonal elevation. |
shadowElevation |
26.dp |
Controls Material shadow elevation. |
shadow |
GuideKitInstructionBoxShadow() |
Adds a customizable outer shadow. Use null to disable it. |
modifier |
Modifier |
Applies an additional modifier to the instruction card. |
The default GuideKitInstructionBoxShadow uses 30.dp elevation with black ambient and spot colors at 0.42f alpha.
To explicitly clear a nullable global value for one step, use its matching override-value property:
GuideKitStep(
targetBounds = targetBounds,
title = "A simpler card",
description = "This step removes the global border and shadow.",
instructionBoxOverride = GuideKitInstructionBoxStyleOverride(
borderOverride = GuideKitOverride.Value(null),
shadowOverride = GuideKitOverride.Value(null),
),
)The same pattern is available for nullable values such as outerPadding, size constraints, container and content colors, and target borderColor.
instructionBottomPadding belongs to GuideKitStep. It is used only when the instruction box's global or step-specific outerPadding is null.
Auto-scroll is enabled for each step by default, but the host screen must provide onScrollBy. GuideKit calculates the required distance and delegates the actual scroll operation to your scroll state.
val scrollState = rememberScrollState()
GuideKit(
steps = guideSteps,
onScrollBy = { deltaPx ->
scrollState.animateScrollBy(deltaPx)
},
onFinished = { showGuide = false },
)Tune or disable scrolling for an individual step:
GuideKitStep(
targetBounds = lowerCardBounds,
title = "More options below",
description = "GuideKit scrolls this target into a safe position.",
autoScroll = GuideKitAutoScrollConfig(
enabled = true,
minTopVisibleDistance = 56.dp,
spacing = 48.dp,
),
)| Auto-scroll option | Default | Purpose |
|---|---|---|
enabled |
true |
Enables or disables scrolling for the step. |
minTopVisibleDistance |
null → arrowConfig.minVisibleDistance + 1.dp
|
Keeps the target this far from the top edge. |
spacing |
null → arrowConfig.minVisibleDistance + 1.dp
|
Keeps this much space between the target and instruction card. |
Use GuideKitAutoScrollConfig(enabled = false) for fixed targets such as a floating action button or bottom navigation item.
GuideKit owns the current step and its next, previous, skip, and finish behavior. The host app can choose the starting step, show or hide progress, observe changes, and persist completion.
GuideKit(
steps = guideSteps,
initialStepIndex = 0,
showStepIndicator = true,
onStepChanged = { index ->
//Any implementation
//Ex:
analytics.track("guide_step_${index + 1}")
},
onSkipped = {
showGuide = false
//Any implementation
//Ex:
onboardingStore.markGuideCompleted()
},
onFinished = {
showGuide = false
//Any implementation
//Ex:
onboardingStore.markGuideCompleted()
},
modifier = Modifier.fillMaxSize(),
)| GuideKit option | Default | Purpose |
|---|---|---|
steps |
Required | Ordered list of guide steps. An empty list renders nothing. |
modifier |
Modifier |
Sizes and positions the overlay. Use fillMaxSize() at screen level. |
initialStepIndex |
0 |
Selects the first displayed step. Values outside the list are safely clamped. |
showStepIndicator |
true |
Shows or hides progress dots. |
style |
GuideKitStyle() |
Supplies global visual defaults. |
onStepChanged |
{} |
Reports the zero-based index whenever the visible step changes. |
onScrollBy |
{ 0f } |
Delegates automatic scrolling to the host scroll state. |
onSkipped |
null |
Shows the close button and runs when the user skips. null removes skip. |
onFinished |
Required | Runs when the primary button is pressed on the final step. |
Users can move back to the previous step by swiping the instruction card to the right. GuideKit consumes touches while the overlay is visible so the underlying screen is not activated accidentally.
| Feature | Description |
|---|---|
| Coachmark overlays | Dim the screen and focus attention on the active target. |
| Smart arrows | Draw dashed or solid arrows between instruction cards and targets. |
| Rounded highlights | Match cards, panels, buttons, and custom rounded components. |
| Circular highlights | Perfect for avatars, icons, FABs, and compact controls. |
| Instruction cards | Show titles, descriptions, highlighted text, buttons, and progress. |
| Auto-scroll | Automatically scroll to keep targets visible and clear of the card. |
| Per-step customization | Override arrows, highlights, cards, labels, and scrolling per step. |
| Internal step management | GuideKit owns next, previous, skip, finish, and current step state. |
| Callbacks | React to step changes, skipped tours, and completed tours. |
| Compose Multiplatform | Shared Kotlin API for Android and iOS Compose apps. |
| Platform | Status |
|---|---|
| Android | Supported |
| iOS | Supported |
| Desktop | Planned |
| Web | Planned |
Contributions are welcome. If you want to improve GuideKit, start with a small issue, bug report, or sample enhancement.
Before opening a pull request:
./gradlew build
./gradlew allTestsMIT License. See LICENSE.
GuideKit is built for developers who want onboarding to feel native, polished, and easy to maintain.
Beautiful coach marks for Compose Multiplatform
Build polished product tours, onboarding flows, spotlight overlays, smart arrows, instruction cards, and automatic scrolling in Kotlin Compose.
Website · Installation · Quick Start · Customization · Features · Platform Support
Add GuideKit to the Compose Multiplatform source set where you want to show tours.
implementation("io.github.tharukack:guidekit:1.1.0")For Kotlin Multiplatform projects:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.tharukack:guidekit:1.1.0")
}
}
}Add GuideKit to the main composable that occupies the full screen. It does not matter whether your screen uses a Scaffold, a Box, or another layout. The guide should be placed at the screen's top level so it can draw above all of its content.
Store the measured bounds of the composable you want to highlight. This minimal example uses rememberSaveable for guide visibility, but you can use any persistence approach you prefer, such as a ViewModel backed by DataStore or a database, to prevent completed guides from appearing again.
var targetBounds by remember { mutableStateOf<Rect?>(null) }
var showGuide by rememberSaveable { mutableStateOf(true) } // Replace with your persistence update if needed.Pass a bounds callback into the child composable that contains the target. Attach onGloballyPositioned to the target itself and send its measured bounds back through that callback.
@Composable
fun MainScreenContent(
onTargetBoundsChanged: (Rect?) -> Unit,
modifier: Modifier = Modifier,
) {
Column(modifier) {
Button(
onClick = { /* Your action */ },
modifier = Modifier.onGloballyPositioned { coordinates ->
onTargetBoundsChanged(coordinates.boundsInRoot())
},
) {
Text("Create item")
}
}
}Place GuideKit in the full-size parent composable, after the screen content. The parent owns targetBounds and passes its update callback into MainScreenContent. This lets a target inside any nested composable send its bounds to the parent without moving the target itself.
@Composable
fun MainScreen() {
var targetBounds by remember { mutableStateOf<Rect?>(null) }
var showGuide by rememberSaveable { mutableStateOf(true) }
Box(Modifier.fillMaxSize()) {
MainScreenContent(
onTargetBoundsChanged = { bounds -> targetBounds = bounds },
modifier = Modifier.fillMaxSize(),
)
if (showGuide) {
GuideKit(
steps = listOf(
GuideKitStep(
targetBounds = targetBounds,
title = "Create your first item",
description = "Tap here to start a new workflow.",
arrowConfigOverride = GuideKitArrowConfigOverride(
from = GuideKitAnchor.TopRight, // instruction box connection point
to = GuideKitAnchor.BottomCenter, // target connection point
),
),
),
onSkipped = { showGuide = false },
onFinished = { showGuide = false },
modifier = Modifier.fillMaxSize(),
)
}
}
}See the sample app for full configuration, styling, auto-scroll, arrow variants, rounded highlights, and circular highlights.
| Area | What you can configure |
|---|---|
| Multiple steps | Add targets and control the order of the tour. |
| Global and step-specific styles | Set shared visual defaults and override individual steps. |
| Step text | Customize titles, descriptions, highlighted phrases, and button labels. |
| Arrows | Configure anchors, line patterns, curves, strokes, and arrowheads. |
| Target highlights | Configure shapes, cutouts, padding, glows, and borders. |
| Instruction cards | Configure placement, size, shape, colors, borders, and shadows. |
| Automatic scrolling | Keep highlighted targets visible around the instruction card. |
| Navigation and callbacks | Configure progress, starting step, skip, finish, and analytics callbacks. |
Add one GuideKitStep for each composable you want to highlight. Every step receives the bounds measured from its own target composable, and the order of the list determines the order of the tour. GuideKit automatically handles next, previous, and finish navigation.
The following example creates a three-step guide. createButtonBounds, activityCardBounds, and profileButtonBounds are measured and sent to the parent using the same callback pattern shown in the Quick Start.
val guideSteps = listOf(
GuideKitStep(
targetBounds = createButtonBounds,
title = "Create your first item",
description = "Tap here to start a new workflow.",
arrowConfigOverride = GuideKitArrowConfigOverride(
from = GuideKitAnchor.TopRight,
to = GuideKitAnchor.BottomCenter,
),
),
GuideKitStep(
targetBounds = activityCardBounds,
title = "Track your progress",
description = "Your latest activity and important updates appear here.",
arrowConfigOverride = GuideKitArrowConfigOverride(
from = GuideKitAnchor.BottomLeft,
to = GuideKitAnchor.TopCenter,
),
),
GuideKitStep(
targetBounds = profileButtonBounds,
title = "Make it yours",
description = "Open your profile to customize your experience.",
arrowConfigOverride = GuideKitArrowConfigOverride(
from = GuideKitAnchor.TopRight,
to = GuideKitAnchor.CenterLeft,
),
),
)
GuideKit(
steps = guideSteps,
onSkipped = { showGuide = false },
onFinished = { showGuide = false },
modifier = Modifier.fillMaxSize(),
)Use GuideKitStyle to define the visual defaults for the entire guide. A step inherits the global arrow, target highlight, and instruction box styles when it does not provide its own override.
val guideStyle = GuideKitStyle(
accentColor = Color(0xFF7C4DFF),
overlayColor = Color.Black.copy(alpha = 0.72f),
titleColor = Color.White,
descriptionColor = Color.White.copy(alpha = 0.78f),
highlightedDescriptionColor = Color(0xFFFFC857),
stepIndicatorActiveColor = Color(0xFF7C4DFF),
stepIndicatorInactiveColor = Color.White.copy(alpha = 0.30f),
primaryButtonContainerColor = Color(0xFF7C4DFF),
primaryButtonContentColor = Color.White,
skipIconTint = Color.White,
arrowConfig = GuideKitArrowConfig(
lineStyle = GuideKitArrowLineStyle.SpacedDash,
),
targetHighlight = GuideKitTargetHighlightStyle(
shape = GuideKitTargetHighlightShape.RoundedRect,
),
instructionBox = GuideKitInstructionBoxStyle(
containerColor = Color(0xFF17132B),
contentColor = Color.White,
),
)
GuideKit(
steps = guideSteps,
style = guideStyle,
onFinished = { showGuide = false },
)| Global option | Default | Purpose |
|---|---|---|
accentColor |
Color(0xFF5ED5B3) |
Default color for arrows, highlight borders and glows, highlighted text, progress, and the primary button. |
overlayColor |
Color.Black.copy(alpha = 0.68f) |
Color and opacity of the full-screen dimming layer. |
titleColor |
null → MaterialTheme.colorScheme.onSurface
|
Title color inside the instruction card. |
descriptionColor |
null → MaterialTheme.colorScheme.onSurfaceVariant
|
Description color inside the instruction card. |
highlightedDescriptionColor |
null → accentColor
|
Color of phrases selected by descriptionHighlights. |
stepIndicatorActiveColor |
null → accentColor
|
Active progress indicator color. |
stepIndicatorInactiveColor |
null → MaterialTheme.colorScheme.outlineVariant
|
Inactive progress indicator color. |
primaryButtonContainerColor |
null → accentColor
|
Primary navigation button background. |
primaryButtonContentColor |
Color(0xFF062D25) |
Primary navigation button content color. |
skipIconTint |
null → MaterialTheme.colorScheme.onSurfaceVariant
|
Color of the optional close button. |
arrowConfig |
GuideKitArrowConfig() |
Default arrow configuration inherited by steps. |
targetHighlight |
GuideKitTargetHighlightStyle() |
Default target highlight inherited by steps. |
instructionBox |
GuideKitInstructionBoxStyle() |
Default instruction card inherited by steps. |
Override only the properties a step needs. Every property omitted from an override continues to use its global value:
GuideKitStep(
targetBounds = profileButtonBounds,
title = "Open your profile",
description = "Manage your preferences here.",
arrowConfigOverride = GuideKitArrowConfigOverride(
from = GuideKitAnchor.BottomCenter,
to = GuideKitAnchor.CenterRight,
lineStyle = GuideKitArrowLineStyle.Solid,
),
targetHighlightOverride = GuideKitTargetHighlightStyleOverride(
shape = GuideKitTargetHighlightShape.Circle,
),
)GuideKit resolves styles in this order: library defaults → global GuideKitStyle → explicit step overrides. A step override never resets an unrelated global color, shape, stroke, padding, or shadow.
All ordinary override properties default to null, meaning “inherit the global value.” For properties where null is itself meaningful, the matching ...Override property uses GuideKitOverride.Inherit by default and accepts GuideKitOverride.Value(...) for an explicit value—including null.
| Configuration | Global default | Step-specific override |
|---|---|---|
| Colors and overlay | GuideKitStyle |
Not overridden per step |
| Arrow | GuideKitStyle.arrowConfig |
GuideKitStep.arrowConfigOverride |
| Target highlight | GuideKitStyle.targetHighlight |
GuideKitStep.targetHighlightOverride |
| Instruction card | GuideKitStyle.instructionBox |
GuideKitStep.instructionBoxOverride |
| Text and button label | — | GuideKitStep |
| Auto-scroll | — | GuideKitStep.autoScroll |
Each step requires a title and description. You can highlight one or more exact phrases and change the primary button label. When primaryButtonText is not provided, GuideKit uses Next and changes the final step to Got it.
GuideKitStep(
targetBounds = activityCardBounds,
title = "Never miss an update",
description = "Important activity and urgent alerts appear here.",
descriptionHighlights = listOf(
"Important activity",
"urgent alerts",
),
primaryButtonText = "Continue",
)Use descriptionHighlights = listOf("phrase") for one highlighted phrase or add more list entries for several. Every phrase must exactly match text inside description. Highlighted text uses GuideKitStyle.highlightedDescriptionColor, falling back to the guide's accentColor.
| Step option | Default | Purpose |
|---|---|---|
targetBounds |
Required | Bounds of the composable being highlighted. |
title |
Required | Instruction card title. |
description |
Required | Instruction card description. |
primaryButtonText |
null → Next, or Got it on the last step |
Overrides the primary button label. |
descriptionHighlights |
emptyList() |
Highlights one or more exact phrases from description. |
instructionBottomPadding |
104.dp |
Bottom padding used by the default instruction-card placement. |
arrowConfigOverride |
null → inherit global arrow |
Overrides only supplied arrow properties. |
targetHighlightOverride |
null → inherit global highlight |
Overrides only supplied highlight properties. |
instructionBoxOverride |
null → inherit global instruction card |
Overrides only supplied card properties. |
autoScroll |
GuideKitAutoScrollConfig() |
Controls automatic scrolling for this step. |
The arrow starts from the instruction card's from anchor and points to the highlighted target's to anchor. All nine positions in GuideKitAnchor can be used on either side:
TopLeft TopCenter TopRight
CenterLeft Center CenterRight
BottomLeft BottomCenter BottomRight
Set a global arrow style in GuideKitStyle, then override placement or appearance for individual steps when the layout changes.
val guideStyle = GuideKitStyle(
arrowConfig = GuideKitArrowConfig(
lineStyle = GuideKitArrowLineStyle.MediumDash,
arrowHead = GuideKitArrowHead.TargetSide,
strokeCap = StrokeCap.Round,
),
)
val specialStep = GuideKitStep(
targetBounds = actionButtonBounds,
title = "Take action",
description = "Tap here when you are ready.",
arrowConfigOverride = GuideKitArrowConfigOverride(
from = GuideKitAnchor.TopRight,
to = GuideKitAnchor.CenterLeft,
curveSeed = 3,
lineStyle = GuideKitArrowLineStyle.Solid,
arrowHead = GuideKitArrowHead.BothSides,
strokes = listOf(
GuideKitArrowStroke(
width = 9,
color = Color.Black.copy(alpha = 0.22f),
),
GuideKitArrowStroke(
width = 5,
color = Color(0xFF7C4DFF),
),
),
),
)GuideKit translates the selected lineStyle into its internal drawing pattern, so applications do not need to manage raw dash lengths or gaps.
| Arrow option | Default | Purpose |
|---|---|---|
enabled |
true |
Shows or hides the arrow. |
from |
GuideKitAnchor.TopCenter |
Connection anchor on the instruction card. |
to |
GuideKitAnchor.BottomCenter |
Connection anchor on the highlighted target. |
curveSeed |
0 |
Produces a different deterministic curve for the same anchors. |
minVisibleDistance |
40.dp |
Hides the arrow when the card and target are too close. |
lineStyle |
GuideKitArrowLineStyle.SpacedDash |
Selects the arrow's line pattern. |
strokes |
Widths 9, 6, 2; black, accent, white |
Draws layered arrow-body strokes. A null color uses the global accent color. |
strokeCap |
StrokeCap.Round |
Controls the ends of arrow-body strokes. |
arrowHead |
GuideKitArrowHead.TargetSide |
Uses None, TargetSide, InstructionBoxSide, or BothSides. |
arrowHeadLength |
38 |
Controls arrowhead length. |
arrowHeadAngleDegrees |
30 |
Controls arrowhead angle. |
arrowHeadStrokes |
Widths 9, 6, 2; black, accent, white |
Draws layered arrowhead strokes. |
| Line style | Appearance |
|---|---|
Solid |
One continuous line. |
SpacedDash |
Medium-long dashes with clearly visible spacing between them. |
Dotted |
Closely spaced round dots when used with the default round stroke cap. |
ShortDash |
Short, frequent dashes for compact paths. |
MediumDash |
Medium-length dashes with tighter spacing than SpacedDash. |
LongDash |
Long, widely spaced dashes for larger layouts. |
DashDot |
Alternates a long dash with a dot. |
Target highlights can be rounded rectangles or circles. Define the common highlight globally and override it for targets such as avatars or floating action buttons.
val guideStyle = GuideKitStyle(
targetHighlight = GuideKitTargetHighlightStyle(
shape = GuideKitTargetHighlightShape.RoundedRect,
padding = 12,
cornerRadius = 24.dp,
borderColor = Color(0xFF7C4DFF),
),
)
val avatarStep = GuideKitStep(
targetBounds = avatarBounds,
title = "This is your profile",
description = "Tap your avatar to manage your account.",
targetHighlightOverride = GuideKitTargetHighlightStyleOverride(
shape = GuideKitTargetHighlightShape.Circle,
padding = 16,
glowStrokes = listOf(
GuideKitTargetHighlightStroke(
width = 30,
alpha = 0.14f,
color = Color(0xFF7C4DFF),
),
GuideKitTargetHighlightStroke(
width = 8,
alpha = 0.55f,
color = Color(0xFF7C4DFF),
),
),
),
)| Highlight option | Default | Purpose |
|---|---|---|
enabled |
true |
Enables or disables the target highlight. |
shape |
GuideKitTargetHighlightShape.RoundedRect |
Uses RoundedRect or Circle. |
cutoutEnabled |
true |
Clears the dimmed overlay inside the target when enabled. |
padding |
10 |
Adds space around the measured target bounds. |
cornerRadius |
28.dp |
Controls rounded-rectangle corners. |
glowStrokes |
Width/alpha: 30/0.11f, 22/0.18f, 14/0.30f, 8/0.45f
|
Adds layered glow strokes. Each default null color resolves to the global accent. |
borderColor |
null → accentColor
|
Controls the main target border color. |
borderWidth |
3 |
Controls the main target border width. |
innerBorderColor |
Color.White.copy(alpha = 0.7f) |
Controls the inner border color. |
innerBorderWidth |
1 |
Controls the inner border width. |
innerBorderInset |
2 |
Moves the inner border inward from the highlight edge. |
For a custom GuideKitTargetHighlightStroke, width and alpha are required, while color defaults to null (the global accent).
Use GuideKitInstructionBoxStyle to define the global instruction card. Use GuideKitInstructionBoxStyleOverride to change selected properties for one step while retaining every other global value.
val guideStyle = GuideKitStyle(
instructionBox = GuideKitInstructionBoxStyle(
alignment = Alignment.BottomCenter,
outerPadding = PaddingValues(20.dp),
contentPadding = PaddingValues(
horizontal = 24.dp,
vertical = 20.dp,
),
fillMaxWidth = false,
maxWidth = 420.dp,
shape = RoundedCornerShape(28.dp),
containerColor = Color(0xFF17132B),
contentColor = Color.White,
border = BorderStroke(
1.dp,
Color(0xFF7C4DFF).copy(alpha = 0.45f),
),
shadow = GuideKitInstructionBoxShadow(
elevation = 32.dp,
ambientColor = Color.Black.copy(alpha = 0.34f),
spotColor = Color.Black.copy(alpha = 0.34f),
),
),
)For a single step:
GuideKitStep(
targetBounds = wideCardBounds,
title = "Your weekly summary",
description = "Everything important appears in this section.",
instructionBottomPadding = 72.dp,
instructionBoxOverride = GuideKitInstructionBoxStyleOverride(
alignment = Alignment.BottomCenter,
fillMaxWidth = false,
maxWidth = 300.dp,
shape = RoundedCornerShape(20.dp),
),
)| Instruction card option | Default | Purpose |
|---|---|---|
alignment |
Alignment.BottomCenter |
Positions the card inside the full-screen GuideKit container. |
outerPadding |
null → start/end 18.dp, bottom instructionBottomPadding
|
Adds space around the card. |
contentPadding |
Horizontal 22.dp, vertical 24.dp
|
Adds space inside the card. |
fillMaxWidth |
true |
Makes the card use the available width. |
minWidth, maxWidth, minHeight, maxHeight
|
null |
Constrains the card size when provided. |
shape |
RoundedCornerShape(30.dp) |
Controls the card shape. |
containerColor |
null → MaterialTheme.colorScheme.surface
|
Controls the card background. |
contentColor |
null → MaterialTheme.colorScheme.onSurface
|
Controls the Surface content color. |
border |
BorderStroke(1.dp, Color(0xFF5ED5B3).copy(alpha = 0.28f)) |
Adds or removes the card border. Use null for no border. |
tonalElevation |
0.dp |
Controls Material tonal elevation. |
shadowElevation |
26.dp |
Controls Material shadow elevation. |
shadow |
GuideKitInstructionBoxShadow() |
Adds a customizable outer shadow. Use null to disable it. |
modifier |
Modifier |
Applies an additional modifier to the instruction card. |
The default GuideKitInstructionBoxShadow uses 30.dp elevation with black ambient and spot colors at 0.42f alpha.
To explicitly clear a nullable global value for one step, use its matching override-value property:
GuideKitStep(
targetBounds = targetBounds,
title = "A simpler card",
description = "This step removes the global border and shadow.",
instructionBoxOverride = GuideKitInstructionBoxStyleOverride(
borderOverride = GuideKitOverride.Value(null),
shadowOverride = GuideKitOverride.Value(null),
),
)The same pattern is available for nullable values such as outerPadding, size constraints, container and content colors, and target borderColor.
instructionBottomPadding belongs to GuideKitStep. It is used only when the instruction box's global or step-specific outerPadding is null.
Auto-scroll is enabled for each step by default, but the host screen must provide onScrollBy. GuideKit calculates the required distance and delegates the actual scroll operation to your scroll state.
val scrollState = rememberScrollState()
GuideKit(
steps = guideSteps,
onScrollBy = { deltaPx ->
scrollState.animateScrollBy(deltaPx)
},
onFinished = { showGuide = false },
)Tune or disable scrolling for an individual step:
GuideKitStep(
targetBounds = lowerCardBounds,
title = "More options below",
description = "GuideKit scrolls this target into a safe position.",
autoScroll = GuideKitAutoScrollConfig(
enabled = true,
minTopVisibleDistance = 56.dp,
spacing = 48.dp,
),
)| Auto-scroll option | Default | Purpose |
|---|---|---|
enabled |
true |
Enables or disables scrolling for the step. |
minTopVisibleDistance |
null → arrowConfig.minVisibleDistance + 1.dp
|
Keeps the target this far from the top edge. |
spacing |
null → arrowConfig.minVisibleDistance + 1.dp
|
Keeps this much space between the target and instruction card. |
Use GuideKitAutoScrollConfig(enabled = false) for fixed targets such as a floating action button or bottom navigation item.
GuideKit owns the current step and its next, previous, skip, and finish behavior. The host app can choose the starting step, show or hide progress, observe changes, and persist completion.
GuideKit(
steps = guideSteps,
initialStepIndex = 0,
showStepIndicator = true,
onStepChanged = { index ->
//Any implementation
//Ex:
analytics.track("guide_step_${index + 1}")
},
onSkipped = {
showGuide = false
//Any implementation
//Ex:
onboardingStore.markGuideCompleted()
},
onFinished = {
showGuide = false
//Any implementation
//Ex:
onboardingStore.markGuideCompleted()
},
modifier = Modifier.fillMaxSize(),
)| GuideKit option | Default | Purpose |
|---|---|---|
steps |
Required | Ordered list of guide steps. An empty list renders nothing. |
modifier |
Modifier |
Sizes and positions the overlay. Use fillMaxSize() at screen level. |
initialStepIndex |
0 |
Selects the first displayed step. Values outside the list are safely clamped. |
showStepIndicator |
true |
Shows or hides progress dots. |
style |
GuideKitStyle() |
Supplies global visual defaults. |
onStepChanged |
{} |
Reports the zero-based index whenever the visible step changes. |
onScrollBy |
{ 0f } |
Delegates automatic scrolling to the host scroll state. |
onSkipped |
null |
Shows the close button and runs when the user skips. null removes skip. |
onFinished |
Required | Runs when the primary button is pressed on the final step. |
Users can move back to the previous step by swiping the instruction card to the right. GuideKit consumes touches while the overlay is visible so the underlying screen is not activated accidentally.
| Feature | Description |
|---|---|
| Coachmark overlays | Dim the screen and focus attention on the active target. |
| Smart arrows | Draw dashed or solid arrows between instruction cards and targets. |
| Rounded highlights | Match cards, panels, buttons, and custom rounded components. |
| Circular highlights | Perfect for avatars, icons, FABs, and compact controls. |
| Instruction cards | Show titles, descriptions, highlighted text, buttons, and progress. |
| Auto-scroll | Automatically scroll to keep targets visible and clear of the card. |
| Per-step customization | Override arrows, highlights, cards, labels, and scrolling per step. |
| Internal step management | GuideKit owns next, previous, skip, finish, and current step state. |
| Callbacks | React to step changes, skipped tours, and completed tours. |
| Compose Multiplatform | Shared Kotlin API for Android and iOS Compose apps. |
| Platform | Status |
|---|---|
| Android | Supported |
| iOS | Supported |
| Desktop | Planned |
| Web | Planned |
Contributions are welcome. If you want to improve GuideKit, start with a small issue, bug report, or sample enhancement.
Before opening a pull request:
./gradlew build
./gradlew allTestsMIT License. See LICENSE.
GuideKit is built for developers who want onboarding to feel native, polished, and easy to maintain.