Search

fun Search(query: TextFieldValue, onQueryChange: (TextFieldValue) -> Unit, onSearch: (String) -> Unit, expanded: Boolean, onExpandedChange: (Boolean) -> Unit, placeholder: String?, searchFieldContentDescription: String?, clearButtonContentDescription: String?, backButtonContentDescription: String?, modifier: Modifier = Modifier, actionItems: List<SearchActionItem> = emptyList(), enabled: Boolean = true, size: SearchSize = SearchSize.Large, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() })(source)

Search is a standalone search component built from scratch.

This component provides a search input field with animated leading and trailing icons, including a back button, clear button, and optional action items. Search does not support content which needs to be handled separately with your method of choice.

The use of TextFieldValue query types instead of a String allows for more control and manipulation of the input field. This variant does not contain any nested content container and is meant to be used in a fully customisable way, where you can create your own solutions to the search field layout, placing the search bar where you need.

Parameters

query

The current text value of the search field as TextFieldValue.

onQueryChange

Callback invoked when the search field text changes.

onSearch

Callback invoked when the search action is triggered (e.g., IME search action).

expanded

Whether the search field is in expanded state, showing the back button.

onExpandedChange

Callback invoked when the expanded state should change.

placeholder

Optional placeholder text displayed when the search field is empty.

searchFieldContentDescription

Accessibility label for the search field.

clearButtonContentDescription

Accessibility label for the clear button.

backButtonContentDescription

Accessibility label for the back button.

modifier

The Modifier to be applied to the search component.

actionItems

List of action items displayed as icon buttons when the field is empty. Maximum 2 items recommended.

enabled

Whether the search field is enabled for user interaction. Defaults to true.

size

The size variant of the search component. Defaults to SearchSize.Large.

interactionSource

The MutableInteractionSource representing the stream of interactions for this component.

See also

Samples

import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.toLowerCase
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import net.ikea.skapa.R
import net.ikea.skapa.foundation.*
import net.ikea.skapa.ui.components.Search
import net.ikea.skapa.ui.components.SearchActionItem
import net.ikea.skapa.ui.components.SearchSize

fun main() { 
   //sampleStart 
   SkapaTheme2(isSystemInDarkTheme()) {
    var query by rememberSaveable { mutableStateOf("") }
    val keyboardController = LocalSoftwareKeyboardController.current

    Column {
        // Inline search
        Search(
            query = query,
            onQueryChange = { query = it },
            onSearch = {
                keyboardController?.hide()
            },
            expanded = false,
            onExpandedChange = { /* Not used */ },
            placeholder = "Search",
            clearButtonContentDescription = "Clear input",
            backButtonContentDescription = null, // No back button in inline search
            modifier = Modifier,
            actionItems = listOf(
                SearchActionItem(
                    iconResource = R.drawable.ic_search_camera,
                    contentDescription = "Search by camera",
                    onClick = { /* Open camera */ }
                )
            ),
            windowInsets = WindowInsets(0.dp, 0.dp, 0.dp, 0.dp),
            interactionSource = remember { MutableInteractionSource() }
        ) { /* Not used */ }

        // Full screen search using `expanded` parameter
        var expanded by rememberSaveable { mutableStateOf(false) }
        val furnitureList = listOf("Sofa", "Armchair", "Coffee Table", "Dining Table", "Dining Chair", "Bed Frame", "Mattress", "Wardrobe")
        Search(
            query = query,
            onQueryChange = {
                query = it
                expanded = true // Make sure to put this view in its own view to avoid crashes
            },
            onSearch = {
                keyboardController?.hide()
                // Potentially set `expanded = false` and navigate to search results page
            },
            expanded = expanded,
            onExpandedChange = { expanded = it },
            placeholder = "Search full screen",
            clearButtonContentDescription = "Clear input",
            backButtonContentDescription = "Navigate back",
            modifier = Modifier,
            actionItems = listOf(
                SearchActionItem(
                    iconResource = R.drawable.ic_search_camera,
                    contentDescription = "Search by camera",
                    onClick = { /* Open camera */ }
                )
            ),
            windowInsets = WindowInsets(0.dp, 0.dp, 0.dp, 0.dp),
            interactionSource = remember { MutableInteractionSource() }
        ) {
            /* Use this to show search results */
            LazyColumn {
                val filteredList = furnitureList.filter { it.toLowerCase(Locale.current).contains(query.toLowerCase(Locale.current)) }
                if (query.isNotEmpty() && filteredList.isEmpty()) {
                    item {
                        Text(
                            text = "No product matched the query",
                            modifier = Modifier
                                .fillMaxWidth()
                                .padding(SkapaSpacing.space200),
                            textAlign = TextAlign.Center
                        )
                    }
                } else {
                    items(filteredList) { item ->
                        Text(
                            text = item,
                            modifier = Modifier
                                .fillMaxWidth()
                                .padding(SkapaSpacing.space200)
                        )
                    }
                }
            }
        }

        var textFieldValueQuery by rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) }
        val isExpanded = rememberSaveable { mutableStateOf(false) }
        val focusManager = LocalFocusManager.current

        // TextFieldValue Search example with custom content on Expanded.
        Search(
            query = textFieldValueQuery,
            onQueryChange = {
                textFieldValueQuery = it
                isExpanded.value = true
            },
            onSearch = {
                keyboardController?.hide()
                focusManager.clearFocus()
                isExpanded.value = false
            },
            expanded = isExpanded.value,
            onExpandedChange = {
                isExpanded.value = it
            },
            placeholder = "Search",
            searchFieldContentDescription = "Search field",
            clearButtonContentDescription = "Clear query",
            backButtonContentDescription = "Navigate back",
            modifier = Modifier
                .windowInsetsPadding(WindowInsets(0.dp, SkapaSpacing.space75, 0.dp, 0.dp)),
            actionItems = listOf(
                SearchActionItem(
                    iconResource = net.ikea.skapa.R.drawable.ic_search_microphone,
                    contentDescription = "Search by voice",
                    onClick = { }
                ),
                SearchActionItem(
                    iconResource = net.ikea.skapa.R.drawable.ic_search_camera,
                    contentDescription = "Search by camera",
                    onClick = { }
                )
            ),
            size = SearchSize.Medium
        )
        if (isExpanded.value) {
            Column {
                LazyColumn {
                    val filteredList = furnitureList.filter {
                        it.toLowerCase(Locale.current).contains(textFieldValueQuery.text.toLowerCase(Locale.current))
                    }

                    if (textFieldValueQuery.text.isNotEmpty() && filteredList.isEmpty()) {
                        item {
                            Text(
                                text = "No product matched the query",
                                Modifier
                                    .fillMaxWidth()
                                    .padding(SkapaSpacing.space200),
                                textAlign = TextAlign.Center
                            )
                        }
                    } else {
                        items(filteredList) { item ->
                            Text(
                                text = item,
                                modifier = Modifier
                                    .fillMaxWidth()
                                    .padding(SkapaSpacing.space200)
                            )
                        }
                    }
                }
            }
        }
    }
} 
   //sampleEnd
}

