Build a list-detail layout

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.

A detail pane shown alongside the list page.
Figure 1. When enough screen size is available, the detail pane is shown alongside the list pane.
After an item is selected, the detail pane takes over the whole screen.
Figure 2. When screen size is limited, the detail pane (since an item has been selected) takes over the whole space.

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:

  1. 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) }

  2. Create the ListDetailPaneScaffoldState. The state is used to move between the list, detail, and extra panes.

    val state = rememberListDetailPaneScaffoldState()

  3. Pass the ListDetailPaneScaffoldState you created to the ListDetailPaneScaffold composable.

    ListDetailPaneScaffold(
        scaffoldState = state,
        // ...
    )

  4. 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 the selectedItem state variable and use ListDetailPaneScaffoldState 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)
                }
            )
        },
        // ...
    )

  5. Include your detail pane implementation in ListDetailPaneScaffold. Display the detail pane only if the selectedItem 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)
        }
    },
)