How to make Segmented Button in Jetpack Compose

How to make Segmented Button in Jetpack Compose

📍 What is a Segmented Button?

In Jetpack Compose, a Segmented Button is a set of buttons (or options) grouped together horizontally (sometimes vertically), visually connected to look like one piece.
Each button represents a choice.
👉 Think of it like tabs, but instead of navigating screens, you're picking options.
  • Each segment (button) looks "attached" to the next.
  • Selected segment usually has a different style (background color or elevation).
  • Non-selected segments look like normal buttons.

How to build a simple Dictionary App using Jetpack Compose?

Types of Segmented Buttons

There are mainly two kinds:
type description example
Single-choiceOnly one option can be selected at a time.Gender selection: Male / Female / Other
Multi-choiceMultiple options can be selected at the same time.Filters: Vegetables / Fruits / Dairy

Do you want to build a simple Camera App using Jetpack Compose?

1. Single Choice Segmented Button

➔ Only one button can be active at a time.

✅ When you select a new button, the previous one automatically deactivates.

Use case: Choosing delivery speed → Standard / Express / Overnight

2. Multi Choice Segmented Button

➔ Multiple buttons can be active at the same time.

✅ Selecting one option does not affect others.

Use case: Applying product filters → Organic / In Stock / Discounted

Let's build

  • Choose Meal Type → Breakfast / Lunch / Dinner (only one).
  • Select Filters → Vegan / Gluten-Free / High Protein (multiple).
  • Show only food items matching selected Meal + Filters.
✅ Start:

Breakfast selected by default

No filters selected → shows all breakfast items

✅ After selecting "Vegan" filter:

Only vegan breakfast items shown.

✅ Switch to "Dinner" + "Vegan":

Only dinner items that are vegan.

Single Choice Segmented Button

@Composable
fun SingleChoiceSegmentedButton(modifier: Modifier = Modifier) {
var selectedIndex by remember { mutableIntStateOf(0) }
val options = listOf("Day", "Month", "Week")
Box(
Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {

SingleChoiceSegmentedButtonRow {
options.forEachIndexed { index, label ->
SegmentedButton(
shape = SegmentedButtonDefaults.itemShape(
index = index,
count = options.size
),
onClick = { selectedIndex = index },
selected = index == selectedIndex,
label = { Text(label) }
)
}
}
}
}

OUTPU:

Single Choice Segmented Button

Multi Choice Segmented Button


@Composable
fun MultiChoiceSegmentedButton(modifier: Modifier = Modifier) {
val selectedOptions = remember {
mutableStateListOf(false, false, false)
}
val options = listOf("Walk", "Ride", "Drive")
Box(Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
){
MultiChoiceSegmentedButtonRow {
options.forEachIndexed { index, label ->
SegmentedButton(
shape = SegmentedButtonDefaults.itemShape(
index = index,
count = options.size
),
checked = selectedOptions[index],
onCheckedChange = {
selectedOptions[index] = !selectedOptions[index]
},
icon = { SegmentedButtonDefaults.Icon(selectedOptions[index]) },
label = {
when (label) {
"Walk" -> Icon(
imageVector =
Icons.AutoMirrored.Filled.DirectionsWalk,
contentDescription = "Directions Walk"
)
"Ride" -> Icon(
imageVector =
Icons.Default.DirectionsBus,
contentDescription = "Directions Bus"
)
"Drive" -> Icon(
imageVector =
Icons.Default.DirectionsCar,
contentDescription = "Directions Car"
)
}
}
)
}
}
}
}

OUTPUT:

Multi Choice Segmented Button

Switching List vs Grid View using Segmented Buttons?


@Composable
fun SegmentedButtonSwitchViews() {
var selectedOption by remember { mutableIntStateOf(0) }
Column (Modifier.fillMaxSize()
.systemBarsPadding(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
){
SingleChoiceSegmentedButtonRow(
modifier = Modifier.padding(16.dp)
) {
SegmentedButton(
selected = selectedOption == 0,
onClick = { selectedOption = 0 },
shape = SegmentedButtonDefaults.itemShape(index = 0, count = 2),
icon = {
Icon(imageVector = Icons.Default.ViewList, contentDescription = "List View")
}
) {
Text("List")
}
SegmentedButton(
selected = selectedOption == 1,
onClick = { selectedOption = 1 },
shape = SegmentedButtonDefaults.itemShape(index = 1, count = 2),
icon = {
Icon(imageVector = Icons.Default.GridView, contentDescription = "Grid View")
}
) {
Text("Grid")
}
}

Spacer(modifier = Modifier.height(16.dp))

if (selectedOption == 0) {
ListView()
} else {
GridView()
}
}
}
@Composable
fun ListView() {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.fillMaxSize()
) {
items(10) { index ->
Card(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp),
colors = CardDefaults.cardColors(containerColor = Color(0xFFF5F5F5))
) {
Text(
text = "List Item #$index",
modifier = Modifier.padding(16.dp)
)
}
}
}
}
@Composable
fun GridView() {
LazyVerticalGrid(
columns = GridCells.Fixed(2),
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(10) { index ->
Card(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f), // Makes it square
colors = CardDefaults.cardColors(containerColor = Color(0xFFE0F7FA))
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(text = "Grid Item #$index")
}
}
}
}
}