fun Search(query: String, onQueryChange: (String) -> Unit, onSearch: (String) -> Unit, expanded: Boolean, onExpandedChange: (Boolean) -> Unit, placeholder: String?, clearButtonContentDescription: String?, backButtonContentDescription: String?, modifier: Modifier = Modifier, actionItems: List<SearchActionItem> = emptyList(), windowInsets: WindowInsets = SearchBarDefaults.windowInsets, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, content: @Composable ColumnScope.() -> Unit)(source)

Search provides an input field for searching content within a site or app to find specific items.

The component supports both inline and full-screen search modes. In inline mode, the search field is displayed within the current layout, while in full-screen mode, it expands to cover the entire screen. Check the Search sample for an example of how to use it.

Parameters

query

The current text value of the search field.

onQueryChange

The callback that is called when the value of the search field changes.

onSearch

The callback that is called when the search IME action is triggered.

expanded

Boolean indicating whether the search field is expanded or not.

onExpandedChange

The callback that is called when the expanded state changes.

placeholder

The placeholder text to display when the search field is empty.

clearButtonContentDescription

Semantic text for the Clear button.

backButtonContentDescription

Semantic text for the Back button.

modifier

The modifier to apply to the search field.

actionItems

List of action items to be displayed in the search field. Use up to 2 action items.

windowInsets

The window insets to apply to the search field.

interactionSource

The interaction source for the search field.

content

The content to be displayed for the search results.

See also

Samples

import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.toLowerCase
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import net.ikea.skapa.R
import net.ikea.skapa.foundation.*
import net.ikea.skapa.ui.components.Search
import net.ikea.skapa.ui.components.SearchActionItem
import net.ikea.skapa.ui.components.SearchSize

