The ability to search for content within an app is a common feature, in fact, you’ll find it somewhere within most applications on your device. On Android, a common UI component we see for this functionality is a floating search bar, placed in a prominent part of the screen. In some cases, this also provides search recommendations to the user to streamline the search process. The Jetpack Compose Material3 package provides access to a DockedSearchBar composable that offers this exact functionality and in this blog post, we’re going to learn how to utilise it in our own apps.

The DockedSearchBar composable allows us to display a floating Search component, which expands to display selectable recommendations. As mentioned above, this is a common pattern that we see in many applications, with this composable offering an out-of-the-box solution. In the last post we looked at the SearchBar composable, which differs from that of the DockedSearchBar. The key here is in the name, this variant of the search bar is docked alongside the rest of the content on the screen, as opposed to consuming available space once the search bar comes into focus.
We can see this difference when we put the two implementations side by side. The docked implementation (left) only consumes the required space within the content area, which will still expand to display the results – depending on your implementation, this will either push content beneath it or display over the top. This is very different from the standard search bar (right) that will fill the entire screen when in focus.

The DockedSearchBar composable provides enough customisation to control the look and feel of the component, along with using a slot-based approach for us to provide the input field for use.
@Composable
fun DockedSearchBar(
inputField: @Composable () -> Unit,
expanded: Boolean,
onExpandedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
shape: Shape = SearchBarDefaults.dockedShape,
colors: SearchBarColors = SearchBarDefaults.colors(),
tonalElevation: Dp = SearchBarDefaults.TonalElevation,
shadowElevation: Dp = SearchBarDefaults.ShadowElevation,
content: @Composable ColumnScope.() -> Unit,
)
The component handles most of the internals for us – with two key parts needing to be provided by ourselves.
- inputField – this is the input composable that represents the search field for content input
- content – this is the content area used to display recommendations when the search bar is expanded
Outside of these fields, there are a collection of other properties that are used to determine the current state of the DockedSearchBar. For example, when the search bar is in an expanded state, the content of the composable will be displayed beneath the input field. In the case of the DockedSearchBar, this can either pushed other content out of place or show over the top of it.
To be able to manage this, we need to provide some arguments to the composable that will be used to manage this state. To start with, the expanded argument is used to depict whether the SearchBar is in an expanded state (which will decide whether the content area will be shown), along with the onExpandedChange argument which is used to provide the implementation with an updated value for the expanded state (which can then be used to reflect our own state implementation).
var expanded by remember { mutableStateOf(false) }
DockedSearchBar(
modifier = Modifier.fillMaxWidth(),
expanded = expanded,
onExpandedChange = {
expanded = it
}
)
Alongside managing this expanded state, we need to provide the inputField that is to be used for the input area of the DockedSearchBar. Outside of following the slot-based approach for composables, this allows the composable to follow the concepts of state hoisting, allowing us to completely manage the state concepts for the input field of the SearchBar.
var expanded by remember { mutableStateOf(false) }
var query by remember { mutableStateOf<String?>(null) }
DockedSearchBar(
modifier = Modifier.fillMaxWidth(),
expanded = expanded,
onExpandedChange = {
expanded = it
},
inputField = {
}
)
To make this simpler, the SearchBarDefaults class provides us access to an InputField composable – this gives us access to a composable specifically implemented for use with the SearchBar. It is not required to utilise this specific composable, but it is provided as a convenience composable specifically for a search-based input field. This composable takes some key arguments that are used to configure it for use within the DockedSearchBar:
- expanded and onExpandedChange – used to manage the expanded state of the field
- query and onQueryChange – used to manage the state of the query displayed in the field
Alongside these core properties, you’ll also notice the support for standard field arguments such as the placeholder, leadingIcon and trailingIcon. Alongside these being used for informative purposes, we can see in the example below how I have used the trailingIcon to allow the SearchBar to revert back to the collapsed state when the cancel button is clicked.
SearchBarDefaults.InputField(
onSearch = { expanded = false },
expanded = expanded,
onExpandedChange = { expanded = it },
placeholder = { Text("What are you looking for?") },
leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
trailingIcon = {
if (expanded) {
IconButton(onClick = {
expanded = false
}) {
Icon(Icons.Default.Close, contentDescription = null)
}
}
},
query = query ?: "",
onQueryChange = {
query = it
}
)
The implementation for this InputField composable can then be slotted into the inputField argument for the DockedSearchBar composable.
var expanded by remember { mutableStateOf(false) }
var query by remember { mutableStateOf<String?>(null) }
DockedSearchBar(
modifier = Modifier.fillMaxWidth(),
expanded = expanded,
onExpandedChange = {
expanded = it
},
inputField = {
SearchBarDefaults.InputField(
onSearch = { expanded = false },
expanded = expanded,
onExpandedChange = { expanded = it },
placeholder = { Text("What are you looking for?") },
leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
trailingIcon = {
if (expanded) {
IconButton(onClick = {
expanded = false
}) {
Icon(Icons.Default.Close, contentDescription = null)
}
}
},
query = query ?: "",
onQueryChange = {
query = it
}
)
}
)
At this point, we would be able to compose the DockedSearchBar and see the floating component displayed within our UI.

All that remains for us to implement at this point is the content of the DockedSearchBar, this is the content that is displayed when the DockedSearchBar is in an expanded state. This argument utilise the ColumnScope, so any composables that are provided here will be stacked vertically. The expected form for this content area is a list of recommendations that can be selected by the user, so we’ll go ahead and compose a few ListItem composables that will each used to display a search recommendation to the user. When any of these items are clicked, the query will be updated to the selected value and the expanded state of the SearchBar reset.
var expanded by remember { mutableStateOf(false) }
var query by remember { mutableStateOf<String?>(null) }
DockedSearchBar(
...
) {
listOf("Result 1", "Result 2", "Result 3", "Result 4").forEach { text ->
ListItem(
headlineContent = { Text(text) },
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
modifier = Modifier.clickable {
query = text
expanded = false
}.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp)
)
}
}
With this in place, we’ll now be able to see recommendations being shown beneath the floating search bar when it comes into focus.

With the above in place, we’ve been able to implement a docked search bar that displays search recommendations to the user. Using the Material3 SearchBar composable, it has been very little effort to implement such a composable that transitions between these two different states. Maybe you are already using the SearchBar in your app or have been looking for similar functionality, but either way, I’m looking forward to seeing more apps saving time from a wider support of components in Jetpack Compose!