Jetpack Compose SearchBar

A Jetpack Compose SearchBar is the composable Jetpack reaches for whenever a screen needs a dedicated place for a user to type a query and immediately see matching suggestions or results appear underneath it. Jetpack expands a Jetpack Compose SearchBar into a full-screen search experience the moment a user taps into it, rather than leaving the input sitting inline the way an ordinary text field does. Jetpack Compose developers reach for a SearchBar whenever a screen needs both a query field and a scrollable list of suggestions tied to that same query, such as a product catalog search or a contacts search. Jetpack ships this SearchBar composable ready to use out of the box, and Jetpack keeps a Jetpack Compose SearchBar behaving the same way across every screen that uses one. Jetpack maintains the SearchBar composable inside the same material3 library that ships every other Jetpack Compose component, so nothing extra needs installing before a Jetpack Compose SearchBar shows up on screen.

This blog walks through every parameter the SearchBar composable in Jetpack Compose exposes - query, onQueryChange, onSearch, active, onActiveChange, placeholder, leadingIcon, trailingIcon, colors, tonalElevation, shadowElevation, shape, windowInsets, enabled, and the content lambda that renders search suggestions. Every code sample below is fully self-contained, so you can paste it into an online Kotlin compiler and watch a Jetpack Compose SearchBar behave exactly as Jetpack describes it.

What Is the Jetpack Compose SearchBar Composable

The SearchBar composable is a Material 3 search input that combines a text field with an expandable panel beneath it, letting a Jetpack Compose SearchBar show suggestions or results without navigating to a different screen. A Jetpack Compose SearchBar is stateless by design, meaning Jetpack never stores the query text or the expanded state on its own - it only reports changes upward and expects the caller to hold both values. Jetpack ships the SearchBar composable in the material3 package behind an experimental opt-in, so a Jetpack Compose SearchBar requires the OptIn annotation for ExperimentalMaterial3Api before it compiles.

Here is the simplest jetpack compose searchbar example you will run into, showing Jetpack's SearchBar composable with its five required parameters - query, onQueryChange, onSearch, active, and onActiveChange:

kotlin
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SearchBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BasicSearchBarExample() {
    var query by remember { mutableStateOf("") }
    var active by remember { mutableStateOf(false) }

    SearchBar(
        query = query,
        onQueryChange = { query = it },
        onSearch = { active = false },
        active = active,
        onActiveChange = { active = it }
    ) {}
}

Running this Jetpack Compose SearchBar example draws a rounded search field near the top of the screen, and tapping into it expands the Jetpack Compose SearchBar to fill the available space above the keyboard. The query variable holds the text currently shown inside a Jetpack Compose SearchBar, while active tracks whether that same SearchBar is currently expanded into its full search mode. Both variables live outside the SearchBar in remember blocks, which is why a Jetpack Compose SearchBar is called a "controlled" composable - Jetpack never lets the SearchBar own either value, it only displays whatever query and active state you hand back to it. The trailing lambda passed to SearchBar is its content slot, and this basic jetpack compose searchbar example leaves that content slot empty since suggestions come later in this blog.

Jetpack designed the SearchBar composable so the field and its expanded panel animate together whenever a user taps in or out of a Jetpack Compose SearchBar. A SearchBar stays this predictable no matter which screen it appears on, because Jetpack keeps the same SearchBar behavior everywhere a Jetpack Compose SearchBar is used. See the official Material3 package reference for the full Jetpack Compose SearchBar signature Jetpack maintains alongside its other Material components.

Managing the Query With query, onQueryChange, and onSearch

The query parameter is a String holding exactly what currently sits inside a Jetpack Compose SearchBar, and onQueryChange is the lambda Jetpack Compose invokes with the updated String on every keystroke. Together query and onQueryChange form the core of any Jetpack Compose SearchBar, since nothing else in the composable works without a query value to search against. The onSearch parameter is a separate lambda that also receives the current query String, but Jetpack only calls onSearch once a user submits the search - typically by pressing the keyboard's search action button - rather than on every keystroke the way onQueryChange does.

