ListDetailPaneScaffold

Functions summary

Unit
@ExperimentalMaterial3AdaptiveApi
@Composable
ListDetailPaneScaffold(
    directive: PaneScaffoldDirective,
    scaffoldState: ThreePaneScaffoldState,
    listPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
    detailPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
    modifier: Modifier,
    extraPane: (@Composable ThreePaneScaffoldPaneScope.() -> Unit)?,
    paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)?,
    paneExpansionState: PaneExpansionState?
)

A three pane layout that follows the Material guidelines, displaying the provided panes in a canonical list-detail layout.

Cmn
Unit
@ExperimentalMaterial3AdaptiveApi
@Composable
ListDetailPaneScaffold(
    directive: PaneScaffoldDirective,
    value: ThreePaneScaffoldValue,
    listPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
    detailPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
    modifier: Modifier,
    extraPane: (@Composable ThreePaneScaffoldPaneScope.() -> Unit)?,
    paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)?,
    paneExpansionState: PaneExpansionState?
)

A three pane layout that follows the Material guidelines, displaying the provided panes in a canonical list-detail layout.

Cmn

Functions

ListDetailPaneScaffold

@ExperimentalMaterial3AdaptiveApi
@Composable
fun ListDetailPaneScaffold(
    directive: PaneScaffoldDirective,
    scaffoldState: ThreePaneScaffoldState,
    listPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
    detailPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
    modifier: Modifier = Modifier,
    extraPane: (@Composable ThreePaneScaffoldPaneScope.() -> Unit)? = null,
    paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? = null,
    paneExpansionState: PaneExpansionState? = null
): Unit

A three pane layout that follows the Material guidelines, displaying the provided panes in a canonical list-detail layout.

This overload takes a ThreePaneScaffoldState describing the current ThreePaneScaffoldValue and any pane transitions or animations in progress.

Here's a basic usage sample, which demonstrates how a layout can change from single pane to dual pane under different window configurations:

import androidx.compose.material3.Scaffold
import androidx.compose.material3.adaptive.layout.AnimatedPane
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

val coroutineScope = rememberCoroutineScope()
val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<NavItemData>()
val items = listOf("Item 1", "Item 2", "Item 3")
val selectedItem = scaffoldNavigator.currentDestination?.contentKey

ListDetailPaneScaffold(
    directive = scaffoldNavigator.scaffoldDirective,
    scaffoldState = scaffoldNavigator.scaffoldState,
    listPane = {
        AnimatedPane(modifier = Modifier.preferredWidth(200.dp)) {
            ListPaneContent(
                items = items,
                selectedItem = selectedItem,
                scaffoldNavigator = scaffoldNavigator,
                coroutineScope = coroutineScope,
            )
        }
    },
    detailPane = {
        AnimatedPane {
            DetailPaneContent(
                items = items,
                selectedItem = selectedItem,
                scaffoldNavigator = scaffoldNavigator,
                coroutineScope = coroutineScope,
            )
        }
    },
)

For a more sophisticated sample that supports an extra pane and pane expansion functionality that allows users to drag to change layout split, see:

import androidx.compose.material3.Scaffold
import androidx.compose.material3.adaptive.layout.AnimatedPane
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
import androidx.compose.material3.adaptive.layout.PaneExpansionAnchor
import androidx.compose.material3.adaptive.layout.PaneExpansionState
import androidx.compose.material3.adaptive.layout.rememberPaneExpansionState
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

val coroutineScope = rememberCoroutineScope()
val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<NavItemData>()
val items = listOf("Item 1", "Item 2", "Item 3")
val extraItems = listOf("Extra 1", "Extra 2", "Extra 3")
val selectedItem = scaffoldNavigator.currentDestination?.contentKey

