Build a supporting pane layout

The supporting pane canonical layout focuses user attention on your app's main content while also displaying relevant supporting content. For example, the main content pane might show information about a recent movie while the supporting pane displays a list of other movies that have a similar theme or the same director or starring actors. For more information about the supporting pane canonical layout, see the Material 3 supporting pane guidelines.

Implement a supporting pane

SupportingPaneScaffold consists of up to three panes: a main pane, a supporting pane, and an optional extra pane. The scaffold handles all calculations for allocating window space to the three panes. On large screens, the scaffold displays the main pane with the supporting pane on the side. On small screens, the scaffold displays either the main or supporting pane full screen.

Main content occupying most of the display with supporting content alongside.
Figure 1. Supporting pane layout.

Add dependencies

SupportingPaneScaffold is part of the Material 3 adaptive layout library.

Add the following three, related dependencies to the build.gradle file of your app or module:

Kotlin

implementation("androidx.compose.material3.adaptive:adaptive")
implementation("androidx.compose.material3.adaptive:adaptive-layout")
implementation("androidx.compose.material3.adaptive:adaptive-navigation")

Groovy

implementation 'androidx.compose.material3.adaptive:adaptive'
implementation 'androidx.compose.material3.adaptive:adaptive-layout'
implementation 'androidx.compose.material3.adaptive:adaptive-navigation'
  • adaptive — Low-level building blocks such as HingeInfo and Posture
  • adaptive-layout — The adaptive layouts, such as SupportingPaneScaffold
  • adaptive-navigation — Composables for navigating within and between panes

Create a navigator and scaffold

In small windows, only one pane displays at a time, so use a ThreePaneScaffoldNavigator to move to and from panes. Create an instance of the navigator with rememberSupportingPaneScaffoldNavigator. To handle back gestures, use a BackHandler that checks canNavigateBack() and calls navigateBack():

val navigator = rememberSupportingPaneScaffoldNavigator()

BackHandler(navigator.canNavigateBack()) {
    navigator.navigateBack()
}

The scaffold requires a PaneScaffoldDirective, which controls how to split up the screen and how much spacing to use, and a ThreePaneScaffoldValue, which provides the current state of the panes (such as whether they're expanded or hidden). For the default behavior, use the navigator's scaffoldDirective and scaffoldValue respectively:

SupportingPaneScaffold(
    directive = navigator.scaffoldDirective,
    value = navigator.scaffoldValue,
    mainPane = { /*...*/ },
    supportingPane = { /*...*/ },
)

The main pane and supporting pane are composables containing your content. Use AnimatedPane to apply the default pane animations during navigation. Use the scaffold value to check whether the supporting pane is hidden; if so, display a button that calls navigateTo(ThreePaneScaffoldRole.Secondary) to display the supporting pane.

Here's a complete implementation of the scaffold:

val navigator = rememberSupportingPaneScaffoldNavigator()

BackHandler(navigator.canNavigateBack()) {
    navigator.navigateBack()
}

SupportingPaneScaffold(
    directive = navigator.scaffoldDirective,
    value = navigator.scaffoldValue,
    mainPane = {
        AnimatedPane(modifier = Modifier.safeContentPadding()) {
            // Main pane content
            if (navigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting] == PaneAdaptedValue.Hidden) {
                Button(
                    modifier = Modifier.wrapContentSize(),
                    onClick = {
                        navigator.navigateTo(SupportingPaneScaffoldRole.Supporting)
                    }
                ) {
                    Text("Show supporting pane")
                }
            } else {
                Text("Supporting pane is shown")
            }
        }
    },
    supportingPane = {
        AnimatedPane(modifier = Modifier.safeContentPadding()) {
            // Supporting pane content
            Text("Supporting pane")
        }
    },
)

Extract pane composables

Extract the individual panes of a SupportingPaneScaffold into their own composables to make them reusable and testable. Use ThreePaneScaffoldScope to access AnimatedPane if you want the default animations:

@Composable
fun ThreePaneScaffoldScope.MainPane(
    shouldShowSupportingPaneButton: Boolean,
    onNavigateToSupportingPane: () -> Unit,
    modifier: Modifier = Modifier,
) {
    AnimatedPane(modifier = modifier.safeContentPadding()) {
        // Main pane content
        if (shouldShowSupportingPaneButton) {
            Button(onClick = onNavigateToSupportingPane) {
                Text("Show supporting pane")
            }
        } else {
            Text("Supporting pane is shown")
        }
    }
}

@Composable
fun ThreePaneScaffoldScope.SupportingPane(
    modifier: Modifier = Modifier,
) {
    AnimatedPane(modifier = modifier.safeContentPadding()) {
        // Supporting pane content
        Text("This is the supporting pane")
    }
}

Extracting the panes into composables simplifies the use of the SupportingPaneScaffold (compare the following to the complete implementation of the scaffold in the previous section):

val navigator = rememberSupportingPaneScaffoldNavigator()

BackHandler(navigator.canNavigateBack()) {
    navigator.navigateBack()
}

SupportingPaneScaffold(
    directive = navigator.scaffoldDirective,
    value = navigator.scaffoldValue,
    mainPane = {
        MainPane(
            shouldShowSupportingPaneButton = navigator.scaffoldValue.secondary == PaneAdaptedValue.Hidden,
            onNavigateToSupportingPane = { navigator.navigateTo(ThreePaneScaffoldRole.Secondary) }
        )
    },
    supportingPane = { SupportingPane() },
)