kotlin
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SearchBar
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun QuerySearchBarExample() {
    var query by remember { mutableStateOf("") }
    var active by remember { mutableStateOf(false) }
    var lastSearch by remember { mutableStateOf("") }

    SearchBar(
        query = query,
        onQueryChange = { query = it },
        onSearch = {
            lastSearch = it
            active = false
        },
        active = active,
        onActiveChange = { active = it }
    ) {
        Text("Last search: $lastSearch")
    }
}

Every keystroke inside this Jetpack Compose SearchBar runs onQueryChange, updating the query variable immediately so a Jetpack Compose SearchBar can filter a list live as a user types. Pressing the keyboard's search key instead fires onSearch with that same query text, storing it in lastSearch and collapsing the Jetpack Compose SearchBar by setting active to false. Jetpack does not require onSearch to collapse the SearchBar - a Jetpack Compose SearchBar can just as easily stay expanded after a search to keep showing results in the same panel. Because onSearch only receives a String, any actual searching - hitting a database, filtering a list, or calling an API - is logic the caller writes inside that lambda, since a Jetpack Compose SearchBar never performs the search itself.

Expanding and Collapsing the SearchBar With active and onActiveChange

The active parameter is a Boolean controlling whether a Jetpack Compose SearchBar is currently expanded into its full search mode, and onActiveChange is the lambda Jetpack calls whenever that expanded state should change. This active search state is what separates a Jetpack Compose SearchBar from a plain text field - toggling active true grows the SearchBar to cover the screen below it and reveals the content lambda, while toggling it false collapses everything back down.

kotlin
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SearchBar
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ActiveStateSearchBar() {
    var query by remember { mutableStateOf("") }
    var active by remember { mutableStateOf(false) }

    SearchBar(
        query = query,
        onQueryChange = { query = it },
        onSearch = { active = false },
        active = active,
        onActiveChange = { active = it }
    ) {
        Text(if (active) "Search is expanded" else "Search is collapsed")
    }
}

Tapping into this Jetpack Compose SearchBar triggers onActiveChange with true, expanding the SearchBar and immediately showing "Search is expanded" inside its content panel. Tapping the back arrow that Jetpack automatically draws while a Jetpack Compose SearchBar is active sends onActiveChange false instead, collapsing the SearchBar back to its resting height. Because active and onActiveChange are separate from query and onQueryChange, a Jetpack Compose SearchBar can hold onto typed text even after collapsing, or clear it on collapse, depending entirely on what the caller does inside onActiveChange. Jetpack relies on this active flag for every Jetpack Compose SearchBar, so understanding active and onActiveChange is a key step toward using a SearchBar correctly.

Adding a Placeholder to the Jetpack Compose SearchBar

The placeholder parameter accepts an optional composable lambda, and Jetpack shows it inside a Jetpack Compose SearchBar only while query is empty, whether or not the SearchBar is active. A placeholder inside a Jetpack Compose SearchBar is meant as a short hint describing what the search covers, not as a label that stays visible once a user starts typing.

kotlin
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SearchBar
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PlaceholderSearchBar() {
    var query by remember { mutableStateOf("") }
    var active by remember { mutableStateOf(false) }

    SearchBar(
        query = query,
        onQueryChange = { query = it },
        onSearch = { active = false },
        active = active,
        onActiveChange = { active = it },
        placeholder = { Text("Search products") }
    ) {}
}

This jetpack compose searchbar example shows "Search products" grayed out inside the field until a user types a single character, at which point the placeholder disappears completely from the Jetpack Compose SearchBar. Jetpack keeps the placeholder visible even while the SearchBar is active, as long as query is still an empty String, so a Jetpack Compose SearchBar can prompt a user for what to type even after it has expanded. Because placeholder is just a composable lambda, a Jetpack Compose SearchBar can style that hint text with a custom color or icon alongside it rather than relying on plain Text alone.