ListDetailPaneScaffold(
    directive = scaffoldNavigator.scaffoldDirective,
    scaffoldState = scaffoldNavigator.scaffoldState,
    listPane = {
        AnimatedPane(modifier = Modifier.preferredWidth(200.dp)) {
            ListPaneContent(
                items = items,
                selectedItem = selectedItem,
                scaffoldNavigator = scaffoldNavigator,
                coroutineScope = coroutineScope,
            )
        }
    },
    detailPane = {
        AnimatedPane {
            DetailPaneContent(
                items = items,
                selectedItem = selectedItem,
                scaffoldNavigator = scaffoldNavigator,
                hasExtraPane = true,
                coroutineScope = coroutineScope,
            )
        }
    },
    extraPane = {
        AnimatedPane {
            ExtraPaneContent(
                extraItems = extraItems,
                selectedItem = selectedItem,
                scaffoldNavigator = scaffoldNavigator,
                coroutineScope = coroutineScope,
            )
        }
    },
    paneExpansionState =
        rememberPaneExpansionState(
            keyProvider = scaffoldNavigator.scaffoldValue,
            anchors = PaneExpansionAnchors,
            initialAnchoredIndex = 1,
        ),
    paneExpansionDragHandle = { state -> PaneExpansionDragHandleSample(state) },
)

By default there isn't a drag handle rendered so users aren't able to drag to change the pane split. Providing a drag handle like the above sample shows will enable the functionality. We suggest developers to use the vertical drag handle implementation provided by the Material3 component library here to have default theming/styling support. You can integrate the component as the following sample shows:

import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
import androidx.compose.material3.VerticalDragHandle
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier

val interactionSource = remember { MutableInteractionSource() }
VerticalDragHandle(
    modifier =
        Modifier.paneExpansionDraggable(
            state,
            LocalMinimumInteractiveComponentSize.current,
            interactionSource,
        ),
    interactionSource = interactionSource,
)

Note that if there's no drag handle, you can still modify paneExpansionState directly to apply pane expansion.

The following code gives a sample of how to integrate with the Compose Navigation library:

import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.adaptive.layout.AnimatedPane
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
import androidx.compose.material3.adaptive.navigation.BackNavigationBehavior
import androidx.compose.material3.adaptive.navigation.NavigableListDetailPaneScaffold
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.unit.dp
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController

val welcomeRoute = "welcome"
val listDetailRoute = "listdetail"

val coroutineScope = rememberCoroutineScope()

// `navController` handles navigation outside the ListDetailPaneScaffold,
// and `scaffoldNavigator` handles navigation within it. The "content" of
// the scaffold uses a custom type which tracks the index of the selected item,
// which is passed as a type argument to `rememberListDetailPaneScaffoldNavigator`.
val navController = rememberNavController()
val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<NavItemData>()

// Back behavior can be customized based on the needs of the app.
var backBehaviorIndex by rememberSaveable { mutableStateOf(0) }
val backBehaviors =
    listOf(
        BackNavigationBehavior.PopUntilScaffoldValueChange,
        BackNavigationBehavior.PopUntilCurrentDestinationChange,
        BackNavigationBehavior.PopUntilContentChange,
        BackNavigationBehavior.PopLatest,
    )
val backBehavior = backBehaviors[backBehaviorIndex]

val items = listOf("Item 1", "Item 2", "Item 3")
val extraItems = listOf("Extra 1", "Extra 2", "Extra 3")