OUTPUT:

Switching List using Segmented Buttons
Switching Grid View using Segmented Buttons

A real Example of  Segmented Button with simple Button

  • Scaffold: A layout component that provides structure for the screen, including a top bar. Here, the top bar contains the title "Food Delivery Filter".
  • foodItems is a list of sample food items, each represented by the FoodItem data class. These items are used to simulate a food database for the filter options.
  • name: The name of the food.
  • mealType: The type of meal (e.g., "Breakfast", "Lunch", "Dinner").
  • dietaryTags: A list of dietary tags that the food can have (e.g., "Vegan", "Gluten-Free").
  • selectedMeal: A state variable that tracks the currently selected meal type (e.g., Breakfast, Lunch, Dinner). It is initialized to "Breakfast".
  • selectedDietaryOptions: A state variable that tracks the selected dietary options (Vegan, Gluten-Free, etc.). It is initialized to an empty set (setOf<String>()).
  • filteredFoodItems: This filters the foodItems based on the selected meal and dietary tags. It checks if the food item’s mealType matches the selected meal (selectedMeal) and if all the selected dietary options (selectedDietaryOptions) are contained in the food.dietaryTags.
  • Dietary Filter Selection is done using another Row of Buttons.
  • Each Button represents a dietary filter ("Vegan", "Gluten-Free", "High Protein").
  • When a Button is clicked, it adds or removes the dietary filter from selectedDietaryOptions.
  • The selected button is styled with a different background color (Color(0xFF03DAC5)).
  • Filtered Food Items List is displayed using a LazyColumn, which efficiently renders the filtered food items.
  • If no items match the selected filters, it shows a message ("No food items match your selection").
  • For each food in filteredFoodItems, a Card is displayed with the food name and its dietary tags.
// ------------ Food Item Data Class ----------------
data class FoodItem(
val name: String,
val mealType: String, // Breakfast, Lunch, Dinner
val dietaryTags: List<String> // Vegan, Gluten-Free, High Protein
)

// ------------ Fake Food Items ----------------------
val foodItems = listOf(
FoodItem("Pancakes", "Breakfast", listOf("Gluten-Free")),
FoodItem("Oatmeal Bowl", "Breakfast", listOf("Vegan", "Gluten-Free", "High Protein")),
FoodItem("Oatmeal Bowl", "Lunch", listOf("Vegan", "Gluten-Free", "High Protein")),
FoodItem("Grilled Chicken Salad", "Lunch", listOf("Gluten-Free", "High Protein")),
FoodItem("Veggie Wrap", "Lunch", listOf("Vegan", "High Protein")),
FoodItem("Steak Dinner", "Dinner", listOf("High Protein")),
FoodItem("Vegan Curry", "Dinner", listOf("Vegan", "Gluten-Free")),
FoodItem("Protein Smoothie", "Breakfast", listOf("Vegan", "High Protein")),
)