Leading and Trailing Icons in the SearchBar

The leadingIcon and trailingIcon parameters each accept an optional composable lambda, letting a Jetpack Compose SearchBar place an icon at the start or end of the input row. Jetpack tints both icons to match the Jetpack Compose SearchBar's current color scheme, and Jetpack commonly swaps the leading icon between a search glyph and a back arrow depending on whether the SearchBar is active.

kotlin
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.SearchBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun IconSearchBar() {
    var query by remember { mutableStateOf("") }
    var active by remember { mutableStateOf(false) }

    SearchBar(
        query = query,
        onQueryChange = { query = it },
        onSearch = { active = false },
        active = active,
        onActiveChange = { active = it },
        leadingIcon = {
            if (active) {
                IconButton(onClick = { active = false }) {
                    Icon(Icons.Filled.ArrowBack, contentDescription = "Collapse search")
                }
            } else {
                Icon(Icons.Filled.Search, contentDescription = null)
            }
        },
        trailingIcon = {
            if (query.isNotEmpty()) {
                IconButton(onClick = { query = "" }) {
                    Icon(Icons.Filled.Search, contentDescription = "Clear search")
                }
            }
        }
    ) {}
}

The leading icon on this Jetpack Compose SearchBar switches from a magnifying glass to a back arrow the instant active turns true, giving a user an obvious way to collapse the Jetpack Compose SearchBar again. The trailing icon only appears once query holds at least one character, acting as a clear button that resets the Jetpack Compose SearchBar's query back to an empty String when tapped. Because leadingIcon and trailingIcon are ordinary composable lambdas wrapped around whatever conditional logic is needed, a Jetpack Compose SearchBar can swap either icon based on active, query, or any other state the screen tracks. Jetpack reserves layout space for whichever icons are present, so an icon showing up or disappearing never shifts the typed text inside the Jetpack Compose SearchBar.

Displaying Search Suggestions With the Content Lambda

The trailing content lambda passed to SearchBar is where a Jetpack Compose SearchBar renders search suggestions, recent searches, or live results while the SearchBar is active. Jetpack scopes this content lambda to ColumnScope, so a Jetpack Compose SearchBar typically fills it with a scrollable list built from LazyColumn, with each row reacting to the current query.

kotlin
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ListItem
import androidx.compose.material3.SearchBar
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SuggestionSearchBar() {
    val allFruits = listOf("Apple", "Banana", "Cherry", "Mango", "Orange")
    var query by remember { mutableStateOf("") }
    var active by remember { mutableStateOf(false) }
    val suggestions = allFruits.filter { it.contains(query, ignoreCase = true) }

    SearchBar(
        query = query,
        onQueryChange = { query = it },
        onSearch = { active = false },
        active = active,
        onActiveChange = { active = it }
    ) {
        LazyColumn {
            items(suggestions) { fruit ->
                ListItem(headlineContent = { Text(fruit) })
            }
        }
    }
}

Typing "an" into this Jetpack Compose SearchBar recalculates the suggestions list on every keystroke, since suggestions filters allFruits against whatever query currently holds. The content lambda then draws each matching fruit as a ListItem row inside a LazyColumn, so the Jetpack Compose SearchBar shows only the suggestions that actually match what a user has typed so far. Jetpack renders this content lambda only while the SearchBar is active, meaning the suggestions panel disappears entirely once a user collapses the Jetpack Compose SearchBar, keeping the resting state of the screen uncluttered. Jetpack expects most search suggestions to come from filtering an existing list this way on a Jetpack Compose SearchBar, rather than rebuilding the list from scratch on every keystroke.

Shape, Elevation, Insets, Colors, and Other SearchBar Parameters