NavHost(
    navController = navController,
    startDestination = welcomeRoute,
    enterTransition = { slideInHorizontally(initialOffsetX = { it }) },
    exitTransition = { slideOutHorizontally(targetOffsetX = { -it }) },
    popEnterTransition = { slideInHorizontally(initialOffsetX = { -it }) },
    popExitTransition = { slideOutHorizontally(targetOffsetX = { it }) },
) {
    composable(welcomeRoute) {
        Scaffold(Modifier.fillMaxSize()) { paddingValues ->
            Column(
                modifier =
                    Modifier.verticalScroll(rememberScrollState())
                        .padding(paddingValues)
                        .padding(24.dp)
                        .fillMaxSize(),
                verticalArrangement = Arrangement.spacedBy(16.dp),
            ) {
                Text(
                    text = "How should the scaffold handle back navigation?",
                    style = MaterialTheme.typography.headlineMedium,
                )
                RadioButtonRow(
                    selected = backBehaviorIndex == 0,
                    onClick = { backBehaviorIndex = 0 },
                    text =
                        "PopUntilScaffoldValueChange - Back navigation forces a change in " +
                            "which pane(s) is/are shown.",
                )
                RadioButtonRow(
                    selected = backBehaviorIndex == 1,
                    onClick = { backBehaviorIndex = 1 },
                    text =
                        "PopUntilCurrentDestinationChange - Back navigation forces a " +
                            "change in which pane is currently considered \"active\".",
                )
                RadioButtonRow(
                    selected = backBehaviorIndex == 2,
                    onClick = { backBehaviorIndex = 2 },
                    text =
                        "PopUntilContentChange - Back navigation forces a change in the " +
                            "content of any pane or which pane(s) is/are shown.\nNote: this " +
                            "may result in unintuitive behavior if the device size changes " +
                            "in the middle of the navigation.",
                )
                RadioButtonRow(
                    selected = backBehaviorIndex == 3,
                    onClick = { backBehaviorIndex = 3 },
                    text =
                        "PopLatest - No special back handling.\nNote: this may result in " +
                            "unintuitive behavior if the device size changes in the middle " +
                            "of the navigation.",
                )
                Button(onClick = { navController.navigate(listDetailRoute) }) { Text("Next") }
            }
        }
    }
    composable(listDetailRoute) {
        val selectedItem = scaffoldNavigator.currentDestination?.contentKey
        NavigableListDetailPaneScaffold(
            navigator = scaffoldNavigator,
            defaultBackBehavior = backBehavior,
            listPane = {
                AnimatedPane(Modifier.preferredWidth(200.dp)) {
                    ListPaneContent(
                        items = items,
                        selectedItem = selectedItem,
                        scaffoldNavigator = scaffoldNavigator,
                        coroutineScope = coroutineScope,
                    )
                }
            },
            detailPane = {
                AnimatedPane {
                    DetailPaneContent(
                        items = items,
                        selectedItem = selectedItem,
                        scaffoldNavigator = scaffoldNavigator,
                        hasExtraPane = true,
                        backBehavior = backBehavior,
                        coroutineScope = coroutineScope,
                    )
                }
            },
            extraPane = {
                AnimatedPane {
                    ExtraPaneContent(
                        extraItems = extraItems,
                        selectedItem = selectedItem,
                        scaffoldNavigator = scaffoldNavigator,
                        backBehavior = backBehavior,
                        coroutineScope = coroutineScope,
                    )
                }
            },
        )
    }
}
Parameters
directive: PaneScaffoldDirective

The top-level directives about how the scaffold should arrange its panes.

scaffoldState: ThreePaneScaffoldState

The current state of the scaffold, containing information about the adapted value of each pane of the scaffold and the transitions/animations in progress.

listPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit

the list pane of the scaffold, which is supposed to hold a list of item summaries that can be selected from, for example, the inbox mail list of a mail app. See ListDetailPaneScaffoldRole.List. Note that we suggest you to use AnimatedPane as the root layout of panes, which supports default pane behaviors like enter/exit transitions.

detailPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit

the detail pane of the scaffold, which is supposed to hold the detailed info of a selected item, for example, the mail content currently being viewed. See ListDetailPaneScaffoldRole.Detail. Note that we suggest you to use AnimatedPane as the root layout of panes, which supports default pane behaviors like enter/exit transitions.

modifier: Modifier = Modifier

Modifier of the scaffold layout.

extraPane: (@Composable ThreePaneScaffoldPaneScope.() -> Unit)? = null

