📍 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-choice | Only one option can be selected at a time. | Gender selection: Male / Female / Other |
Multi-choice | Multiple 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:
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:
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:
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
)
}
}
}
}
}
}
}
}