A handful of smaller parameters round out what a Jetpack Compose SearchBar composable can do, each useful in narrower situations that don't need a full section of their own.

  • shape: a Shape controlling the silhouette of the resting Jetpack Compose SearchBar, defaulting to SearchBarDefaults.inputFieldShape, a fully rounded pill.
  • tonalElevation and shadowElevation: Dp values controlling how strongly a Jetpack Compose SearchBar appears to float above the surface behind it.
  • colors: a SearchBarColors object built through SearchBarDefaults.colors(), letting a Jetpack Compose SearchBar override its container color and the colors used inside the underlying input field.
  • windowInsets: a WindowInsets value telling a Jetpack Compose SearchBar how much padding to reserve for system bars once it expands to fill the screen.
  • enabled: a Boolean that turns a Jetpack Compose SearchBar's input field on or off, graying it out and blocking focus when set to false.
  • modifier: the standard Modifier parameter for sizing, padding, or positioning a Jetpack Compose SearchBar within its parent layout.
  • Jetpack's newer Material3 releases also expose a DockedSearchBar composable, a variant that expands only within its own bounds instead of covering the whole screen, useful on wider layouts like tablets. Some newer Material3 versions additionally offer a SearchBar overload built around an inputField slot and an expanded parameter instead of active, though the query, onSearch, and onActiveChange concepts described in this blog carry over directly to that newer shape.
kotlin
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SearchBar
import androidx.compose.material3.SearchBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun StyledSearchBar() {
    var query by remember { mutableStateOf("") }
    var active by remember { mutableStateOf(false) }

    SearchBar(
        query = query,
        onQueryChange = { query = it },
        onSearch = { active = false },
        active = active,
        onActiveChange = { active = it },
        shape = RoundedCornerShape(12.dp),
        tonalElevation = 4.dp,
        colors = SearchBarDefaults.colors(
            containerColor = MaterialTheme.colorScheme.secondaryContainer
        ),
        enabled = true,
        modifier = Modifier
    ) {}
}

Passing RoundedCornerShape(12.dp) squares off the corners of this Jetpack Compose SearchBar into a softer rectangle instead of the default rounded pill shape. Raising tonalElevation to 4.dp makes the resting SearchBar sit slightly darker against its background, and setting containerColor to the theme's secondaryContainer tints that same background with a different hue. Jetpack rarely asks a developer to touch every one of these parameters on the same Jetpack Compose SearchBar, but each stays available for a search bar ui jetpack compose layout that needs it.

Comparing SearchBar Parameters

ParameterTypeDefaultPurpose
queryStringrequiredThe text currently shown inside the SearchBar
onQueryChange(String) -> UnitrequiredRuns whenever the typed query changes
onSearch(String) -> UnitrequiredRuns when a search is submitted
activeBooleanrequiredWhether the SearchBar is expanded into search mode
onActiveChange(Boolean) -> UnitrequiredRuns when the expanded state should change
placeholder@Composable (() -> Unit)?nullHint shown only while query is empty
leadingIcon / trailingIcon@Composable (() -> Unit)?nullIcon placed at the start or end of the input row
colorsSearchBarColorsSearchBarDefaults.colors()Sets container and input field colors
shapeShapeSearchBarDefaults.inputFieldShapeControls the resting SearchBar's silhouette
tonalElevation / shadowElevationDpSearchBarDefaults valuesControls how strongly the SearchBar floats above the surface
windowInsetsWindowInsetsSearchBarDefaults.windowInsetsReserves padding for system bars when expanded
enabledBooleantrueTurns the SearchBar's input on or off
content@Composable ColumnScope.() -> UnitrequiredRenders suggestions or results while active

Reading across this table shows how a Jetpack Compose SearchBar layers query handling, expansion state, styling, and a suggestions panel on top of one composable. Once these pieces are familiar, reaching for a Jetpack Compose SearchBar becomes a quick, repeatable decision for any search input a screen needs.

Practical Use Case: Building a Filterable Search Screen