the extra pane of the scaffold, which is supposed to hold any supplementary info besides the list and the detail panes, for example, a task list or a mini-calendar view of a mail app. See ListDetailPaneScaffoldRole.Extra. Note that we suggest you to use AnimatedPane as the root layout of panes, which supports default pane behaviors like enter/exit transitions.

paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? = null

the pane expansion drag handle to allow users to drag to change pane expansion state, null by default.

paneExpansionState: PaneExpansionState? = null

the state object of pane expansion; when no value is provided but paneExpansionDragHandle is not null, a default implementation will be created for the drag handle to use.

ListDetailPaneScaffold

@ExperimentalMaterial3AdaptiveApi
@Composable
fun ListDetailPaneScaffold(
    directive: PaneScaffoldDirective,
    value: ThreePaneScaffoldValue,
    listPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
    detailPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
    modifier: Modifier = Modifier,
    extraPane: (@Composable ThreePaneScaffoldPaneScope.() -> Unit)? = null,
    paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? = null,
    paneExpansionState: PaneExpansionState? = null
): Unit

A three pane layout that follows the Material guidelines, displaying the provided panes in a canonical list-detail layout.

This overload takes a ThreePaneScaffoldValue describing the adapted value of each pane within the scaffold.

Here's a basic usage sample, which demonstrates how a layout can change from single pane to dual pane under different window configurations:

import androidx.compose.material3.Scaffold
import androidx.compose.material3.adaptive.layout.AnimatedPane
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

val coroutineScope = rememberCoroutineScope()
val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<NavItemData>()
val items = listOf("Item 1", "Item 2", "Item 3")
val selectedItem = scaffoldNavigator.currentDestination?.contentKey

ListDetailPaneScaffold(
    directive = scaffoldNavigator.scaffoldDirective,
    scaffoldState = scaffoldNavigator.scaffoldState,
    listPane = {
        AnimatedPane(modifier = Modifier.preferredWidth(200.dp)) {
            ListPaneContent(
                items = items,
                selectedItem = selectedItem,
                scaffoldNavigator = scaffoldNavigator,
                coroutineScope = coroutineScope,
            )
        }
    },
    detailPane = {
        AnimatedPane {
            DetailPaneContent(
                items = items,
                selectedItem = selectedItem,
                scaffoldNavigator = scaffoldNavigator,
                coroutineScope = coroutineScope,
            )
        }
    },
)

For a more sophisticated sample that supports an extra pane and pane expansion functionality that allows users to drag to change layout split, see:

import androidx.compose.material3.Scaffold
import androidx.compose.material3.adaptive.layout.AnimatedPane
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
import androidx.compose.material3.adaptive.layout.PaneExpansionAnchor
import androidx.compose.material3.adaptive.layout.PaneExpansionState
import androidx.compose.material3.adaptive.layout.rememberPaneExpansionState
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

val coroutineScope = rememberCoroutineScope()
val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<NavItemData>()
val items = listOf("Item 1", "Item 2", "Item 3")
val extraItems = listOf("Extra 1", "Extra 2", "Extra 3")
val selectedItem = scaffoldNavigator.currentDestination?.contentKey

ListDetailPaneScaffold(
    directive = scaffoldNavigator.scaffoldDirective,
    scaffoldState = scaffoldNavigator.scaffoldState,
    listPane = {
        AnimatedPane(modifier = Modifier.preferredWidth(200.dp)) {
            ListPaneContent(
                items = items,
                selectedItem = selectedItem,
                scaffoldNavigator = scaffoldNavigator,
                coroutineScope = coroutineScope,
            )
        }
    },
    detailPane = {
        AnimatedPane {
            DetailPaneContent(
                items = items,
                selectedItem = selectedItem,
                scaffoldNavigator = scaffoldNavigator,
                hasExtraPane = true,
                coroutineScope = coroutineScope,
            )
        }
    },
    extraPane = {
        AnimatedPane {
            ExtraPaneContent(
                extraItems = extraItems,
                selectedItem = selectedItem,
                scaffoldNavigator = scaffoldNavigator,
                coroutineScope = coroutineScope,
            )
        }
    },
    paneExpansionState =
        rememberPaneExpansionState(
            keyProvider = scaffoldNavigator.scaffoldValue,
            anchors = PaneExpansionAnchors,
            initialAnchoredIndex = 1,
        ),
    paneExpansionDragHandle = { state -> PaneExpansionDragHandleSample(state) },
)

