List-detail is a UI pattern that consists of a dual-pane layout where one pane presents a list of items and another pane displays the details of items selected from the list.
The pattern is particularly useful for applications that provide in-depth information about elements of large collections, for example, an email client that has a list of emails and the detailed content of each email message. List-detail can also be used for less critical paths such as dividing app preferences into a list of categories with the preferences for each category in the detail pane.
Implement UI pattern with ListDetailPaneScaffold
ListDetailPaneScaffold
is a composable that simplifies the implementation of the
list-detail pattern in your app. A list-detail scaffold can consist of up to
three panes: a list pane, a detail pane, and an optional extra pane. The
scaffold handles screen space calculations. When sufficient screen size is
available, the detail pane is displayed alongside the list pane. On small
screen sizes, the scaffold automatically switches to displaying either the
list or detail pane full screen.


Declare dependencies
ListDetailPaneScaffold
is part of the Material 3 adaptive library. Add the
dependencies for the artifacts you need in the build.gradle
file for your app
or module:
implementation("androidx.compose.material3:material3-adaptive")
Basic usage
The following illustrates the basic usage of ListDetailPaneScaffold
:
Store the currently selected item from the list in a mutable state variable. The variable holds the item to be displayed in the detail pane. Typically, you'd want to initialize the currently selected item with
null
, indicating that no selection was made yet.var selectedItem: MyItem? by rememberSaveable { mutableStateOf(null) }
Create the
ListDetailPaneScaffoldState
. The state is used to move between the list, detail, and extra panes.val state = rememberListDetailPaneScaffoldState()
Pass the
ListDetailPaneScaffoldState
you created to theListDetailPaneScaffold
composable.ListDetailPaneScaffold( scaffoldState = state, // ... )
Supply your list pane implementation to the
ListDetailPaneScaffold
. Ensure that your implementation includes a callback argument for capturing the newly selected item. When this callback is triggered, update theselectedItem
state variable and useListDetailPaneScaffoldState
to display the detail pane (ListDetailPaneScaffoldRole.Detail
).ListDetailPaneScaffold( scaffoldState = state, listPane = { MyList( onItemClick = { id -> // Set current item selectedItem = id // Switch focus to detail pane state.navigateTo(ListDetailPaneScaffoldRole.Detail) } ) }, // ... )
Include your detail pane implementation in
ListDetailPaneScaffold
. Display the detail pane only if theselectedItem
is not null.ListDetailPaneScaffold( scaffoldState = state, listPane = // ... detailPane = { selectedItem?.let { item -> MyDetails(item) } }, )
After implementing the above steps, your code should look similar to this:
// Create the ListDetailPaneScaffoldState val state = rememberListDetailPaneScaffoldState() // Currently selected item var selectedItem: MyItem? by rememberSaveable { mutableStateOf(null) } ListDetailPaneScaffold( scaffoldState = state, listPane = { MyList( onItemClick = { id -> // Set current item selectedItem = id // Display the detail pane state.navigateTo(ListDetailPaneScaffoldRole.Detail) }, ) }, detailPane = { // Show the detail pane content if selected item is available selectedItem?.let { item -> MyDetails(item) } }, )