A common pattern combines query, active, and the content lambda into one working search screen, filtering a real list of items as a user types into the Jetpack Compose SearchBar and showing the filtered results as tappable suggestions.

kotlin
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.SearchBar
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ProductSearchScreen() {
    val catalog = listOf("Laptop", "Keyboard", "Monitor", "Mouse", "Headset", "Webcam")
    var query by remember { mutableStateOf("") }
    var active by remember { mutableStateOf(false) }
    val results = catalog.filter { it.contains(query, ignoreCase = true) }

    SearchBar(
        query = query,
        onQueryChange = { query = it },
        onSearch = { active = false },
        active = active,
        onActiveChange = { active = it },
        placeholder = { Text("Search catalog") },
        leadingIcon = { Icon(Icons.Filled.Search, contentDescription = null) },
        modifier = Modifier.fillMaxWidth()
    ) {
        LazyColumn {
            items(results) { product ->
                ListItem(headlineContent = { Text(product) })
            }
        }
    }
}

Typing "mo" into this Jetpack Compose SearchBar narrows the results list down to Monitor and Mouse, since results filters catalog against the current query on every keystroke. The leadingIcon stays fixed as a search glyph in this screen since it only ever needs to prompt a user into typing, and placeholder guides that same user with "Search catalog" before any text is entered into the field. This compose search bar example demonstrates the pattern most real search screens follow - a Jetpack Compose SearchBar holding query and active state, paired with a derived results list that recalculates itself on every change, exactly the kind of android compose search bar behavior a product listing or contacts screen relies on. See Google's Material3 search guidelines for more on designing this expand-and-filter search pattern.

Complete Jetpack Compose SearchBar Example

This final example pulls several pieces together - query, active, placeholder, leadingIcon, trailingIcon, and the content lambda - into one runnable screen built entirely around the Jetpack Compose SearchBar composable.

kotlin
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SearchBar
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MaterialTheme {
                Surface {
                    CitySearchScreen()
                }
            }
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CitySearchScreen() {
    val cities = listOf("Austin", "Boston", "Chicago", "Denver", "Portland", "Seattle")
    var query by remember { mutableStateOf("") }
    var active by remember { mutableStateOf(false) }
    val matches = cities.filter { it.contains(query, ignoreCase = true) }

    SearchBar(
        query = query,
        onQueryChange = { query = it },
        onSearch = { active = false },
        active = active,
        onActiveChange = { active = it },
        placeholder = { Text("Search cities") },
        leadingIcon = {
            if (active) {
                IconButton(onClick = { active = false }) {
                    Icon(Icons.Filled.ArrowBack, contentDescription = "Collapse search")
                }
            } else {
                Icon(Icons.Filled.Search, contentDescription = null)
            }
        },
        trailingIcon = {
            if (query.isNotEmpty()) {
                IconButton(onClick = { query = "" }) {
                    Icon(Icons.Filled.Search, contentDescription = "Clear search")
                }
            }
        },
        modifier = Modifier.fillMaxWidth()
    ) {
        LazyColumn {
            items(matches) { city ->
                ListItem(headlineContent = { Text(city) })
            }
        }
    }
}

Launching this Jetpack Compose SearchBar example shows a rounded search field labeled "Search cities" sitting near the top of the screen, ready to expand the instant a user taps into it. Typing "bo" narrows matches down to just Boston, and the leading icon flips from a search glyph to a back arrow the moment active turns true, giving a clear way to collapse the Jetpack Compose SearchBar again. Every piece covered above - query, active, placeholder, leadingIcon, trailingIcon, and the content lambda - comes together in this one screen, the kind of validated Jetpack Compose SearchBar a real city-search feature is meant to build. Jetpack intends every SearchBar built this way to feel consistent, whether it searches a handful of cities or a full product catalog, so the same query, active, and content knowledge carries over the next time a Jetpack Compose SearchBar shows up in a different part of an app.