fun main() { 
   //sampleStart 
   SkapaTheme2(isSystemInDarkTheme()) {
    var query by rememberSaveable { mutableStateOf("") }
    val keyboardController = LocalSoftwareKeyboardController.current

    Column {
        // Inline search
        Search(
            query = query,
            onQueryChange = { query = it },
            onSearch = {
                keyboardController?.hide()
            },
            expanded = false,
            onExpandedChange = { /* Not used */ },
            placeholder = "Search",
            clearButtonContentDescription = "Clear input",
            backButtonContentDescription = null, // No back button in inline search
            modifier = Modifier,
            actionItems = listOf(
                SearchActionItem(
                    iconResource = R.drawable.ic_search_camera,
                    contentDescription = "Search by camera",
                    onClick = { /* Open camera */ }
                )
            ),
            windowInsets = WindowInsets(0.dp, 0.dp, 0.dp, 0.dp),
            interactionSource = remember { MutableInteractionSource() }
        ) { /* Not used */ }

        // Full screen search using `expanded` parameter
        var expanded by rememberSaveable { mutableStateOf(false) }
        val furnitureList = listOf("Sofa", "Armchair", "Coffee Table", "Dining Table", "Dining Chair", "Bed Frame", "Mattress", "Wardrobe")
        Search(
            query = query,
            onQueryChange = {
                query = it
                expanded = true // Make sure to put this view in its own view to avoid crashes
            },
            onSearch = {
                keyboardController?.hide()
                // Potentially set `expanded = false` and navigate to search results page
            },
            expanded = expanded,
            onExpandedChange = { expanded = it },
            placeholder = "Search full screen",
            clearButtonContentDescription = "Clear input",
            backButtonContentDescription = "Navigate back",
            modifier = Modifier,
            actionItems = listOf(
                SearchActionItem(
                    iconResource = R.drawable.ic_search_camera,
                    contentDescription = "Search by camera",
                    onClick = { /* Open camera */ }
                )
            ),
            windowInsets = WindowInsets(0.dp, 0.dp, 0.dp, 0.dp),
            interactionSource = remember { MutableInteractionSource() }
        ) {
            /* Use this to show search results */
            LazyColumn {
                val filteredList = furnitureList.filter { it.toLowerCase(Locale.current).contains(query.toLowerCase(Locale.current)) }
                if (query.isNotEmpty() && filteredList.isEmpty()) {
                    item {
                        Text(
                            text = "No product matched the query",
                            modifier = Modifier
                                .fillMaxWidth()
                                .padding(SkapaSpacing.space200),
                            textAlign = TextAlign.Center
                        )
                    }
                } else {
                    items(filteredList) { item ->
                        Text(
                            text = item,
                            modifier = Modifier
                                .fillMaxWidth()
                                .padding(SkapaSpacing.space200)
                        )
                    }
                }
            }
        }

        var textFieldValueQuery by rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) }
        val isExpanded = rememberSaveable { mutableStateOf(false) }
        val focusManager = LocalFocusManager.current

        // TextFieldValue Search example with custom content on Expanded.
        Search(
            query = textFieldValueQuery,
            onQueryChange = {
                textFieldValueQuery = it
                isExpanded.value = true
            },
            onSearch = {
                keyboardController?.hide()
                focusManager.clearFocus()
                isExpanded.value = false
            },
            expanded = isExpanded.value,
            onExpandedChange = {
                isExpanded.value = it
            },
            placeholder = "Search",
            searchFieldContentDescription = "Search field",
            clearButtonContentDescription = "Clear query",
            backButtonContentDescription = "Navigate back",
            modifier = Modifier
                .windowInsetsPadding(WindowInsets(0.dp, SkapaSpacing.space75, 0.dp, 0.dp)),
            actionItems = listOf(
                SearchActionItem(
                    iconResource = net.ikea.skapa.R.drawable.ic_search_microphone,
                    contentDescription = "Search by voice",
                    onClick = { }
                ),
                SearchActionItem(
                    iconResource = net.ikea.skapa.R.drawable.ic_search_camera,
                    contentDescription = "Search by camera",
                    onClick = { }
                )
            ),
            size = SearchSize.Medium
        )
        if (isExpanded.value) {
            Column {
                LazyColumn {
                    val filteredList = furnitureList.filter {
                        it.toLowerCase(Locale.current).contains(textFieldValueQuery.text.toLowerCase(Locale.current))
                    }

                    if (textFieldValueQuery.text.isNotEmpty() && filteredList.isEmpty()) {
                        item {
                            Text(
                                text = "No product matched the query",
                                Modifier
                                    .fillMaxWidth()
                                    .padding(SkapaSpacing.space200),
                                textAlign = TextAlign.Center
                            )
                        }
                    } else {
                        items(filteredList) { item ->
                            Text(
                                text = item,
                                modifier = Modifier
                                    .fillMaxWidth()
                                    .padding(SkapaSpacing.space200)
                            )
                        }
                    }
                }
            }
        }
    }
} 
   //sampleEnd
}