By default there isn't a drag handle rendered so users aren't able to drag to change the pane split. Providing a drag handle like the above sample shows will enable the functionality. We suggest developers to use the vertical drag handle implementation provided by the Material3 component library here to have default theming/styling support. You can integrate the component as the following sample shows:

import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
import androidx.compose.material3.VerticalDragHandle
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier

val interactionSource = remember { MutableInteractionSource() }
VerticalDragHandle(
    modifier =
        Modifier.paneExpansionDraggable(
            state,
            LocalMinimumInteractiveComponentSize.current,
            interactionSource,
        ),
    interactionSource = interactionSource,
)

Note that if there's no drag handle, you can still modify paneExpansionState directly to apply pane expansion.

The following code gives a sample of how to integrate with the Compose Navigation library:

import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.adaptive.layout.AnimatedPane
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
import androidx.compose.material3.adaptive.navigation.BackNavigationBehavior
import androidx.compose.material3.adaptive.navigation.NavigableListDetailPaneScaffold
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.unit.dp
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController

val welcomeRoute = "welcome"
val listDetailRoute = "listdetail"

val coroutineScope = rememberCoroutineScope()

// `navController` handles navigation outside the ListDetailPaneScaffold,
// and `scaffoldNavigator` handles navigation within it. The "content" of
// the scaffold uses a custom type which tracks the index of the selected item,
// which is passed as a type argument to `rememberListDetailPaneScaffoldNavigator`.
val navController = rememberNavController()
val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<NavItemData>()

// Back behavior can be customized based on the needs of the app.
var backBehaviorIndex by rememberSaveable { mutableStateOf(0) }
val backBehaviors =
    listOf(
        BackNavigationBehavior.PopUntilScaffoldValueChange,
        BackNavigationBehavior.PopUntilCurrentDestinationChange,
        BackNavigationBehavior.PopUntilContentChange,
        BackNavigationBehavior.PopLatest,
    )
val backBehavior = backBehaviors[backBehaviorIndex]

val items = listOf("Item 1", "Item 2", "Item 3")
val extraItems = listOf("Extra 1", "Extra 2", "Extra 3")