// ------------ UI Screen ----------------------------
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FoodDeliverySegmentedButton() {
val mealOptions = listOf("Breakfast", "Lunch", "Dinner")
var selectedMeal by remember { mutableStateOf(mealOptions[0]) }

val dietaryOptions = listOf("Vegan", "Gluten-Free", "High Protein")
var selectedDietaryOptions by remember { mutableStateOf(setOf<String>()) }

val filteredFoodItems = foodItems.filter { food ->
food.mealType == selectedMeal && selectedDietaryOptions.all { tag ->
food.dietaryTags.contains(tag)
}
}

Scaffold(
topBar = {
TopAppBar(
title = { Text("Food Delivery Filter") }
)
}
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.padding(16.dp)
) {
// ---------------- Meal Type Selection ----------------
Text("Select Meal Type:", fontSize = 20.sp, fontWeight = FontWeight.Bold)

Row(modifier = Modifier.padding(vertical = 8.dp)) {
mealOptions.forEach { meal ->
Button(
onClick = { selectedMeal = meal },
colors = ButtonDefaults.buttonColors(
containerColor = if (selectedMeal == meal) Color(0xFF6200EE) else Color.LightGray,
contentColor = Color.White
),
shape = when (meal) {
mealOptions.first() -> RoundedCornerShape(topStart = 12.dp, bottomStart = 12.dp)
mealOptions.last() -> RoundedCornerShape(topEnd = 12.dp, bottomEnd = 12.dp)
else -> RectangleShape
},
modifier = Modifier
.weight(1f)
.padding(horizontal = 2.dp)
) {
Text(meal)
}
}
}

Spacer(modifier = Modifier.height(16.dp))

// ---------------- Dietary Filter Selection ----------------
Text("Select Dietary Filters:", fontSize = 20.sp, fontWeight = FontWeight.Bold)

Row(modifier = Modifier.padding(vertical = 8.dp)) {
dietaryOptions.forEach { diet ->
val isSelected = diet in selectedDietaryOptions

Button(
onClick = {
selectedDietaryOptions = if (isSelected) {
selectedDietaryOptions - diet
} else {
selectedDietaryOptions + diet
}
},
colors = ButtonDefaults.buttonColors(
containerColor = if (isSelected) Color(0xFF03DAC5) else Color.LightGray,
contentColor = Color.Black
),
shape = when (diet) {
dietaryOptions.first() -> RoundedCornerShape(topStart = 12.dp, bottomStart = 12.dp)
dietaryOptions.last() -> RoundedCornerShape(topEnd = 12.dp, bottomEnd = 12.dp)
else -> RectangleShape
},
modifier = Modifier
.weight(1f)
.padding(horizontal = 2.dp)
) {
Text(diet)
}
}
}

Spacer(modifier = Modifier.height(24.dp))

// ---------------- Filtered Food Items List ----------------
Text(
text = "Available Food Items:",
fontSize = 22.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(bottom = 8.dp)
)

LazyColumn {
if (filteredFoodItems.isEmpty()) {
item {
Text("No food items match your selection.", color = Color.Red)
}
} else {

items(filteredFoodItems) { food ->
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
colors = CardDefaults.cardColors(
containerColor = Color(0xFFF1F1F1)
)
) {
Column(modifier = Modifier.padding(12.dp)) {
Text(food.name, fontSize = 18.sp, fontWeight = FontWeight.Bold)
Text(
text = "Tags: ${food.dietaryTags.joinToString(", ")}",
fontSize = 14.sp,
color = Color.Gray
)
}
}
}
}
}
}
}
}

OUTPUT:

A real Example of  Segmented Button with simple Button


Previous Post Next Post

Contact Form