Skip to content

NadeemIqbal/segmented-control

Repository files navigation

SegmentedControl

An iOS-style segmented control for Compose Multiplatform — platform-appropriate visuals with three styles built in, an animated selection indicator, and full keyboard & RTL support.

Maven Central License Build Kotlin Android iOS Desktop Web

Why this library

Material 3's SingleChoiceSegmentedButtonRow always looks like Material — which feels out of place on iOS, where users expect the familiar UISegmentedControl. SegmentedControl gives you a platform-appropriate look from a single Compose codebase: ship the iOS style on Apple platforms, Material3 on Android, or Pill everywhere — without forking your UI code.

Platform support

Platform Supported Tested
Android ✅ (unit + UI)
iOS ✅ (UI, Skiko)
Desktop ✅ (unit + UI)
Web ✅ (compile + logic)

Installation

gradle/libs.versions.toml:

[libraries]
segmented-control = { module = "io.github.nadeemiqbal:segmented-control", version = "0.1.0" }

commonMain dependencies:

kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation(libs.segmented.control)
        }
    }
}

Quick start

var selected by remember { mutableStateOf(0) }

SegmentedControl(
    items = listOf("Day", "Week", "Month"),
    selectedIndex = selected,
    onSelectionChange = { selected = it },
    modifier = Modifier.fillMaxWidth(),
)

API examples

Icon + text segments

SegmentedControl(
    items = listOf(
        SegmentItem("List", Icons.Default.List),
        SegmentItem("Grid", Icons.Default.GridView),
    ),
    selectedIndex = selected,
    onSelectionChange = { selected = it },
)

Pick a style

SegmentedControl(
    items = items,
    selectedIndex = selected,
    onSelectionChange = { selected = it },
    style = SegmentedControlStyle.iOS,        // iOS | Material3 | Pill
)

Disabled segments

SegmentedControl(
    items = labels.mapIndexed { i, label -> SegmentItem(label, enabled = i != 2) },
    selectedIndex = selected,
    onSelectionChange = { selected = it },     // never fires for segment 2
)

Equal vs content width

// Equal: each segment shares the width evenly; long labels ellipsize.
SegmentedControl(items, selected, { selected = it }, width = SegmentedControlWidth.Equal, modifier = Modifier.fillMaxWidth())

// Content: each segment is as wide as its content; the control grows to fit.
SegmentedControl(items, selected, { selected = it }, width = SegmentedControlWidth.Content)

Icon-only

SegmentedControl(
    items = listOf(
        SegmentItem(icon = Icons.Default.FormatAlignLeft),
        SegmentItem(icon = Icons.Default.FormatAlignCenter),
        SegmentItem(icon = Icons.Default.FormatAlignRight),
    ),
    selectedIndex = selected,
    onSelectionChange = { selected = it },
)

Keyboard navigation — on Desktop and Web, focus the control (Tab) and use the Left/Right arrow keys to move the selection between enabled segments. No extra setup required.

Customization

SegmentedControl(
    items = items,
    selectedIndex = selected,
    onSelectionChange = { selected = it },
    style = SegmentedControlStyle.Pill,
    colors = SegmentedControlDefaults.colors(SegmentedControlStyle.Pill).copy(
        thumbColor = MaterialTheme.colorScheme.tertiary,
        selectedContentColor = MaterialTheme.colorScheme.onTertiary,
    ),
    shape = RoundedCornerShape(8.dp),
    animationSpec = spring(dampingRatio = Spring.DampingRatioLowBouncy),
)
  • styleiOS, Material3 or Pill. Drives the track/thumb shape, elevation and dividers.
  • colors — start from SegmentedControlDefaults.colors(style) and .copy(...) what you need, or build a SegmentedControlColors from scratch.
  • shape — overrides the track and thumb shape.
  • animationSpec — any AnimationSpec<Float>; defaults to a gentle spring.
  • widthEqual or Content.
  • enabledfalse makes the whole control inert and muted.

Comparison with alternatives

SegmentedControl Material 3 SingleChoiceSegmentedButtonRow Hand-rolled Row
iOS-native look ✅ built-in iOS style ❌ always Material ⚠️ you build it
Material look Material3 style ⚠️ you build it
Animated indicator ✅ spring, configurable ⚠️ basic ❌ DIY
Icon / text / both ⚠️ DIY
Disabled segments ✅ per-segment ⚠️ DIY
Equal & content width ⚠️ equal only ⚠️ DIY
Keyboard navigation ✅ arrow keys ❌ DIY
RTL ⚠️ DIY
Multiplatform ✅ Android/iOS/Desktop/Web

Be honest with yourself: if you only ship Android and you're all-in on Material, the built-in M3 control is fine. Reach for this library when you want an iOS-appropriate look, a richer animated indicator, or keyboard support — without maintaining your own component.

Roadmap

  • Scrollable / overflow mode for many segments
  • Badge support on segments
  • Optional haptic feedback on selection (Android/iOS)

Contributing

See CONTRIBUTING.md. Bug reports and feature requests are welcome via GitHub Issues.

License

Copyright 2026 Nadeem Iqbal

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

See LICENSE for the full text.

Releases

No releases published

Packages

 
 
 

Contributors

Languages