NavHost(
    navController = navController,
    startDestination = welcomeRoute,
    enterTransition = { slideInHorizontally(initialOffsetX = { it }) },
    exitTransition = { slideOutHorizontally(targetOffsetX = { -it }) },
    popEnterTransition = { slideInHorizontally(initialOffsetX = { -it }) },
    popExitTransition = { slideOutHorizontally(targetOffsetX = { it }) },
) {
    composable(welcomeRoute) {
        Scaffold(Modifier.fillMaxSize()) { paddingValues ->
            Column(
                modifier =
                    Modifier.verticalScroll(rememberScrollState())
                        .padding(paddingValues)
                        .padding(24.dp)
                        .fillMaxSize(),
                verticalArrangement = Arrangement.spacedBy(16.dp),
            ) {
                Text(
                    text = "How should the scaffold handle back navigation?",
                    style = MaterialTheme.typography.headlineMedium,
                )
                RadioButtonRow(
                    selected = backBehaviorIndex == 0,
                    onClick = { backBehaviorIndex = 0 },
                    text =
                        "PopUntilScaffoldValueChange - Back navigation forces a change in " +
                            "which pane(s) is/are shown.",
                )
                RadioButtonRow(
                    selected = backBehaviorIndex == 1,
                    onClick = { backBehaviorIndex = 1 },
                    text =
                        "PopUntilCurrentDestinationChange - Back navigation forces a " +
                            "change in which pane is currently considered \"active\".",
                )
                RadioButtonRow(
                    selected = backBehaviorIndex == 2,
                    onClick = { backBehaviorIndex = 2 },
                    text =
                        "PopUntilContentChange - Back navigation forces a change in the " +
                            "content of any pane or which pane(s) is/are shown.\nNote: this " +
                            "may result in unintuitive behavior if the device size changes " +
                            "in the middle of the navigation.",
                )
                RadioButtonRow(
                    selected = backBehaviorIndex == 3,
                    onClick = { backBehaviorIndex = 3 },
                    text =
                        "PopLatest - No special back handling.\nNote: this may result in " +
                            "unintuitive behavior if the device size changes in the middle " +
                            "of the navigation.",
                )
                Button(onClick = { navController.navigate(listDetailRoute) }) { Text("Next") }
            }
        }
    }
    composable(listDetailRoute) {
        val selectedItem = scaffoldNavigator.currentDestination?.contentKey
        NavigableListDetailPaneScaffold(
            navigator = scaffoldNavigator,
            defaultBackBehavior = backBehavior,
            listPane = {
                AnimatedPane(Modifier.preferredWidth(200.dp)) {
                    ListPaneContent(
                        items = items,
                        selectedItem = selectedItem,
                        scaffoldNavigator = scaffoldNavigator,
                        coroutineScope = coroutineScope,
                    )
                }
            },
            detailPane = {
                AnimatedPane {
                    DetailPaneContent(
                        items = items,
                        selectedItem = selectedItem,
                        scaffoldNavigator = scaffoldNavigator,
                        hasExtraPane = true,
                        backBehavior = backBehavior,
                        coroutineScope = coroutineScope,
                    )
                }
            },
            extraPane = {
                AnimatedPane {
                    ExtraPaneContent(
                        extraItems = extraItems,
                        selectedItem = selectedItem,
                        scaffoldNavigator = scaffoldNavigator,
                        backBehavior = backBehavior,
                        coroutineScope = coroutineScope,
                    )
                }
            },
        )
    }
}
Parameters
directive: PaneScaffoldDirective

The top-level directives about how the scaffold should arrange its panes.

value: ThreePaneScaffoldValue

The current adapted value of the scaffold, which indicates how each pane of the scaffold is adapted.

listPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit

the list pane of the scaffold, which is supposed to hold a list of item summaries that can be selected from, for example, the inbox mail list of a mail app. See ListDetailPaneScaffoldRole.List. Note that we suggest you to use AnimatedPane as the root layout of panes, which supports default pane behaviors like enter/exit transitions.

detailPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit

the detail pane of the scaffold, which is supposed to hold the detailed info of a selected item, for example, the mail content currently being viewed. See ListDetailPaneScaffoldRole.Detail. Note that we suggest you to use AnimatedPane as the root layout of panes, which supports default pane behaviors like enter/exit transitions.

modifier: Modifier = Modifier

Modifier of the scaffold layout.

extraPane: (@Composable ThreePaneScaffoldPaneScope.() -> Unit)? = null

the extra pane of the scaffold, which is supposed to hold any supplementary info besides the list and the detail panes, for example, a task list or a mini-calendar view of a mail app. See ListDetailPaneScaffoldRole.Extra. Note that we suggest you to use AnimatedPane as the root layout of panes, which supports default pane behaviors like enter/exit transitions.

paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? = null

the pane expansion drag handle to allow users to drag to change pane expansion state, null by default.

paneExpansionState: PaneExpansionState? = null

the state object of pane expansion; when no value is provided but paneExpansionDragHandle is not null, a default implementation will be created for the drag handle to use.