# Material 3 Expressive Docs (complete documentation) > Interactive documentation for Material 3 Expressive on Android. Every demo is the real Compose component. # Getting started > Add Material 3 Expressive to a Compose project in a few lines. - Interactive page: https://m3e.ljzapps.app/#foundations/getting-started ## 1. Dependencies Expressive APIs ship with the Material3 1.5 alpha line. Compose Multiplatform mirrors it. ```kotlin // Android (Compose BOM or explicit version) implementation("androidx.compose.material3:material3:1.5.0-alpha22") // Compose Multiplatform implementation("org.jetbrains.compose.material3:material3:1.12.0-alpha03") ``` ## 2. Theme `MaterialExpressiveTheme` turns on the expressive motion scheme and the expressive component defaults. ```kotlin @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun AppTheme(content: @Composable () -> Unit) { MaterialExpressiveTheme( colorScheme = if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme(), motionScheme = MotionScheme.expressive(), content = content, ) } ``` ## 3. Opt in Expressive APIs are experimental. Add `@OptIn(ExperimentalMaterial3ExpressiveApi::class)` to the composable, the file or the module. ```kotlin kotlin { compilerOptions { optIn.add("androidx.compose.material3.ExperimentalMaterial3ExpressiveApi") } } ``` ## 4. First expressive screen ```kotlin Button(onClick = { }, shapes = ButtonDefaults.shapes()) { Text("Hello, Expressive") } LoadingIndicator() ``` --- # For AI agents > An MCP server, llms.txt, markdown pages and in-page tools, so coding agents read these docs instead of guessing. - Interactive page: https://m3e.ljzapps.app/#foundations/ai-agents This site is an interactive app, which agents cannot read. Everything on it is also published as plain files and through a Model Context Protocol server, generated from the same sources as the pages. ## Endpoints (relative to the site origin) - `/mcp`: MCP server, Streamable HTTP, read only, no login - `/llms.txt`: curated index of every page - `/llms-full.txt`: all pages in one file - `/docs/index.json`: ids, categories, APIs and parameters - `/docs/snippets.json`: every example as JSON - `/docs/tokens.json`: baseline colors, type scale and shapes - `/docs/components/{id}.md` and `/docs/foundations/{id}.md`: any page as markdown - `/skills/m3-expressive-docs/SKILL.md`: instructions for coding agents - `/.well-known/mcp/server-card.json`: discovery metadata ## Connect a coding agent ```sh claude mcp add --transport http m3-docs https://YOUR-DOCS-DOMAIN/mcp ``` Cursor `.cursor/mcp.json`: `{"mcpServers":{"m3-docs":{"url":"https://YOUR-DOCS-DOMAIN/mcp"}}}` VS Code `.vscode/mcp.json`: `{"servers":{"m3-docs":{"type":"http","url":"https://YOUR-DOCS-DOMAIN/mcp"}}}` ## Tools `search_docs`, `list_components`, `get_component`, `get_foundation`, `get_snippet`, `find_by_api`, `get_design_tokens`. Resources `m3docs://components/{id}` and prompts `implement_component` and `review_expressive_usage` are also available. ## In the browser Browsers that implement WebMCP (`document.modelContext`) get the tools `search_docs`, `get_page_markdown`, `open_page`, `get_current_page` and `set_theme` for the open tab. --- # Theming > MaterialTheme, dynamic color and how to build a scheme from one seed color. - Interactive page: https://m3e.ljzapps.app/#foundations/theming `MaterialTheme` exposes `colorScheme`, `typography`, `shapes` and `motionScheme` through CompositionLocals. Components read these values, so changing the theme restyles every component below it. ```kotlin MaterialTheme.colorScheme.primary MaterialTheme.typography.titleLargeEmphasized MaterialTheme.shapes.extraLarge MaterialTheme.motionScheme.defaultSpatialSpec() ``` ## Dynamic color on Android Android 12 and later can derive the scheme from the wallpaper. ```kotlin val scheme = if (isSystemInDarkTheme()) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) ``` ## Seed colors on every platform Material Kolor is a Kotlin port of Material Color Utilities and works on all Compose Multiplatform targets. ```kotlin DynamicMaterialExpressiveTheme( seedColor = Color(0xFF6750A4), style = PaletteStyle.Expressive, contrastLevel = Contrast.Default.value, motionScheme = MotionScheme.expressive(), ) { /* content */ } ``` ## Rules of thumb Use roles, not hex values. Pair every container role with its on role. Prefer tonal containers over shadows. Let users pick contrast and respect dark mode. --- # Color > A full color scheme is generated from one seed color. Roles, not hex values, carry the meaning. - Interactive page: https://m3e.ljzapps.app/#foundations/color A full color scheme is generated from one seed color. Roles, not hex values, carry the meaning. Every surface color has a matching content color (`primary` with `onPrimary`, `surfaceContainer` with `onSurface`). Always use them as a pair. Role groups: primary, secondary, tertiary (each with `on`, `container`, `onContainer` and fixed variants), error (with container), surface (`surface`, `surfaceDim`, `surfaceBright`, `surfaceContainerLowest` to `surfaceContainerHighest`, `onSurface`, `onSurfaceVariant`), outline (`outline`, `outlineVariant`) and inverse (`inverseSurface`, `inverseOnSurface`, `inversePrimary`), `scrim`. Surface container roles replace elevation shadows as the main depth cue. ```kotlin Surface(color = MaterialTheme.colorScheme.primaryContainer) { Text("Pairs stay readable", color = MaterialTheme.colorScheme.onPrimaryContainer) } ``` Baseline scheme values are available in `docs/tokens.json`. --- # Typography > Fifteen baseline styles, fifteen emphasized variants, one typeface. - Interactive page: https://m3e.ljzapps.app/#foundations/typography Fifteen baseline styles (display, headline, title, body and label in large, medium and small) and fifteen emphasized variants, in one typeface. The emphasized variants (for example `labelLargeEmphasized`) mark selected, active or important text without changing size. Baseline values are available in `docs/tokens.json`. ```kotlin Text("Selected", style = MaterialTheme.typography.labelLargeEmphasized) ``` This site sets all text in Google Sans Flex, a variable font with weight, width, optical size, slant and roundness axes. Android's default Material typeface is Roboto Flex. --- # Shape > A corner radius scale plus 35 expressive shapes that components can morph between. - Interactive page: https://m3e.ljzapps.app/#foundations/shape A corner radius scale (extraSmall 4 dp, small 8 dp, medium 12 dp, large 16 dp, largeIncreased 20 dp, extraLarge 28 dp, extraLargeIncreased 32 dp, extraExtraLarge 48 dp, plus full) and 35 expressive shapes in `MaterialShapes` that components such as buttons, icon buttons and FABs morph between. ```kotlin Box( Modifier .size(96.dp) .clip(MaterialShapes.Cookie9Sided.toShape()) .background(MaterialTheme.colorScheme.primaryContainer), ) ``` Shapes in `MaterialShapes`: Circle, Square, Slanted, Arch, Fan, Arrow, SemiCircle, Oval, Pill, Triangle, Diamond, ClamShell, Pentagon, Gem, Sunny, VerySunny, Cookie4Sided, Cookie6Sided, Cookie7Sided, Cookie9Sided, Cookie12Sided, Ghostish, Clover4Leaf, Clover8Leaf, Burst, SoftBurst, Boom, SoftBoom, Flower, Puffy, PuffyDiamond, PixelCircle, PixelTriangle, Bun, Heart. --- # Elevation > Depth comes from tonal surface colors first and from shadows only where separation is needed. - Interactive page: https://m3e.ljzapps.app/#foundations/elevation Depth comes from tonal surface colors first and from shadows only where separation is needed. Five container roles run from `surfaceContainerLowest` to `surfaceContainerHighest`. Shadow elevation levels are 0, 1, 3, 6, 8 and 12 dp. ```kotlin Surface( color = MaterialTheme.colorScheme.surfaceContainerHigh, shadowElevation = 3.dp, shape = MaterialTheme.shapes.large, ) { /* content */ } ``` Prefer a container role. Add `shadowElevation` only for floating elements. --- # Motion > Springs replace fixed easing curves. Two schemes ship: standard and expressive. - Interactive page: https://m3e.ljzapps.app/#foundations/motion Springs replace fixed easing curves. Two schemes ship: `MotionScheme.standard()` and `MotionScheme.expressive()`. The expressive spring overshoots and settles with more energy. Spatial specs animate position, size and shape and may overshoot in the expressive scheme. Effects specs animate color and opacity and never overshoot. Each scheme offers fast, default and slow variants of both. ```kotlin val motion = MaterialTheme.motionScheme val offset by animateDpAsState(if (expanded) 200.dp else 0.dp, animationSpec = motion.defaultSpatialSpec()) val alpha by animateFloatAsState(if (visible) 1f else 0f, animationSpec = motion.defaultEffectsSpec()) ``` Screen transitions still use easing and duration tokens: emphasized 500 ms, emphasized decelerate 400 ms (enter), emphasized accelerate 200 ms (exit), standard 300 ms. --- # Layout > Window size classes decide which navigation and how many panes a screen shows. - Interactive page: https://m3e.ljzapps.app/#foundations/layout Window size classes decide which navigation and how many panes a screen shows. | Class | Width | Navigation | Panes | |---|---|---|---| | Compact | 0 to 599 dp | Navigation bar | One | | Medium | 600 to 839 dp | Navigation rail | One or two | | Expanded | 840 to 1199 dp | Rail or drawer | Two | | Large | 1200 to 1599 dp | Navigation drawer | Two or three | | Extra large | 1600 dp and up | Navigation drawer | Three | Canonical layouts: list-detail, supporting pane and feed. Constrain reading width to 840 to 1040 dp on large windows. ```kotlin val sizeClass = currentWindowAdaptiveInfo().windowSizeClass val expanded = sizeClass.isWidthAtLeastBreakpoint(WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND) ``` --- # Interaction states > Hover, focus, press and drag states, and how to observe them. - Interactive page: https://m3e.ljzapps.app/#foundations/interaction-states Components overlay their content color as a state layer: hover 8 percent, focus 10 percent, press 10 percent, drag 16 percent. Observe states with a `MutableInteractionSource`. ```kotlin val source = remember { MutableInteractionSource() } val hovered by source.collectIsHoveredAsState() val pressed by source.collectIsPressedAsState() val focused by source.collectIsFocusedAsState() Surface(onClick = { }, interactionSource = source) { /* content */ } ``` Expressive components also morph their shape while pressed. Pass `shapes` to the component, do not animate it yourself. --- # Iconography > Material Symbols, sizes and how icons take their color. - Interactive page: https://m3e.ljzapps.app/#foundations/iconography Material Symbols is a variable icon font with weight, fill, grade and optical size axes. Use the rounded style with Expressive. In Compose use vector drawables generated from the symbols you need, or the icons artifact for prototypes. Avoid shipping the full font on mobile. Common sizes: 18, 20, 24 (default), 32, 40 and 48 dp. Icons use `LocalContentColor`, which every `Surface` sets from its container role. Only set a tint for emphasis and use a color role. --- # Adaptive design > Layouts that respond to window size, posture and input. - Interactive page: https://m3e.ljzapps.app/#foundations/adaptive-design Design for the window, not the device. Read the window size class and choose navigation and pane count from it. - Compact: navigation bar, modal drawer, one pane, bottom sheets. - Medium: navigation rail, one or two panes. - Expanded: wide rail or permanent drawer, list-detail, supporting pane. - Large and up: permanent drawer, three panes, docked search, side sheets. Support touch, mouse and keyboard. Every control needs a visible focus indicator, hover feedback on pointer devices and a keyboard path. Touch targets stay at least 48 dp. --- # Accessibility > Contrast, touch targets, semantics and focus are part of every component. - Interactive page: https://m3e.ljzapps.app/#foundations/accessibility Contrast, touch targets, semantics and focus are part of every component. Checklist: - Touch targets are at least 48 by 48 dp, even when the visible control is smaller. - Text has a contrast ratio of at least 4.5:1, large text and icons at least 3:1. - Icon-only controls have a `contentDescription`. Decorative icons use null. - State is exposed through semantics, such as Role, selected and toggleable state. - Everything reachable by pointer is reachable by keyboard, with a visible focus indicator. - Layouts survive 200 percent font scale without clipping. - Motion can be reduced, and nothing flashes more than three times per second. ```kotlin IconButton(onClick = onDelete) { Icon(Icons.Rounded.Delete, contentDescription = "Delete message") } ``` --- # Button group > Arranges related buttons and moves the ones that do not fit into an overflow menu. - Category: Actions - API status: Expressive - Compose APIs: `ButtonGroup`, `ButtonGroupDefaults.OverflowIndicator`, `clickableItem`, `toggleableItem`, `customItem` - Interactive page: https://m3e.ljzapps.app/#components/button-group ## When to use ButtonGroup lays out clickable and toggleable items in a row. Items grow slightly when pressed and push their neighbors aside. When the available width is too small, the remaining items move into an overflow menu. Use items through the group scope instead of placing buttons directly. That lets the group measure every item, decide what fits and build the overflow menu from the same data. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Overflow Narrow the window or use the playground to see items move into the menu. ```kotlin var starred by rememberSaveable { mutableStateOf(false) } ButtonGroup( modifier = Modifier.width(360.dp), overflowIndicator = { menuState -> ButtonGroupDefaults.OverflowIndicator(menuState) }, ) { clickableItem( onClick = {}, label = "Edit", icon = { Icon(Icons.Rounded.Edit, contentDescription = null) }, ) toggleableItem( checked = starred, label = "Star", onCheckedChange = { starred = it }, icon = { Icon(Icons.Rounded.Star, contentDescription = null) }, ) clickableItem( onClick = {}, label = "Share", icon = { Icon(Icons.Rounded.Share, contentDescription = null) }, ) clickableItem( onClick = {}, label = "Delete", icon = { Icon(Icons.Rounded.Delete, contentDescription = null) }, ) } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `overflowIndicator` | `@Composable (ButtonGroupMenuState) -> Unit` | | Shown when items do not fit. Use ButtonGroupDefaults.OverflowIndicator. | | `modifier` | `Modifier` | `Modifier` | Constrain the width to control when overflow happens. | | `expandedRatio` | `Float` | `0.15f` | How much a pressed item grows. | | `horizontalArrangement` | `Arrangement.Horizontal` | `ButtonGroupDefaults.HorizontalArrangement` | Spacing between items. | | `verticalAlignment` | `Alignment.Vertical` | `Alignment.Top` | Cross axis alignment of items. | | `content` | `ButtonGroupScope.() -> Unit` | | Declare items with clickableItem, toggleableItem and customItem. | ## Guidelines Do: - Group actions that are closely related. - Put the most important actions first, they overflow last. Don't: - Do not mix unrelated actions in one group. - Do not rely on overflow for primary actions on small screens. ## Accessibility - Every item needs a label. The label doubles as the menu text when the item overflows. - Icon-only items still need a label string, which is announced by screen readers. - The overflow indicator is an icon button with a built-in content description. ## Related components - [Connected button group](https://m3e.ljzapps.app/docs/components/connected-button-group.md) - [Buttons](https://m3e.ljzapps.app/docs/components/button.md) - [Split button](https://m3e.ljzapps.app/docs/components/split-button.md) - [Segmented button](https://m3e.ljzapps.app/docs/components/segmented-button.md) --- # Buttons > Let people take an action. Five types, five sizes, and shapes that morph when pressed. - Category: Actions - API status: Expressive - Compose APIs: `Button`, `FilledTonalButton`, `ElevatedButton`, `OutlinedButton`, `TextButton`, `ButtonDefaults` - Interactive page: https://m3e.ljzapps.app/#components/button ## When to use Use a filled button for the single most important action on a screen, a tonal button for secondary actions, an outlined button for medium emphasis and a text button for the lowest emphasis. Expressive buttons come in five sizes. Pair the height with the matching content padding, text style and icon size from ButtonDefaults so the proportions stay correct. Pass a ButtonShapes to animate the container between a round and a squared shape when the button is pressed. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Types From highest to lowest emphasis. ```kotlin Button(onClick = {}) { Text("Filled") } FilledTonalButton(onClick = {}) { Text("Tonal") } ElevatedButton(onClick = {}) { Text("Elevated") } OutlinedButton(onClick = {}) { Text("Outlined") } TextButton(onClick = {}) { Text("Text") } ``` ### Sizes Extra small to extra large. ```kotlin val heights = listOf( "XS" to ButtonDefaults.ExtraSmallContainerHeight, "S" to ButtonDefaults.MinHeight, "M" to ButtonDefaults.MediumContainerHeight, "L" to ButtonDefaults.LargeContainerHeight, "XL" to ButtonDefaults.ExtraLargeContainerHeight, ) heights.forEach { (label, height) -> Button( onClick = {}, shapes = ButtonDefaults.shapesFor(height), modifier = Modifier.heightIn(min = height), contentPadding = ButtonDefaults.contentPaddingFor(height), ) { Text(label, style = ButtonDefaults.textStyleFor(height)) } } ``` ### With icon Use the icon helpers so size and spacing follow the button size. ```kotlin val height = ButtonDefaults.MediumContainerHeight Button( onClick = {}, shapes = ButtonDefaults.shapesFor(height), modifier = Modifier.heightIn(min = height), contentPadding = ButtonDefaults.contentPaddingFor(height, hasStartIcon = true), ) { Icon( imageVector = Icons.Rounded.Favorite, contentDescription = null, modifier = Modifier.size(ButtonDefaults.iconSizeFor(height)), ) Spacer(Modifier.width(ButtonDefaults.iconSpacingFor(height))) Text("Like", style = ButtonDefaults.textStyleFor(height)) } ``` ### Shape morphing The shapes parameter animates the corners on press. ```kotlin Button( onClick = {}, shapes = ButtonDefaults.shapes(), ) { Text("Round, squares on press") } Button( onClick = {}, shapes = ButtonDefaults.shapes( shape = ButtonDefaults.squareShape, pressedShape = ButtonDefaults.pressedShape, ), ) { Text("Square, rounds on press") } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `onClick` | `() -> Unit` | | Called when the button is clicked. | | `shapes` | `ButtonShapes` | `ButtonDefaults.shapes()` | Resting and pressed shape. Use shapesFor(height) to match a size. | | `modifier` | `Modifier` | `Modifier` | Apply a minimum height with heightIn to select a size. | | `enabled` | `Boolean` | `true` | When false the button ignores input and uses disabled colors. | | `colors` | `ButtonColors` | `ButtonDefaults.buttonColors()` | Container and content colors for enabled and disabled states. | | `contentPadding` | `PaddingValues` | `contentPaddingFor(MinHeight)` | Use contentPaddingFor(height, hasStartIcon) for the matching padding. | | `interactionSource` | `MutableInteractionSource?` | `null` | Observe or emit interactions such as press and hover. | ## Guidelines Do: - Use one filled button per screen section. - Use verbs in the label, such as Save or Send. - Keep related buttons in a button group. Don't: - Do not place several filled buttons next to each other. - Do not mix sizes within one row of actions. - Do not use a button to navigate between destinations. Use navigation components. ## Accessibility - Buttons expose the Button role and their label to screen readers. Keep labels short and action oriented. - Extra small buttons are visually 32 dp tall but still get a 48 dp touch target through the minimum interactive size. - Disabled buttons are skipped by keyboard focus. Do not rely on color alone to explain why a button is disabled. ## Related components - [Connected button group](https://m3e.ljzapps.app/docs/components/connected-button-group.md) - [Icon button](https://m3e.ljzapps.app/docs/components/icon-button.md) - [Split button](https://m3e.ljzapps.app/docs/components/split-button.md) - [Floating action button](https://m3e.ljzapps.app/docs/components/fab.md) --- # Connected button group > Toggle buttons that share one row and morph their shape on press and selection. - Category: Actions - API status: Expressive - Compose APIs: `ToggleButton`, `ButtonGroupDefaults.connectedLeadingButtonShapes`, `ButtonGroupDefaults.ConnectedSpaceBetween` - Interactive page: https://m3e.ljzapps.app/#components/connected-button-group ## When to use Use a connected group for a small set of related options where one or more can be active. The outer ends are fully round, the inner corners are small, and the selected button turns fully round. Build it from a Row of ToggleButtons. Pick the leading, middle or trailing shapes from ButtonGroupDefaults by position, and use ConnectedSpaceBetween for the gap. ## Examples ### Single selection Exactly one option is active at a time. ```kotlin val options = listOf("Day", "Week", "Month") var selected by rememberSaveable { mutableIntStateOf(0) } Row(horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween)) { options.forEachIndexed { index, label -> ToggleButton( checked = selected == index, onCheckedChange = { selected = index }, shapes = when (index) { 0 -> ButtonGroupDefaults.connectedLeadingButtonShapes() options.lastIndex -> ButtonGroupDefaults.connectedTrailingButtonShapes() else -> ButtonGroupDefaults.connectedMiddleButtonShapes() }, ) { Text(label) } } } ``` ### Multiple selection Any number of options can be active. ```kotlin val options = listOf("Bold", "Italic", "Underline") val selected = remember { mutableStateOf(setOf(0)) } Row(horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween)) { options.forEachIndexed { index, label -> ToggleButton( checked = index in selected.value, onCheckedChange = { checked -> selected.value = if (checked) selected.value + index else selected.value - index }, shapes = when (index) { 0 -> ButtonGroupDefaults.connectedLeadingButtonShapes() options.lastIndex -> ButtonGroupDefaults.connectedTrailingButtonShapes() else -> ButtonGroupDefaults.connectedMiddleButtonShapes() }, ) { Text(label) } } } ``` ### Icons only Icon labels need a content description. ```kotlin val options = listOf(Icons.Rounded.ViewDay to "Day view", Icons.Rounded.ViewWeek to "Week view") var selected by rememberSaveable { mutableIntStateOf(0) } Row(horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween)) { options.forEachIndexed { index, (icon, description) -> ToggleButton( checked = selected == index, onCheckedChange = { selected = index }, shapes = if (index == 0) { ButtonGroupDefaults.connectedLeadingButtonShapes() } else { ButtonGroupDefaults.connectedTrailingButtonShapes() }, ) { Icon(icon, contentDescription = description, modifier = Modifier.size(20.dp)) } } } ``` ## Guidelines Do: - Keep labels short and parallel. - Use three to five options. Don't: - Do not use a connected group for navigation. - Do not wrap options onto a second row. ## Accessibility - Each ToggleButton exposes its checked state. Group related buttons with a visible label nearby. - Icon-only options must have a contentDescription. ## Related components - [Button group](https://m3e.ljzapps.app/docs/components/button-group.md) - [Toggle button](https://m3e.ljzapps.app/docs/components/toggle-button.md) - [Segmented button](https://m3e.ljzapps.app/docs/components/segmented-button.md) --- # Extended FAB > A floating action button with a text label, in four sizes, that collapses to its icon. - Category: Actions - API status: Expressive - Compose APIs: `ExtendedFloatingActionButton`, `SmallExtendedFloatingActionButton`, `MediumExtendedFloatingActionButton`, `LargeExtendedFloatingActionButton` - Interactive page: https://m3e.ljzapps.app/#components/extended-fab ## When to use Use an extended FAB when the action needs a label to be understood. Expand it at the top of a screen and collapse it to the icon while the user scrolls down, then expand it again on scroll up. The text and icon overloads handle the expand and collapse animation. Sizes follow the regular FAB sizes. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Sizes ```kotlin SmallExtendedFloatingActionButton(text = { Text("Small") }, icon = { Icon(Icons.Rounded.Edit, contentDescription = null) }, onClick = {}) ExtendedFloatingActionButton(text = { Text("Regular") }, icon = { Icon(Icons.Rounded.Edit, contentDescription = null) }, onClick = {}) MediumExtendedFloatingActionButton(text = { Text("Medium") }, icon = { Icon(Icons.Rounded.Edit, contentDescription = null) }, onClick = {}) LargeExtendedFloatingActionButton(text = { Text("Large") }, icon = { Icon(Icons.Rounded.Edit, contentDescription = null) }, onClick = {}) ``` ### Collapse and expand Tap the button. ```kotlin var expanded by rememberSaveable { mutableStateOf(true) } ExtendedFloatingActionButton( text = { Text("New message") }, icon = { Icon(Icons.Rounded.Edit, contentDescription = null) }, expanded = expanded, onClick = { expanded = !expanded }, ) ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `text` | `@Composable () -> Unit` | | The label. | | `icon` | `@Composable () -> Unit` | | The icon, shown in both states. | | `expanded` | `Boolean` | `true` | When false only the icon remains. | | `containerColor` | `Color` | `FloatingActionButtonDefaults.containerColor` | Use a container color role. | ## Guidelines Do: - Use for the primary action only. - Collapse on scroll. Don't: - Do not use long labels. - Do not show it with another FAB. ## Accessibility - Keep the text slot even when collapsed so the action keeps its accessible name. - The label should start with a verb. ## Related components - [Floating action button](https://m3e.ljzapps.app/docs/components/fab.md) - [FAB menu](https://m3e.ljzapps.app/docs/components/fab-menu.md) - [Buttons](https://m3e.ljzapps.app/docs/components/button.md) --- # FAB menu > A FAB that opens into a stack of related actions. - Category: Actions - API status: Expressive - Compose APIs: `FloatingActionButtonMenu`, `FloatingActionButtonMenuItem`, `ToggleFloatingActionButton` - Interactive page: https://m3e.ljzapps.app/#components/fab-menu ## When to use Use a FAB menu when a screen has several related primary actions. A toggle FAB morphs into a close button and reveals labeled actions above it. The menu moves keyboard focus to the first item when it opens, and Tab or the down arrow moves into it. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Three actions Open the menu with the toggle button. ```kotlin var expanded by rememberSaveable { mutableStateOf(false) } val items = listOf( Icons.Rounded.Email to "Email", Icons.Rounded.Person to "Contact", Icons.Rounded.Edit to "Note", ) Box(modifier = Modifier.fillMaxWidth().height(360.dp)) { FloatingActionButtonMenu( expanded = expanded, modifier = Modifier.align(Alignment.BottomEnd), button = { ToggleFloatingActionButton( checked = expanded, onCheckedChange = { expanded = !expanded }, ) { val icon by remember { derivedStateOf { if (checkedProgress > 0.5f) Icons.Rounded.Close else Icons.Rounded.Add } } Icon( imageVector = icon, contentDescription = if (expanded) "Close menu" else "Open menu", modifier = Modifier.animateIcon({ checkedProgress }), ) } }, ) { items.forEach { (icon, label) -> FloatingActionButtonMenuItem( onClick = { expanded = false }, icon = { Icon(icon, contentDescription = null) }, text = { Text(label) }, ) } } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `expanded` | `Boolean` | | Whether the items are visible. | | `button` | `@Composable () -> Unit` | | Usually a ToggleFloatingActionButton. | | `horizontalAlignment` | `Alignment.Horizontal` | `Alignment.End` | Aligns the items with the button. | | `content` | `FloatingActionButtonMenuScope.() -> Unit` | | Declare FloatingActionButtonMenuItem entries. | ## Guidelines Do: - Use two to six items. - Close the menu after an item is chosen. Don't: - Do not use it for a single action. Use a plain FAB. - Do not nest menus. ## Accessibility - Describe the toggle button state, for example Open menu and Close menu. - Each item is a button with a text label. Keep labels short. - Collapse the menu when focus leaves it and when an item is activated. ## Related components - [Floating action button](https://m3e.ljzapps.app/docs/components/fab.md) - [Split button](https://m3e.ljzapps.app/docs/components/split-button.md) - [Floating toolbar](https://m3e.ljzapps.app/docs/components/floating-toolbar.md) --- # Floating action button > The primary action of a screen, in five sizes. The extended variant adds a label. - Category: Actions - API status: Expressive - Compose APIs: `FloatingActionButton`, `SmallFloatingActionButton`, `MediumFloatingActionButton`, `LargeFloatingActionButton`, `ExtendedFloatingActionButton` - Interactive page: https://m3e.ljzapps.app/#components/fab ## When to use Use a FAB for the single most important action on a screen, such as compose, create or add. Place it where it is easy to reach and keep it visible while content scrolls. Collapse an extended FAB to its icon while the user scrolls down and expand it again when they scroll up. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Sizes Small, regular, medium and large. ```kotlin SmallFloatingActionButton(onClick = {}) { Icon(Icons.Rounded.Add, contentDescription = "Add") } FloatingActionButton(onClick = {}) { Icon(Icons.Rounded.Add, contentDescription = "Add") } MediumFloatingActionButton(onClick = {}) { Icon(Icons.Rounded.Add, contentDescription = "Add") } LargeFloatingActionButton(onClick = {}) { Icon( imageVector = Icons.Rounded.Add, contentDescription = "Add", modifier = Modifier.size(FloatingActionButtonDefaults.LargeIconSize), ) } ``` ### Extended Tap to collapse and expand. ```kotlin var expanded by rememberSaveable { mutableStateOf(true) } ExtendedFloatingActionButton( text = { Text("Compose") }, icon = { Icon(Icons.Rounded.Edit, contentDescription = null) }, onClick = { expanded = !expanded }, expanded = expanded, ) ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `onClick` | `() -> Unit` | | Called when the FAB is clicked. | | `containerColor` | `Color` | `FloatingActionButtonDefaults.containerColor` | Use the container roles, such as primaryContainer. | | `expanded` | `Boolean` | `true` | Extended FAB only. When false only the icon remains. | | `elevation` | `FloatingActionButtonElevation` | `elevation()` | Shadow elevation for the FAB states. | ## Guidelines Do: - Use one FAB per screen. - Keep the action constructive. Don't: - Do not use a FAB for destructive actions. - Do not stack several FABs. Use the FAB menu. ## Accessibility - A FAB with only an icon needs a contentDescription. - In the collapsed extended FAB the text is hidden visually but still exposed to screen readers when you keep the text slot. ## Related components - [FAB menu](https://m3e.ljzapps.app/docs/components/fab-menu.md) - [Buttons](https://m3e.ljzapps.app/docs/components/button.md) - [Floating toolbar](https://m3e.ljzapps.app/docs/components/floating-toolbar.md) --- # Icon button > A compact button that shows one icon. Four types, five sizes and three widths. - Category: Actions - API status: Expressive - Compose APIs: `IconButton`, `FilledIconButton`, `FilledTonalIconButton`, `OutlinedIconButton`, `IconToggleButton`, `IconButtonDefaults` - Interactive page: https://m3e.ljzapps.app/#components/icon-button ## When to use Use an icon button for familiar actions where the icon alone is clear, such as favorite, share or close. Choose the size with the matching container size from IconButtonDefaults. The width option changes the container without changing the icon, which helps in dense toolbars. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Types ```kotlin IconButton(onClick = {}) { Icon(Icons.Rounded.Favorite, contentDescription = "Favorite") } FilledIconButton(onClick = {}) { Icon(Icons.Rounded.Favorite, contentDescription = "Favorite") } FilledTonalIconButton(onClick = {}) { Icon(Icons.Rounded.Favorite, contentDescription = "Favorite") } OutlinedIconButton(onClick = {}) { Icon(Icons.Rounded.Favorite, contentDescription = "Favorite") } ``` ### Toggle The shape morphs between unselected and selected. ```kotlin var liked by rememberSaveable { mutableStateOf(false) } FilledIconToggleButton( checked = liked, onCheckedChange = { liked = it }, shapes = IconButtonDefaults.toggleableShapes(), ) { Icon( imageVector = if (liked) Icons.Rounded.Favorite else Icons.Rounded.FavoriteBorder, contentDescription = if (liked) "Remove from favorites" else "Add to favorites", ) } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `onClick` | `() -> Unit` | | Called when the button is clicked. | | `shapes` | `IconButtonShapes` | `IconButtonDefaults.shapes()` | Resting and pressed shape. | | `modifier` | `Modifier` | `Modifier` | Apply Modifier.size(containerSize) to choose a size and width. | | `colors` | `IconButtonColors` | `iconButtonColors()` | Container and content colors. | | `content` | `@Composable () -> Unit` | | Usually a single Icon. | ## Guidelines Do: - Use well known icons. - Describe the action, not the icon, in the content description. Don't: - Do not use an icon button for an action that needs a text label to be understood. - Do not nest an icon button inside another clickable. ## Accessibility - Always set a contentDescription on the Icon. Icon buttons have no visible label. - The touch target is at least 48 dp even for the extra small size. - Toggle buttons expose their checked state to assistive technology. ## Related components - [Buttons](https://m3e.ljzapps.app/docs/components/button.md) - [Toggle button](https://m3e.ljzapps.app/docs/components/toggle-button.md) - [Floating action button](https://m3e.ljzapps.app/docs/components/fab.md) --- # Segmented button > A row of two to five options for choosing one or several of them. - Category: Actions - API status: Stable - Compose APIs: `SingleChoiceSegmentedButtonRow`, `MultiChoiceSegmentedButtonRow`, `SegmentedButton`, `SegmentedButtonDefaults` - Interactive page: https://m3e.ljzapps.app/#components/segmented-button ## When to use Segmented buttons help people select options, switch views or sort elements. Use single choice when exactly one option applies and multi choice when any number can be active. Material 3 Expressive recommends connected button groups for new designs. The segmented button remains available and is not deprecated. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Single choice ```kotlin val options = listOf("Day", "Week", "Month") var selected by rememberSaveable { mutableIntStateOf(0) } SingleChoiceSegmentedButtonRow { options.forEachIndexed { index, label -> SegmentedButton( selected = selected == index, onClick = { selected = index }, shape = SegmentedButtonDefaults.itemShape(index, options.size), ) { Text(label) } } } ``` ### Multiple choice ```kotlin val options = listOf("Bold", "Italic", "Underline") var checked by remember { mutableStateOf(setOf()) } MultiChoiceSegmentedButtonRow { options.forEachIndexed { index, label -> SegmentedButton( checked = index in checked, onCheckedChange = { isChecked -> checked = if (isChecked) checked + index else checked - index }, shape = SegmentedButtonDefaults.itemShape(index, options.size), ) { Text(label) } } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `selected / checked` | `Boolean` | | Whether the segment is active. | | `shape` | `Shape` | | Use SegmentedButtonDefaults.itemShape(index, count). | | `icon` | `@Composable () -> Unit` | `SegmentedButtonDefaults.Icon(selected)` | Leading icon, by default a check mark when selected. | | `colors` | `SegmentedButtonColors` | `SegmentedButtonDefaults.colors()` | Colors per state. | ## Guidelines Do: - Use two to five segments. - Show the selection state with more than color. Don't: - Do not use for navigation between pages. - Do not mix text and icon-only segments. ## Accessibility - Single choice rows expose the radio group semantics, multi choice rows expose checkboxes. - Keep labels short enough to stay on one line at 200 percent font scale. ## Related components - [Connected button group](https://m3e.ljzapps.app/docs/components/connected-button-group.md) - [Toggle button](https://m3e.ljzapps.app/docs/components/toggle-button.md) - [Chips](https://m3e.ljzapps.app/docs/components/chips.md) --- # Split button > Pairs a primary action with a menu of related alternatives. - Category: Actions - API status: Expressive - Compose APIs: `SplitButtonLayout`, `SplitButtonDefaults.LeadingButton`, `SplitButtonDefaults.TrailingButton` - Interactive page: https://m3e.ljzapps.app/#components/split-button ## When to use A split button has two touch targets: the leading button runs the main action and the trailing button opens a menu. The trailing button toggles, and its shape and arrow reflect whether the menu is open. Compose the pieces with SplitButtonLayout and the buttons from SplitButtonDefaults. Pick the shapes, padding and icon sizes for the height with the ...For(height) helpers. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Menu on the trailing button The arrow rotates while the menu is open. ```kotlin var expanded by remember { mutableStateOf(false) } SplitButtonLayout( leadingButton = { SplitButtonDefaults.LeadingButton(onClick = {}) { Icon( Icons.Rounded.Edit, contentDescription = null, modifier = Modifier.size(SplitButtonDefaults.LeadingIconSize), ) Spacer(Modifier.width(ButtonDefaults.IconSpacing)) Text("Edit") } }, trailingButton = { SplitButtonDefaults.TrailingButton( checked = expanded, onCheckedChange = { expanded = it }, modifier = Modifier.semantics { contentDescription = "More options" }, ) { val rotation by animateFloatAsState(if (expanded) 180f else 0f) Icon( Icons.Rounded.KeyboardArrowDown, contentDescription = null, modifier = Modifier.size(SplitButtonDefaults.TrailingIconSize).rotate(rotation), ) } DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { DropdownMenuItem(text = { Text("Edit as copy") }, onClick = { expanded = false }) DropdownMenuItem(text = { Text("Rename") }, onClick = { expanded = false }) } }, ) ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `leadingButton` | `@Composable () -> Unit` | | The primary action. | | `trailingButton` | `@Composable () -> Unit` | | The toggle that opens the menu. | | `spacing` | `Dp` | `SplitButtonDefaults.Spacing` | Gap between the two buttons. | ## Guidelines Do: - Make the leading action the most common one. - Keep the menu short. Don't: - Do not hide the primary action in the menu. - Do not use it when there is only one action. ## Accessibility - Give the trailing button a content description such as More options, because it only shows an arrow. - The trailing button exposes its expanded state. Return focus to it when the menu closes. ## Related components - [Buttons](https://m3e.ljzapps.app/docs/components/button.md) - [Button group](https://m3e.ljzapps.app/docs/components/button-group.md) - [Menu](https://m3e.ljzapps.app/docs/components/menu.md) --- # Toggle button > A button that keeps a selected state and morphs between round and square when it changes. - Category: Actions - API status: Expressive - Compose APIs: `ToggleButton`, `TonalToggleButton`, `ElevatedToggleButton`, `OutlinedToggleButton`, `ToggleButtonDefaults` - Interactive page: https://m3e.ljzapps.app/#components/toggle-button ## When to use Use a toggle button to switch a single option on or off, such as a filter or a favorite. The shape is part of the state: unselected buttons are round, selected buttons turn square. Pick the shapes for the height with ToggleButtonDefaults.shapesFor so the pressed and checked shapes match the size. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Types All four types, toggle each one. ```kotlin var a by rememberSaveable { mutableStateOf(true) } var b by rememberSaveable { mutableStateOf(false) } var c by rememberSaveable { mutableStateOf(false) } var d by rememberSaveable { mutableStateOf(false) } ToggleButton(checked = a, onCheckedChange = { a = it }) { Text("Filled") } TonalToggleButton(checked = b, onCheckedChange = { b = it }) { Text("Tonal") } ElevatedToggleButton(checked = c, onCheckedChange = { c = it }) { Text("Elevated") } OutlinedToggleButton(checked = d, onCheckedChange = { d = it }) { Text("Outlined") } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `checked` | `Boolean` | | Whether the button is selected. | | `onCheckedChange` | `(Boolean) -> Unit` | | Called with the requested new state. | | `shapes` | `ToggleButtonShapes` | `ToggleButtonDefaults.shapesFor(MinHeight)` | Resting, pressed and checked shapes. The button morphs between them. | | `colors` | `ToggleButtonColors` | `toggleButtonColors()` | Container and content colors for checked and unchecked states. | | `contentPadding` | `PaddingValues` | `contentPaddingFor(MinHeight)` | Padding that matches the height. | ## Guidelines Do: - Use for binary options that apply immediately. - Keep the label constant, the state shows the change. Don't: - Do not change the label from On to Off. - Do not use it for one-time actions. ## Accessibility - The button exposes a toggleable state and the Checkbox-like role, so screen readers announce selected or not selected. - Do not communicate the state with the shape alone. The color difference is part of the default colors. ## Related components - [Connected button group](https://m3e.ljzapps.app/docs/components/connected-button-group.md) - [Buttons](https://m3e.ljzapps.app/docs/components/button.md) - [Icon button](https://m3e.ljzapps.app/docs/components/icon-button.md) --- # Badge > A small marker that shows a status or a count on top of another element. - Category: Communication - API status: Stable - Compose APIs: `BadgedBox`, `Badge`, `BadgeDefaults` - Interactive page: https://m3e.ljzapps.app/#components/badge ## When to use A small badge is a dot that says something changed. A large badge carries a number or a short text. Attach badges to icons in navigation bars, rails and toolbars with BadgedBox. Cap counts at 99 and show 99+ above that. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Dot and count ```kotlin BadgedBox(badge = { Badge() }) { Icon(Icons.Rounded.NotificationsActive, contentDescription = "Alerts") } BadgedBox(badge = { Badge { Text("3") } }) { Icon(Icons.Rounded.Email, contentDescription = "Inbox, 3 unread") } BadgedBox(badge = { Badge { Text("99+") } }) { Icon(Icons.Rounded.Notifications, contentDescription = "Notifications, more than 99") } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `badge` | `@Composable BoxScope.() -> Unit` | | The Badge to show on the anchor. | | `content` | `@Composable BoxScope.() -> Unit` | | The anchor, usually an Icon. | | `containerColor` | `Color` | `BadgeDefaults.containerColor` | Badge color, error by default. | ## Guidelines Do: - Use a dot for a new state and a number for a count. - Remove the badge when there is nothing new. Don't: - Do not use badges as buttons. - Do not show long text in a badge. ## Accessibility - Put the count into the anchor's content description, for example Inbox, 3 unread. - A dot badge without text needs a description of what changed. ## Related components - [Navigation bar](https://m3e.ljzapps.app/docs/components/navigation-bar.md) - [Icon button](https://m3e.ljzapps.app/docs/components/icon-button.md) --- # Icon > Draws a vector or bitmap symbol that follows the content color. - Category: Communication - API status: Stable - Compose APIs: `Icon`, `LocalContentColor`, `ImageVector` - Interactive page: https://m3e.ljzapps.app/#components/icon ## When to use Use icons to support text or to label compact controls. Icons take their color from the surface they sit on, so they stay readable in every theme. Material Symbols are the recommended icon set. Icons are 24 dp by default. Inside buttons use the size helpers from ButtonDefaults. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Icons and tint ```kotlin Icon(Icons.Rounded.Home, contentDescription = "Home") Icon(Icons.Rounded.Settings, contentDescription = "Settings", tint = MaterialTheme.colorScheme.primary) Icon(Icons.Rounded.Interests, contentDescription = null, modifier = Modifier.size(40.dp)) ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `imageVector / painter / bitmap` | `ImageVector \| Painter \| ImageBitmap` | | The image to draw. | | `contentDescription` | `String?` | | Accessible name. Use null only for decorative icons. | | `tint` | `Color` | `LocalContentColor.current` | Tint color. | ## Guidelines Do: - Use one icon style across the app. - Match the icon size to the text next to it. Don't: - Do not use an icon alone when its meaning is unclear. - Do not tint with hardcoded colors. ## Accessibility - Informative icons need a contentDescription that describes the meaning, not the shape. - Decorative icons next to text pass null so screen readers skip them. ## Related components - [Icon button](https://m3e.ljzapps.app/docs/components/icon-button.md) - [Buttons](https://m3e.ljzapps.app/docs/components/button.md) - [Badge](https://m3e.ljzapps.app/docs/components/badge.md) --- # Loading indicator > A morphing shape that shows an indeterminate wait of up to a few seconds. - Category: Communication - API status: Expressive - Compose APIs: `LoadingIndicator`, `ContainedLoadingIndicator`, `LoadingIndicatorDefaults` - Interactive page: https://m3e.ljzapps.app/#components/loading-indicator ## When to use Use it for short waits such as pull to refresh. For longer or measurable work, use a progress indicator. The indicator cycles through a sequence of shapes while it rotates. Pass a progress lambda to drive the morph from a known value instead. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Plain and contained The contained version sits on a rounded container. ```kotlin LoadingIndicator() ContainedLoadingIndicator() ``` ### Driven by progress The morph follows the progress value. ```kotlin val transition = rememberInfiniteTransition(label = "progress") val progress by transition.animateFloat( initialValue = 0f, targetValue = 1f, animationSpec = infiniteRepeatable(tween(3_000, easing = LinearEasing), RepeatMode.Restart), label = "progress", ) LoadingIndicator(progress = { progress }) ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `progress` | `() -> Float` | | Optional. When set, the indicator is determinate and morphs with the value from 0 to 1. | | `color` | `Color` | `LoadingIndicatorDefaults.indicatorColor` | Color of the shape. | | `polygons` | `List` | `IndeterminateIndicatorPolygons` | Shapes to morph between. | | `containerColor` | `Color` | `containedContainerColor` | Contained only. Color of the container. | ## Guidelines Do: - Use for waits of one to a few seconds. - Place it where the content will appear. Don't: - Do not show it for waits you can measure. Use a progress indicator. - Do not show several at once. ## Accessibility - Announce the loading state through the surrounding content, for example with a live region or a status message. - Do not use it alone to block interaction for long. Offer a way to cancel. ## Related components - [Progress indicator](https://m3e.ljzapps.app/docs/components/progress-indicator.md) - [Pull to refresh](https://m3e.ljzapps.app/docs/components/pull-to-refresh.md) --- # Progress indicator > Shows how far an operation has come, as a bar or a ring, flat or wavy. - Category: Communication - API status: Expressive - Compose APIs: `LinearProgressIndicator`, `CircularProgressIndicator`, `LinearWavyProgressIndicator`, `CircularWavyProgressIndicator` - Interactive page: https://m3e.ljzapps.app/#components/progress-indicator ## When to use Use a determinate indicator when you know the progress, and an indeterminate one when you only know that work is going on. The wavy shapes are the Expressive style. They suit longer waits where the user watches the screen. Wavy indicators calm down to a straight line when the work ends and when the amplitude is zero. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Linear Flat, wavy and indeterminate. ```kotlin Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(24.dp)) { LinearProgressIndicator(progress = { 0.4f }, modifier = Modifier.fillMaxWidth()) LinearWavyProgressIndicator(progress = { 0.4f }, modifier = Modifier.fillMaxWidth()) LinearWavyProgressIndicator(modifier = Modifier.fillMaxWidth()) } ``` ### Circular Flat, wavy and indeterminate. ```kotlin CircularProgressIndicator(progress = { 0.4f }) CircularWavyProgressIndicator(progress = { 0.4f }) CircularWavyProgressIndicator() ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `progress` | `() -> Float` | | Current value from 0 to 1. Omit for indeterminate. | | `amplitude` | `Float \| (Float) -> Float` | `1f` | Wave height. Pass a lambda to shape it by progress. | | `wavelength` | `Dp` | `WavyProgressIndicatorDefaults` | Distance between wave peaks. | | `color / trackColor` | `Color` | `indicatorColor / trackColor` | Colors of the active part and the track. | ## Guidelines Do: - Use determinate indicators whenever you can measure. - Keep indicators close to the content they affect. Don't: - Do not combine a linear and a circular indicator for the same task. - Do not animate faster than the real progress. ## Accessibility - Determinate indicators expose their value as a range to screen readers. - Pair the indicator with text such as 40 percent complete when the exact value matters. - Do not rely on the wave alone to convey the value. ## Related components - [Loading indicator](https://m3e.ljzapps.app/docs/components/loading-indicator.md) - [Pull to refresh](https://m3e.ljzapps.app/docs/components/pull-to-refresh.md) - [Slider](https://m3e.ljzapps.app/docs/components/slider.md) --- # Snackbar > A short message about a process that just happened, with an optional action. - Category: Communication - API status: Stable - Compose APIs: `SnackbarHost`, `SnackbarHostState`, `Snackbar`, `SnackbarDuration` - Interactive page: https://m3e.ljzapps.app/#components/snackbar ## When to use Show a snackbar to confirm a result such as archived or sent, and offer a way to undo it. It appears at the bottom of the screen without interrupting what the user is doing. Drive snackbars through a SnackbarHostState. showSnackbar suspends until the snackbar is dismissed and returns whether the action was used. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### With undo action The result of the action is read from showSnackbar. ```kotlin val snackbarHostState = remember { SnackbarHostState() } val scope = rememberCoroutineScope() var result by remember { mutableStateOf("") } Box(modifier = Modifier.fillMaxWidth().height(220.dp)) { Button( onClick = { scope.launch { val outcome = snackbarHostState.showSnackbar( message = "Message archived", actionLabel = "Undo", withDismissAction = true, ) result = if (outcome == SnackbarResult.ActionPerformed) "Undo pressed" else "" } }, modifier = Modifier.align(Alignment.TopStart), ) { Text("Show snackbar") } if (result.isNotEmpty()) { Text(result, modifier = Modifier.align(Alignment.TopEnd).padding(top = 12.dp)) } SnackbarHost(hostState = snackbarHostState, modifier = Modifier.align(Alignment.BottomCenter)) } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `message` | `String` | | Text to show. | | `actionLabel` | `String?` | `null` | Label of the single action. Without one the snackbar is a plain message. | | `withDismissAction` | `Boolean` | `false` | Adds a close icon. | | `duration` | `SnackbarDuration` | `Short, or Indefinite with an action` | How long it stays. | ## Guidelines Do: - Use for confirmations the user can ignore. - Show one snackbar at a time. Don't: - Do not use for errors that block progress. Use a dialog or inline message. - Do not add more than one action. ## Accessibility - Snackbars are announced as live regions. Keep the message short and complete. - An action that expires after a few seconds is hard to use. Prefer Indefinite or a dismiss icon when the action matters. ## Related components - [Dialog](https://m3e.ljzapps.app/docs/components/dialog.md) - [Buttons](https://m3e.ljzapps.app/docs/components/button.md) --- # Tooltip > A short label or explanation that appears on hover, focus or long press. - Category: Communication - API status: Stable - Compose APIs: `TooltipBox`, `PlainTooltip`, `RichTooltip`, `TooltipDefaults`, `rememberTooltipState` - Interactive page: https://m3e.ljzapps.app/#components/tooltip ## When to use Plain tooltips name an element, usually an icon button. Rich tooltips carry a title, a few lines of text and an optional action. Hover an icon button with the pointer, focus it with the keyboard or long press it to see the tooltip. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Plain Hover or focus the button. ```kotlin TooltipBox( positionProvider = TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above), tooltip = { PlainTooltip { Text("Share this page") } }, state = rememberTooltipState(), ) { IconButton(onClick = {}) { Icon(Icons.Rounded.Share, contentDescription = "Share") } } ``` ### Rich, with action Persistent until dismissed. ```kotlin val state = rememberTooltipState(isPersistent = true) val scope = rememberCoroutineScope() TooltipBox( positionProvider = TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above), tooltip = { RichTooltip( title = { Text("Sync") }, action = { TextButton(onClick = { scope.launch { state.dismiss() } }) { Text("Got it") } }, ) { Text("Changes are saved on this device first and uploaded when you are online.") } }, state = state, hasAction = true, ) { Button(onClick = { scope.launch { state.show() } }) { Icon(Icons.Rounded.Info, contentDescription = null) Text("About sync") } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `positionProvider` | `PopupPositionProvider` | | Use TooltipDefaults.rememberTooltipPositionProvider(position). | | `tooltip` | `@Composable TooltipScope.() -> Unit` | | PlainTooltip or RichTooltip. | | `state` | `TooltipState` | | rememberTooltipState(isPersistent = true) keeps a rich tooltip open. | | `hasAction` | `Boolean` | `false` | Set to true when a rich tooltip contains an action. | ## Guidelines Do: - Keep plain tooltips to a few words. - Use rich tooltips for context the user may need. Don't: - Do not hide essential information in a tooltip. - Do not put interactive content in a plain tooltip. ## Accessibility - A tooltip is not a replacement for a content description. Set both. - Tooltips must be reachable without a pointer: they show on keyboard focus and long press. - Rich tooltips with actions are focusable and can be dismissed with Escape. ## Related components - [Icon button](https://m3e.ljzapps.app/docs/components/icon-button.md) - [Snackbar](https://m3e.ljzapps.app/docs/components/snackbar.md) --- # Bottom sheet > A surface anchored to the bottom edge for secondary content and actions. - Category: Containment - API status: Stable - Compose APIs: `ModalBottomSheet`, `BottomSheetScaffold`, `rememberModalBottomSheetState`, `BottomSheetDefaults` - Interactive page: https://m3e.ljzapps.app/#components/bottom-sheet ## When to use A modal bottom sheet blocks the screen behind a scrim and is dismissed by swiping down, tapping the scrim or pressing Escape. A standard bottom sheet lives beside the content and stays visible while the user keeps working. On wide windows prefer a side sheet or a dialog, because a full width bottom sheet reads poorly. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Modal Open it and drag or tap outside to close. ```kotlin var open by rememberSaveable { mutableStateOf(false) } val sheetState = rememberModalBottomSheetState() Button(onClick = { open = true }) { Text("Share options") } if (open) { ModalBottomSheet( onDismissRequest = { open = false }, sheetState = sheetState, dragHandle = { BottomSheetDefaults.DragHandle() }, ) { Column(Modifier.padding(bottom = 32.dp)) { listOf("Copy link", "Send as message", "Save to files").forEach { label -> ListItem(headlineContent = { Text(label) }) } } } } ``` ### Standard Drag the handle up. The content stays interactive. ```kotlin Box(Modifier.fillMaxWidth().height(380.dp)) { BottomSheetScaffold( sheetPeekHeight = 96.dp, sheetContent = { Column(Modifier.fillMaxWidth().padding(bottom = 24.dp)) { Text("Now playing", Modifier.padding(horizontal = 24.dp, vertical = 8.dp)) listOf("Queue", "Lyrics", "Related").forEach { label -> ListItem(headlineContent = { Text(label) }) } } }, ) { Box(Modifier.fillMaxWidth().padding(24.dp)) { Text("Content behind the sheet") } } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `onDismissRequest` | `() -> Unit` | | Called when the sheet should close. | | `sheetState` | `SheetState` | `rememberModalBottomSheetState()` | Controls expansion. skipPartiallyExpanded jumps straight to full height. | | `dragHandle` | `@Composable (() -> Unit)?` | `DragHandle()` | Set to null to hide the handle. | | `sheetMaxWidth` | `Dp` | `BottomSheetDefaults.SheetMaxWidth` | Limits the width on large windows. | ## Guidelines Do: - Use for short lists of actions or supporting detail. - Provide a visible close path. Don't: - Do not place primary content only in a sheet. - Do not stack sheets. ## Accessibility - The drag handle has actions for expand and collapse, so the sheet works without gestures. - A modal sheet traps focus and returns it to the trigger on close. ## Related components - [Dialog](https://m3e.ljzapps.app/docs/components/dialog.md) - [List](https://m3e.ljzapps.app/docs/components/list.md) - [Floating toolbar](https://m3e.ljzapps.app/docs/components/floating-toolbar.md) --- # Card > A container for content and actions about a single subject. - Category: Containment - API status: Stable - Compose APIs: `Card`, `ElevatedCard`, `OutlinedCard`, `CardDefaults` - Interactive page: https://m3e.ljzapps.app/#components/card ## When to use Use filled cards for the lowest emphasis, elevated cards when the card should stand out from a busy background and outlined cards for the highest separation without a shadow. Make the whole card clickable with the onClick overload when it leads to a detail view. Put separate actions in a row at the bottom instead. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Types ```kotlin Card(Modifier.widthIn(max = 200.dp)) { Text("Filled", Modifier.padding(16.dp)) } ElevatedCard(Modifier.widthIn(max = 200.dp)) { Text("Elevated", Modifier.padding(16.dp)) } OutlinedCard(Modifier.widthIn(max = 200.dp)) { Text("Outlined", Modifier.padding(16.dp)) } ``` ### Media and actions A clickable card with a media area and text buttons. ```kotlin ElevatedCard(onClick = {}, modifier = Modifier.widthIn(max = 320.dp)) { Box( modifier = Modifier .fillMaxWidth() .aspectRatio(16f / 9f) .background(MaterialTheme.colorScheme.tertiaryContainer), ) Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { Text("Coastal route", style = MaterialTheme.typography.titleMediumEmphasized) Text("Plan stops, book stays and share the trip.", style = MaterialTheme.typography.bodyMedium) } Row(Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 4.dp), horizontalArrangement = Arrangement.End) { TextButton(onClick = {}) { Text("Share") } TextButton(onClick = {}) { Text("Open") } } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `onClick` | `() -> Unit` | | Optional. Makes the whole card a button. | | `shape` | `Shape` | `CardDefaults.shape` | Corner shape. Use shapes from the theme. | | `colors` | `CardColors` | `CardDefaults.cardColors()` | Container and content colors. | | `elevation` | `CardElevation` | `CardDefaults.cardElevation()` | Shadow per state. | | `content` | `@Composable ColumnScope.() -> Unit` | | Card content, laid out in a column. | ## Guidelines Do: - Keep one subject per card. - Align cards on a grid. Don't: - Do not nest cards. - Do not put long scrolling content inside a card. ## Accessibility - A clickable card is one focus target. Do not nest buttons inside it unless they are separate actions. - Provide a descriptive text for images inside a card, or mark them decorative. ## Related components - [Carousel](https://m3e.ljzapps.app/docs/components/carousel.md) - [List](https://m3e.ljzapps.app/docs/components/list.md) - [Buttons](https://m3e.ljzapps.app/docs/components/button.md) --- # Carousel > Scrolling collections of items whose sizes change as they move through the viewport. - Category: Containment - API status: Expressive - Compose APIs: `HorizontalMultiBrowseCarousel`, `HorizontalUncontainedCarousel`, `HorizontalCenteredHeroCarousel`, `rememberCarouselState` - Interactive page: https://m3e.ljzapps.app/#components/carousel ## When to use Multi-browse shows one large item next to medium and small ones, which suits browsing many items. Uncontained keeps every item at one size and lets the last one peek in. Center hero focuses a single item. Clip each item with maskClip so the item shrinks and grows with the carousel instead of being cut off. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Multi-browse Drag or scroll horizontally. ```kotlin val colors = itemColors() HorizontalMultiBrowseCarousel( state = rememberCarouselState { Titles.size }, preferredItemWidth = 186.dp, itemSpacing = 8.dp, modifier = Modifier.fillMaxWidth(), ) { index -> CarouselItemBox(Titles[index], colors[index], Modifier.maskClip(MaterialTheme.shapes.extraLarge)) } ``` ### Uncontained ```kotlin val colors = itemColors() HorizontalUncontainedCarousel( state = rememberCarouselState { Titles.size }, itemWidth = 200.dp, itemSpacing = 8.dp, modifier = Modifier.fillMaxWidth(), ) { index -> CarouselItemBox(Titles[index], colors[index], Modifier.maskClip(MaterialTheme.shapes.extraLarge)) } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `state` | `CarouselState` | | Create with rememberCarouselState { itemCount }. | | `preferredItemWidth / itemWidth` | `Dp` | | Target width of the large item. | | `itemSpacing` | `Dp` | `0.dp` | Gap between items. | | `contentPadding` | `PaddingValues` | `PaddingValues(0.dp)` | Padding around the carousel content. | | `content` | `@Composable CarouselItemScope.(Int) -> Unit` | | Item content. Use Modifier.maskClip(shape). | ## Guidelines Do: - Use for browsing media or categories. - Show a title on each item. Don't: - Do not use a carousel for content that must all be seen. - Do not autoplay. ## Accessibility - Items that are not fully visible are still part of the accessibility tree. Keep descriptions short. - Provide another way to reach all items, such as a list view, for users who cannot swipe. - Keyboard users scroll with the arrow keys once the carousel has focus. ## Related components - [Card](https://m3e.ljzapps.app/docs/components/card.md) - [List](https://m3e.ljzapps.app/docs/components/list.md) --- # Dialog > A modal window for a decision or a small task that needs the user's attention. - Category: Containment - API status: Stable - Compose APIs: `AlertDialog`, `BasicAlertDialog`, `AlertDialogDefaults` - Interactive page: https://m3e.ljzapps.app/#components/dialog ## When to use Dialogs interrupt the flow, so reserve them for important choices such as confirming a destructive action. Use AlertDialog for the standard icon, title, text and two buttons, and BasicAlertDialog for custom content such as a list of choices. Buttons are text buttons. The confirming action goes on the right and uses a verb that names the result. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Confirmation Standard AlertDialog. ```kotlin var open by rememberSaveable { mutableStateOf(false) } Button(onClick = { open = true }) { Text("Delete draft") } if (open) { AlertDialog( onDismissRequest = { open = false }, icon = { Icon(Icons.Rounded.Delete, contentDescription = null) }, title = { Text("Delete this draft?") }, text = { Text("The draft and its attachments are removed. This cannot be undone.") }, confirmButton = { TextButton(onClick = { open = false }) { Text("Delete") } }, dismissButton = { TextButton(onClick = { open = false }) { Text("Cancel") } }, ) } ``` ### Single choice BasicAlertDialog with custom content. ```kotlin var open by rememberSaveable { mutableStateOf(false) } val options = listOf("Phone ringtone", "Alarm", "Silent") var selected by rememberSaveable { mutableStateOf(options.first()) } Button(onClick = { open = true }) { Text("Notification sound") } if (open) { BasicAlertDialog(onDismissRequest = { open = false }) { Surface(shape = MaterialTheme.shapes.extraLarge, tonalElevation = 6.dp) { Column(Modifier.padding(24.dp)) { Text("Notification sound", style = MaterialTheme.typography.headlineSmall) Column(Modifier.selectableGroup().padding(vertical = 16.dp)) { options.forEach { option -> Row( Modifier .fillMaxWidth() .selectable(option == selected, onClick = { selected = option }, role = Role.RadioButton), verticalAlignment = Alignment.CenterVertically, ) { RadioButton(selected = option == selected, onClick = null) Text(option, Modifier.padding(start = 16.dp, top = 12.dp, bottom = 12.dp)) } } } Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { TextButton(onClick = { open = false }) { Text("Cancel") } TextButton(onClick = { open = false }) { Text("OK") } } } } } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `onDismissRequest` | `() -> Unit` | | Called on scrim click, back or Escape. | | `confirmButton` | `@Composable () -> Unit` | | The affirmative action. | | `dismissButton` | `@Composable (() -> Unit)?` | `null` | The neutral or negative action. | | `icon / title / text` | `@Composable (() -> Unit)?` | `null` | Optional content slots. | | `properties` | `DialogProperties` | `DialogProperties()` | Dismiss on back and outside click, platform behavior. | ## Guidelines Do: - Ask one question per dialog. - Name the confirming action with a verb. Don't: - Do not open a dialog from another dialog. - Do not use a dialog for information that is not actionable. ## Accessibility - Focus moves into the dialog when it opens and returns to the trigger when it closes. - The title is announced first. Without a title, make the text self explanatory. - Escape and the scrim dismiss the dialog. Do not remove both without offering a visible way out. ## Related components - [Snackbar](https://m3e.ljzapps.app/docs/components/snackbar.md) - [Bottom sheet](https://m3e.ljzapps.app/docs/components/bottom-sheet.md) - [Buttons](https://m3e.ljzapps.app/docs/components/button.md) --- # Divider > A thin line that groups content in lists and layouts. - Category: Containment - API status: Stable - Compose APIs: `HorizontalDivider`, `VerticalDivider`, `DividerDefaults` - Interactive page: https://m3e.ljzapps.app/#components/divider ## When to use Use a divider to separate related items inside a surface. Prefer spacing or a change of container color when a divider is not needed. Dividers use the outlineVariant color role. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Between sections ```kotlin Column(Modifier.fillMaxWidth()) { Text("Inbox", style = MaterialTheme.typography.titleMedium) HorizontalDivider(Modifier.padding(vertical = 12.dp)) Text("Starred", style = MaterialTheme.typography.titleMedium) } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `thickness` | `Dp` | `DividerDefaults.Thickness` | Line thickness. | | `color` | `Color` | `DividerDefaults.color` | Defaults to outlineVariant. | ## Guidelines Do: - Use sparingly. - Inset dividers to align with text in lists. Don't: - Do not use dividers between every list item in a card. - Do not use outline as the color. ## Accessibility - Dividers are decorative and are skipped by screen readers. Do not use them to convey meaning. ## Related components - [List](https://m3e.ljzapps.app/docs/components/list.md) - [Card](https://m3e.ljzapps.app/docs/components/card.md) --- # List > Rows of related content with optional icons, supporting text and trailing controls. - Category: Containment - API status: Expressive - Compose APIs: `ListItem`, `SegmentedListItem`, `ListItemDefaults` - Interactive page: https://m3e.ljzapps.app/#components/list ## When to use ListItem supports up to three lines with an overline, headline and supporting text, plus leading and trailing content. Clickable, selectable and checkable overloads share the same layout. Segmented list items are the Expressive style. Each item is a separate rounded container with a small gap, and the first and last items have larger outer corners. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Segmented, selectable ```kotlin val names = listOf("Maya Chen", "Jonas Weber", "Ana Silva", "Kenji Sato") var selected by rememberSaveable { mutableIntStateOf(0) } Column( modifier = Modifier.widthIn(max = 420.dp).fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(ListItemDefaults.SegmentedGap), ) { names.forEachIndexed { index, name -> SegmentedListItem( selected = selected == index, onClick = { selected = index }, shapes = ListItemDefaults.segmentedShapes(index = index, count = names.size), leadingContent = { Icon(Icons.Rounded.Person, contentDescription = null) }, supportingContent = { Text("Last active ${index + 1} h ago") }, ) { Text(name) } } } ``` ### Checkable The whole row toggles. ```kotlin val items = listOf("Wi-Fi", "Bluetooth", "Location") var checked by remember { mutableStateOf(setOf("Wi-Fi")) } Column(Modifier.widthIn(max = 420.dp).fillMaxWidth()) { items.forEach { item -> ListItem( checked = item in checked, onCheckedChange = { isChecked -> checked = if (isChecked) checked + item else checked - item }, trailingContent = { Checkbox(checked = item in checked, onCheckedChange = null) }, ) { Text(item) } } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `headlineContent` | `@Composable () -> Unit` | | Main text. In the clickable overloads this is the trailing content lambda. | | `supportingContent` | `@Composable (() -> Unit)?` | `null` | Secondary text. | | `overlineContent` | `@Composable (() -> Unit)?` | `null` | Small text above the headline. | | `leadingContent / trailingContent` | `@Composable (() -> Unit)?` | `null` | Icons, avatars or controls. | | `shapes` | `ListItemShapes` | `ListItemDefaults.shapes()` | Interactive overloads. Use segmentedShapes(index, count). | ## Guidelines Do: - Keep the headline to one line. - Align leading elements. Don't: - Do not mix one, two and three line items in a list. - Do not use more than one trailing control. ## Accessibility - Interactive rows merge their content into one focus target. - Do not nest a separate button in a clickable row, put the action in the trailing content only when it is a different action. ## Related components - [Card](https://m3e.ljzapps.app/docs/components/card.md) - [Divider](https://m3e.ljzapps.app/docs/components/divider.md) - [Menu](https://m3e.ljzapps.app/docs/components/menu.md) --- # Swipe to dismiss > Reveals or triggers an action when an item is swiped sideways. - Category: Containment - API status: Stable - Compose APIs: `SwipeToDismissBox`, `rememberSwipeToDismissBoxState`, `SwipeToDismissBoxValue` - Interactive page: https://m3e.ljzapps.app/#components/swipe-to-dismiss ## When to use Use it for list items with one or two quick actions, such as archive and delete. The background content is revealed during the swipe and tells the user what will happen. Swiping is a shortcut. Offer the same actions in a menu or on selection, because not everyone can swipe. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Swipe to delete ```kotlin var visible by remember { mutableStateOf(true) } val state = rememberSwipeToDismissBoxState() if (visible) { SwipeToDismissBox( state = state, onDismiss = { visible = false }, backgroundContent = { Box( Modifier.fillMaxSize().background(MaterialTheme.colorScheme.errorContainer).padding(horizontal = 24.dp), contentAlignment = Alignment.CenterEnd, ) { Icon(Icons.Rounded.Delete, contentDescription = "Delete") } }, ) { ListItem(headlineContent = { Text("Swipe to delete") }) } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `state` | `SwipeToDismissBoxState` | | rememberSwipeToDismissBoxState(). Exposes the direction and progress. | | `backgroundContent` | `@Composable RowScope.() -> Unit` | | Drawn behind the item during the swipe. | | `onDismiss` | `(SwipeToDismissBoxValue) -> Unit` | `{}` | Called when the item was swiped past the threshold. | | `enableDismissFromStartToEnd / EndToStart` | `Boolean` | `true` | Allowed directions. | ## Guidelines Do: - Offer undo. - Use colors and icons that name the action. Don't: - Do not make destructive actions swipe only. - Do not use it for items with horizontal scrolling content. ## Accessibility - Provide an alternative to the gesture, such as a menu with the same actions. - Announce the result, for example with a snackbar that offers undo. ## Related components - [List](https://m3e.ljzapps.app/docs/components/list.md) - [Snackbar](https://m3e.ljzapps.app/docs/components/snackbar.md) - [Menu](https://m3e.ljzapps.app/docs/components/menu.md) --- # Bottom app bar > Puts the most important actions of a screen within thumb reach. - Category: Navigation - API status: Expressive - Compose APIs: `BottomAppBar`, `FlexibleBottomAppBar`, `BottomAppBarDefaults` - Interactive page: https://m3e.ljzapps.app/#components/bottom-app-bar ## When to use A bottom app bar holds up to four actions and optionally a FAB. The flexible variant lets you arrange any content with an arrangement, which suits custom action sets. For many floating actions that do not need a full width bar, use the floating toolbar instead. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### With FAB ```kotlin BottomAppBar( actions = { IconButton(onClick = {}) { Icon(Icons.Rounded.Check, contentDescription = "Mark done") } IconButton(onClick = {}) { Icon(Icons.Rounded.Edit, contentDescription = "Edit") } }, floatingActionButton = { FloatingActionButton(onClick = {}) { Icon(Icons.Rounded.Add, contentDescription = "Add") } }, ) ``` ### Flexible Free arrangement of actions. ```kotlin FlexibleBottomAppBar { IconButton(onClick = {}) { Icon(Icons.Rounded.Check, contentDescription = "Mark done") } IconButton(onClick = {}) { Icon(Icons.Rounded.Edit, contentDescription = "Edit") } IconButton(onClick = {}) { Icon(Icons.Rounded.Mic, contentDescription = "Record") } IconButton(onClick = {}) { Icon(Icons.Rounded.Delete, contentDescription = "Delete") } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `actions` | `@Composable RowScope.() -> Unit` | | Icon buttons shown at the start. | | `floatingActionButton` | `@Composable (() -> Unit)?` | `null` | Optional FAB at the end. | | `horizontalArrangement` | `Arrangement.Horizontal` | `FlexibleHorizontalArrangement` | Flexible bar only. | ## Guidelines Do: - Show at most four actions. - Use it for screen level actions. Don't: - Do not combine with a navigation bar. - Do not use it for navigation between destinations. ## Accessibility - Every icon needs a content description. - The bar can hide on scroll. Keep the actions reachable another way for keyboard users. ## Related components - [Floating toolbar](https://m3e.ljzapps.app/docs/components/floating-toolbar.md) - [Top app bar](https://m3e.ljzapps.app/docs/components/top-app-bar.md) - [Floating action button](https://m3e.ljzapps.app/docs/components/fab.md) --- # Docked and full-screen search > Two ways to show search results: in a dropdown under the field or on a full-screen surface. - Category: Navigation - API status: Expressive - Compose APIs: `DockedSearchBar`, `SearchBar`, `ExpandedFullScreenSearchBar`, `SearchBarDefaults.InputField` - Interactive page: https://m3e.ljzapps.app/#components/full-screen-search ## When to use Use the docked search bar on large windows, where a dropdown under the field is easy to reach. Use the full-screen expanded search on compact windows, where it gives the results the whole screen. Both share the same input field. Choose by window size class. ## Examples ### Docked Results appear below the field. ```kotlin var query by rememberSaveable { mutableStateOf("") } var expanded by rememberSaveable { mutableStateOf(false) } val cities = listOf("Berlin", "Bergen", "Barcelona", "Boston", "Brussels") Box(Modifier.fillMaxWidth().height(320.dp), contentAlignment = Alignment.TopCenter) { DockedSearchBar( inputField = { SearchBarDefaults.InputField( query = query, onQueryChange = { query = it }, onSearch = { expanded = false }, expanded = expanded, onExpandedChange = { expanded = it }, placeholder = { Text("Search cities") }, leadingIcon = { Icon(Icons.Rounded.Search, contentDescription = null) }, ) }, expanded = expanded, onExpandedChange = { expanded = it }, ) { cities.filter { it.contains(query, ignoreCase = true) }.forEach { city -> ListItem(headlineContent = { Text(city) }) } } } ``` ### Full screen The expanded bar covers the window. ```kotlin val searchBarState = rememberSearchBarState() val textFieldState = rememberTextFieldState() val scope = rememberCoroutineScope() val inputField = @Composable { SearchBarDefaults.InputField( textFieldState = textFieldState, searchBarState = searchBarState, onSearch = { scope.launch { searchBarState.animateToCollapsed() } }, placeholder = { Text("Search the library") }, leadingIcon = { Icon(Icons.Rounded.Search, contentDescription = null) }, ) } Box(Modifier.fillMaxWidth().height(120.dp), contentAlignment = Alignment.TopCenter) { SearchBar(state = searchBarState, inputField = inputField) ExpandedFullScreenSearchBar(state = searchBarState, inputField = inputField) { listOf("Albums", "Artists", "Playlists").forEach { label -> ListItem(headlineContent = { Text(label) }) } } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `state` | `SearchBarState` | | Full screen variant. Animates between Collapsed and Expanded. | | `expanded / onExpandedChange` | `Boolean / (Boolean) -> Unit` | | Docked variant state. | | `inputField` | `@Composable () -> Unit` | | SearchBarDefaults.InputField. | | `content` | `@Composable ColumnScope.() -> Unit` | | Suggestions or results. | ## Guidelines Do: - Show recent searches. - Keep the field visible while results are shown. Don't: - Do not use the docked variant on compact windows. - Do not search on every keystroke without a delay. ## Accessibility - Escape collapses the expanded bar. Announce the number of results. ## Related components - [Search](https://m3e.ljzapps.app/docs/components/search.md) - [Top app bar](https://m3e.ljzapps.app/docs/components/top-app-bar.md) - [Text field](https://m3e.ljzapps.app/docs/components/text-field.md) --- # Floating toolbar > A toolbar that floats above content with actions for the current context. - Category: Navigation - API status: Expressive - Compose APIs: `HorizontalFloatingToolbar`, `VerticalFloatingToolbar`, `FloatingToolbarDefaults` - Interactive page: https://m3e.ljzapps.app/#components/floating-toolbar ## When to use Floating toolbars hold actions that relate to what is on screen, such as text formatting. They can sit horizontally or vertically, with or without a FAB, and collapse when the user scrolls. Standard colors blend into the surface. Vibrant colors draw attention to the toolbar. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### With FAB ```kotlin Box(Modifier.fillMaxWidth().height(160.dp), contentAlignment = Alignment.Center) { HorizontalFloatingToolbar( expanded = true, floatingActionButton = { FloatingToolbarDefaults.StandardFloatingActionButton(onClick = {}) { Icon(Icons.Rounded.Edit, contentDescription = "Edit") } }, ) { IconButton(onClick = {}) { Icon(Icons.Rounded.FormatBold, contentDescription = "Bold") } IconButton(onClick = {}) { Icon(Icons.Rounded.FormatItalic, contentDescription = "Italic") } IconButton(onClick = {}) { Icon(Icons.Rounded.Straighten, contentDescription = "Ruler") } } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `expanded` | `Boolean` | | Whether all actions are visible. When false only the FAB remains. | | `colors` | `FloatingToolbarColors` | `standardFloatingToolbarColors()` | Standard or vibrant. | | `scrollBehavior` | `FloatingToolbarScrollBehavior?` | `null` | Collapse on scroll. | | `floatingActionButton` | `@Composable () -> Unit` | | Optional FAB, paired with the toolbar. | ## Guidelines Do: - Show actions that apply to the current selection. - Keep it to five actions. Don't: - Do not use it for navigation. - Do not cover important content. ## Accessibility - Each action needs a content description. - Keep the toolbar reachable by keyboard when it is collapsed by scrolling. ## Related components - [Bottom app bar](https://m3e.ljzapps.app/docs/components/bottom-app-bar.md) - [Floating action button](https://m3e.ljzapps.app/docs/components/fab.md) - [Icon button](https://m3e.ljzapps.app/docs/components/icon-button.md) --- # Modal and dismissible drawer > A navigation panel that slides over the content or pushes it aside, and can be closed again. - Category: Navigation - API status: Stable - Compose APIs: `ModalNavigationDrawer`, `DismissibleNavigationDrawer`, `ModalDrawerSheet`, `DismissibleDrawerSheet`, `rememberDrawerState` - Interactive page: https://m3e.ljzapps.app/#components/modal-navigation-drawer ## When to use The modal drawer dims the content behind a scrim and closes on outside click, swipe or Escape. The dismissible drawer shares the screen with the content and is closed by the menu button. Control either one with a DrawerState. open and close are suspend functions, so call them from a coroutine. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Modal drawer ```kotlin val drawerState = rememberDrawerState(DrawerValue.Closed) val scope = rememberCoroutineScope() Box(Modifier.fillMaxWidth().height(300.dp)) { ModalNavigationDrawer( drawerState = drawerState, drawerContent = { ModalDrawerSheet { Text("Mail", Modifier.padding(24.dp), style = MaterialTheme.typography.titleLarge) NavigationDrawerItem( icon = { Icon(Icons.Rounded.Inbox, contentDescription = null) }, label = { Text("Inbox") }, selected = true, onClick = { scope.launch { drawerState.close() } }, modifier = Modifier.padding(horizontal = 12.dp), ) } }, ) { Box(Modifier.fillMaxSize().padding(24.dp)) { Button(onClick = { scope.launch { drawerState.open() } }) { Text("Open drawer") } } } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `drawerState` | `DrawerState` | `rememberDrawerState(Closed)` | Open, close and observe the drawer. | | `gesturesEnabled` | `Boolean` | `true` | Swipe to open and close. | | `drawerContent` | `@Composable () -> Unit` | | The sheet with NavigationDrawerItems. | | `scrimColor` | `Color` | `DrawerDefaults.scrimColor` | Modal drawer only. | ## Guidelines Do: - Close the drawer after navigating. - Highlight the current destination. Don't: - Do not use a drawer with fewer than four destinations. - Do not nest destinations deeper than two levels. ## Accessibility - A modal drawer traps focus and returns it to the menu button on close. - Escape closes the drawer. Keep the menu button labeled. ## Related components - [Navigation drawer](https://m3e.ljzapps.app/docs/components/navigation-drawer.md) - [Navigation rail](https://m3e.ljzapps.app/docs/components/navigation-rail.md) - [Modal navigation rail](https://m3e.ljzapps.app/docs/components/modal-navigation-rail.md) --- # Modal navigation rail > A rail that expands over the content as a modal panel and collapses back to icons. - Category: Navigation - API status: Expressive - Compose APIs: `ModalWideNavigationRail`, `WideNavigationRailItem`, `rememberWideNavigationRailState` - Interactive page: https://m3e.ljzapps.app/#components/modal-navigation-rail ## When to use Use the modal rail on medium windows where a permanent expanded rail would take too much space. Collapsed it behaves like a normal rail. Expanded it overlays the content with a scrim. With hideOnCollapse the rail disappears completely when collapsed, which suits focused tasks. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Expand over content ```kotlin ModalRailDemo() ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `state` | `WideNavigationRailState` | `rememberWideNavigationRailState()` | Collapsed or Expanded. | | `hideOnCollapse` | `Boolean` | `false` | Remove the rail when collapsed. | | `header` | `@Composable (() -> Unit)?` | `null` | Menu button or FAB. | | `railExpanded` | `Boolean` | | Item parameter, must match the state. | ## Guidelines Do: - Collapse after a destination is chosen. - Keep icon and label consistent. Don't: - Do not use it with a bottom bar. - Do not hide it on medium windows without a menu button. ## Accessibility - The expanded rail is modal. Focus moves in and returns to the toggle on collapse. ## Related components - [Navigation rail](https://m3e.ljzapps.app/docs/components/navigation-rail.md) - [Modal and dismissible drawer](https://m3e.ljzapps.app/docs/components/modal-navigation-drawer.md) - [Navigation bar](https://m3e.ljzapps.app/docs/components/navigation-bar.md) --- # Navigation bar > Switches between three to five top level destinations on compact windows. - Category: Navigation - API status: Expressive - Compose APIs: `ShortNavigationBar`, `ShortNavigationBarItem`, `NavigationBar`, `NavigationBarItem` - Interactive page: https://m3e.ljzapps.app/#components/navigation-bar ## When to use The short navigation bar is the Expressive navigation bar. It is shorter than the baseline bar, supports icons beside labels and a centered arrangement for wider windows. The baseline NavigationBar is still available. Use a bar on compact windows only. From the medium size class on, switch to a navigation rail. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Short navigation bar Expressive. Select a destination. ```kotlin var selected by rememberSaveable { mutableIntStateOf(0) } val destinations = listOf( Icons.Rounded.Home to "Home", Icons.Rounded.Search to "Search", Icons.Rounded.LibraryMusic to "Library", ) ShortNavigationBar { destinations.forEachIndexed { index, (icon, label) -> ShortNavigationBarItem( selected = selected == index, onClick = { selected = index }, icon = { Icon(icon, contentDescription = null) }, label = { Text(label) }, ) } } ``` ### Baseline navigation bar ```kotlin var selected by rememberSaveable { mutableIntStateOf(0) } val destinations = listOf( Icons.Rounded.Home to "Home", Icons.Rounded.Search to "Search", Icons.Rounded.LibraryMusic to "Library", ) NavigationBar { destinations.forEachIndexed { index, (icon, label) -> NavigationBarItem( selected = selected == index, onClick = { selected = index }, icon = { Icon(icon, contentDescription = null) }, label = { Text(label) }, ) } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `arrangement` | `ShortNavigationBarArrangement` | `EqualWeight` | EqualWeight fills the width, Centered groups items in the middle. | | `selected` | `Boolean` | | Item parameter. Marks the active destination. | | `icon / label` | `@Composable` | | Item parameters. The label may be null for icon only items. | | `iconPosition` | `NavigationItemIconPosition` | `Top` | Top stacks icon above label, Start puts them side by side. | ## Guidelines Do: - Use three to five destinations. - Use a badge to signal new content. Don't: - Do not use for actions. Use buttons. - Do not hide or add destinations at runtime. ## Accessibility - Selected items expose the selected state. Do not rely on the indicator color alone. - Always show labels. Icon only bars are hard to understand. - Keep the order of destinations stable. ## Related components - [Navigation rail](https://m3e.ljzapps.app/docs/components/navigation-rail.md) - [Navigation drawer](https://m3e.ljzapps.app/docs/components/navigation-drawer.md) - [Badge](https://m3e.ljzapps.app/docs/components/badge.md) --- # Navigation drawer > A side panel with many destinations. Modal, dismissible or permanent. - Category: Navigation - API status: Stable - Compose APIs: `ModalNavigationDrawer`, `DismissibleNavigationDrawer`, `PermanentNavigationDrawer`, `NavigationDrawerItem` - Interactive page: https://m3e.ljzapps.app/#components/navigation-drawer ## When to use Use a modal drawer on compact windows, a dismissible drawer when the content should shift aside, and a permanent drawer on large windows where there is room. This documentation uses exactly that pattern. For new designs on medium and expanded windows, the wide navigation rail is the recommended component. The drawers remain supported. ## Examples ### Permanent drawer Always visible next to the content. ```kotlin var selected by rememberSaveable { mutableIntStateOf(0) } val items = listOf( Icons.Rounded.Inbox to "Inbox", Icons.Rounded.Send to "Sent", Icons.Rounded.Star to "Starred", ) Box(Modifier.fillMaxWidth().height(260.dp)) { PermanentNavigationDrawer( drawerContent = { PermanentDrawerSheet(Modifier.padding(end = 0.dp)) { Text("Mail", Modifier.padding(24.dp), style = MaterialTheme.typography.titleLarge) items.forEachIndexed { index, (icon, label) -> NavigationDrawerItem( icon = { Icon(icon, contentDescription = null) }, label = { Text(label) }, selected = selected == index, onClick = { selected = index }, modifier = Modifier.padding(horizontal = 12.dp), ) } } }, ) { Box(Modifier.fillMaxWidth().padding(24.dp), contentAlignment = Alignment.TopStart) { Text(items[selected].second, style = MaterialTheme.typography.headlineMedium) } } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `drawerContent` | `@Composable () -> Unit` | | Usually a ModalDrawerSheet, DismissibleDrawerSheet or PermanentDrawerSheet. | | `drawerState` | `DrawerState` | `rememberDrawerState(Closed)` | Modal and dismissible drawers only. | | `gesturesEnabled` | `Boolean` | `true` | Allows opening and closing by swipe. | ## Guidelines Do: - Group destinations under headers. - Highlight the active destination. Don't: - Do not nest drawers. - Do not put actions that are not destinations into a drawer. ## Accessibility - Move focus into the drawer when it opens and back to the menu button when it closes. - The modal drawer closes on Escape and on the scrim. ## Related components - [Navigation rail](https://m3e.ljzapps.app/docs/components/navigation-rail.md) - [Navigation bar](https://m3e.ljzapps.app/docs/components/navigation-bar.md) - [Top app bar](https://m3e.ljzapps.app/docs/components/top-app-bar.md) --- # Navigation rail > A vertical rail that collapses to icons or expands to full labels. - Category: Navigation - API status: Expressive - Compose APIs: `WideNavigationRail`, `WideNavigationRailItem`, `rememberWideNavigationRailState`, `ModalWideNavigationRail` - Interactive page: https://m3e.ljzapps.app/#components/navigation-rail ## When to use The wide navigation rail replaces the baseline rail and the standard drawer on medium and larger windows. One component covers both: collapsed it shows icons with small labels, expanded it shows icons beside full labels. Toggle it from a menu button in the header. Pass railExpanded to every item so they lay out for the current state. ## Examples ### Collapsible rail Use the menu button to expand and collapse. ```kotlin val state = rememberWideNavigationRailState() val scope = rememberCoroutineScope() var selected by rememberSaveable { mutableIntStateOf(0) } val expanded = state.targetValue == WideNavigationRailValue.Expanded Box(Modifier.fillMaxWidth().height(380.dp)) { WideNavigationRail( state = state, header = { IconButton( modifier = Modifier.padding(start = 24.dp), onClick = { scope.launch { state.toggle() } }, ) { Icon( imageVector = if (expanded) Icons.Rounded.Close else Icons.Rounded.Menu, contentDescription = if (expanded) "Collapse rail" else "Expand rail", ) } }, ) { NavDestinations.take(4).forEachIndexed { index, (icon, label) -> WideNavigationRailItem( selected = selected == index, onClick = { selected = index }, icon = { Icon(icon, contentDescription = null) }, label = { Text(label) }, railExpanded = expanded, ) } } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `state` | `WideNavigationRailState` | `rememberWideNavigationRailState()` | Holds Collapsed or Expanded and animates between them. | | `header` | `@Composable (() -> Unit)?` | `null` | Slot for a menu button or a FAB. | | `railExpanded` | `Boolean` | | Item parameter. Must match the rail state. | | `arrangement` | `Arrangement.Vertical` | `WideNavigationRailDefaults.arrangement` | Vertical placement of the items. | ## Guidelines Do: - Show the rail from the medium size class. - Offer the main FAB in the header. Don't: - Do not combine a rail with a bottom bar. - Do not use more than seven destinations. ## Accessibility - The toggle button needs a state aware description such as Expand rail. - The rail is a navigation landmark. Keep the item order stable between states. ## Related components - [Navigation bar](https://m3e.ljzapps.app/docs/components/navigation-bar.md) - [Navigation drawer](https://m3e.ljzapps.app/docs/components/navigation-drawer.md) --- # Search > A search field that expands into a list of suggestions or results. - Category: Navigation - API status: Expressive - Compose APIs: `AppBarWithSearch`, `ExpandedDockedSearchBar`, `ExpandedFullScreenSearchBar`, `SearchBarDefaults.InputField`, `rememberSearchBarState` - Interactive page: https://m3e.ljzapps.app/#components/search ## When to use The app bar with search combines a search field with navigation icon and actions. When it expands, show suggestions or results either full screen or docked below the field. Both variants share a SearchBarState and a TextFieldState, so the field, the expansion animation and the result list stay in sync. ## Examples ### Docked results Focus the field and type. ```kotlin val searchBarState = rememberSearchBarState() val textFieldState = rememberTextFieldState() val scope = rememberCoroutineScope() val fruits = listOf("Apple", "Apricot", "Banana", "Blueberry", "Cherry", "Mango") val results = fruits.filter { it.contains(textFieldState.text, ignoreCase = true) } val inputField = @Composable { SearchBarDefaults.InputField( textFieldState = textFieldState, searchBarState = searchBarState, onSearch = { scope.launch { searchBarState.animateToCollapsed() } }, placeholder = { Text("Search fruit") }, leadingIcon = { Icon(Icons.Rounded.Search, contentDescription = null) }, trailingIcon = { if (textFieldState.text.isNotEmpty()) { IconButton(onClick = { textFieldState.clearText() }) { Icon(Icons.Rounded.Close, contentDescription = "Clear") } } }, ) } Box(Modifier.fillMaxWidth().height(320.dp), contentAlignment = Alignment.TopCenter) { AppBarWithSearch( state = searchBarState, inputField = inputField, ) ExpandedDockedSearchBar(state = searchBarState, inputField = inputField) { results.forEach { fruit -> ListItem( headlineContent = { Text(fruit) }, modifier = Modifier.fillMaxWidth(), ) } } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `state` | `SearchBarState` | | rememberSearchBarState(). Animates between Collapsed and Expanded. | | `inputField` | `@Composable () -> Unit` | | Usually SearchBarDefaults.InputField. | | `navigationIcon / actions` | `@Composable` | `null` | AppBarWithSearch only. | | `content` | `@Composable ColumnScope.() -> Unit` | | Suggestions or results of the expanded bar. | ## Guidelines Do: - Show recent searches before the user types. - Provide a clear button. Don't: - Do not start searching on every keystroke without debouncing. - Do not hide the field behind an icon on large windows. ## Accessibility - The input field is a text field with a search action on the keyboard. - Announce the number of results when they change. - Escape collapses the expanded search. ## Related components - [Top app bar](https://m3e.ljzapps.app/docs/components/top-app-bar.md) - [Chips](https://m3e.ljzapps.app/docs/components/chips.md) - [List](https://m3e.ljzapps.app/docs/components/list.md) --- # Tabs > Organize related content at one level of hierarchy and switch between views. - Category: Navigation - API status: Stable - Compose APIs: `PrimaryTabRow`, `SecondaryTabRow`, `PrimaryScrollableTabRow`, `SecondaryScrollableTabRow`, `Tab`, `LeadingIconTab` - Interactive page: https://m3e.ljzapps.app/#components/tabs ## When to use Primary tabs sit at the top of a screen and switch its main content areas. Secondary tabs divide content inside a region of a screen. Use the scrollable variants when there are more tabs than fit. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Primary tabs ```kotlin var selected by rememberSaveable { mutableIntStateOf(0) } val titles = listOf("Overview", "Specs", "Reviews") PrimaryTabRow(selectedTabIndex = selected) { titles.forEachIndexed { index, title -> Tab( selected = selected == index, onClick = { selected = index }, text = { Text(title) }, ) } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `selectedTabIndex` | `Int` | | Index of the active tab. | | `indicator` | `@Composable TabIndicatorScope.() -> Unit` | `PrimaryIndicator / SecondaryIndicator` | Custom selection indicator. | | `Tab.text / icon` | `@Composable (() -> Unit)?` | `null` | Provide text, an icon or both. | ## Guidelines Do: - Keep tab names short and distinct. - Preserve the scroll position of each tab. Don't: - Do not nest tab rows of the same type. - Do not use tabs for sequential steps. ## Accessibility - Tabs expose a tab list with a selected state and position, for example Tab 2 of 3. - Left and right arrow keys move focus between tabs. - Use short single word labels. ## Related components - [Navigation bar](https://m3e.ljzapps.app/docs/components/navigation-bar.md) - [Segmented button](https://m3e.ljzapps.app/docs/components/segmented-button.md) - [Top app bar](https://m3e.ljzapps.app/docs/components/top-app-bar.md) --- # Top app bar > Shows the screen title and the most important actions at the top of a screen. - Category: Navigation - API status: Expressive - Compose APIs: `TopAppBar`, `CenterAlignedTopAppBar`, `MediumTopAppBar`, `LargeTopAppBar`, `MediumFlexibleTopAppBar`, `LargeFlexibleTopAppBar`, `TopAppBarDefaults` - Interactive page: https://m3e.ljzapps.app/#components/top-app-bar ## When to use Small and center aligned bars suit simple screens. Medium and large bars show a big title that collapses into a small bar while content scrolls. The flexible variants add an optional subtitle and a title alignment option. Connect the bar to the scrolling content with a scroll behavior and nestedScroll. Pinned keeps the bar visible, enter always shows it again as soon as the user scrolls up and exit until collapsed collapses the large title. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Collapsing large bar Scroll the list. ```kotlin val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() Scaffold( modifier = Modifier.fillMaxWidth().height(320.dp).nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { LargeTopAppBar( title = { Text("Library") }, navigationIcon = { IconButton(onClick = {}) { Icon(Icons.Rounded.ArrowBack, contentDescription = "Back") } }, scrollBehavior = scrollBehavior, ) }, ) { padding -> LazyColumn(contentPadding = padding) { items((1..30).toList()) { index -> ListItem(headlineContent = { Text("Album $index") }) } } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `title` | `@Composable () -> Unit` | | The screen title. | | `subtitle` | `@Composable (() -> Unit)?` | `null` | Flexible variants only. | | `navigationIcon` | `@Composable () -> Unit` | `{}` | Usually a back or menu button. | | `actions` | `@Composable RowScope.() -> Unit` | `{}` | Up to three icon buttons, extra ones go to an overflow menu. | | `scrollBehavior` | `TopAppBarScrollBehavior?` | `null` | Use together with Modifier.nestedScroll. | ## Guidelines Do: - Keep titles short. - Limit visible actions to three. Don't: - Do not hide the back button on detail screens. - Do not combine a large bar with a tab row above it. ## Accessibility - Mark the title as a heading so screen reader users can jump to it. - Every action needs a content description. Overflow actions are menu items with text. ## Related components - [Bottom app bar](https://m3e.ljzapps.app/docs/components/bottom-app-bar.md) - [Navigation drawer](https://m3e.ljzapps.app/docs/components/navigation-drawer.md) - [Search](https://m3e.ljzapps.app/docs/components/search.md) --- # App bar row > A row of actions that moves what does not fit into an overflow menu. - Category: Layout and structure - API status: Expressive - Compose APIs: `AppBarRow`, `AppBarColumn`, `AppBarOverflowIndicator` - Interactive page: https://m3e.ljzapps.app/#components/app-bar-row ## When to use App bar row is the layout behind app bar actions. Declare each action once with a label and an icon. When space runs out the remaining actions become menu items with the same label. Use AppBarColumn for the same behavior in a vertical bar. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Custom overflow indicator ```kotlin AppBarRow( modifier = Modifier.width(240.dp), overflowIndicator = { menuState -> IconButton(onClick = { if (menuState.isShowing) menuState.dismiss() else menuState.show() }) { Icon(Icons.Rounded.MoreHoriz, contentDescription = "More actions") } }, ) { clickableItem(onClick = {}, icon = { Icon(Icons.Rounded.Edit, contentDescription = null) }, label = "Edit") clickableItem(onClick = {}, icon = { Icon(Icons.Rounded.Share, contentDescription = null) }, label = "Share") clickableItem(onClick = {}, icon = { Icon(Icons.Rounded.Delete, contentDescription = null) }, label = "Delete") } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `overflowIndicator` | `@Composable (AppBarMenuState) -> Unit` | `AppBarOverflowIndicator` | Shown when items overflow. | | `maxItemCount` | `Int` | `Int.MAX_VALUE` | Upper limit of visible actions. | | `content` | `AppBarRowScope.() -> Unit` | | clickableItem, toggleableItem and customItem. | ## Guidelines Do: - Order actions by importance. - Keep labels short. Don't: - Do not hide the most important action in the overflow. - Do not repeat actions in both places. ## Accessibility - The label is the menu text and the accessible name of the icon. Always set it. ## Related components - [Top app bar](https://m3e.ljzapps.app/docs/components/top-app-bar.md) - [Button group](https://m3e.ljzapps.app/docs/components/button-group.md) - [Bottom app bar](https://m3e.ljzapps.app/docs/components/bottom-app-bar.md) --- # Drag handle > A grabbable bar between two panes that lets people resize them. - Category: Layout and structure - API status: Expressive - Compose APIs: `VerticalDragHandle`, `VerticalDragHandleDefaults` - Interactive page: https://m3e.ljzapps.app/#components/drag-handle ## When to use The vertical drag handle sits between panes of a multi-pane layout. It changes shape while it is pressed or dragged. You attach the drag behavior yourself, or let the adaptive pane scaffold do it. ## Examples ### Resizable split Drag the handle. ```kotlin var listWidth by remember { mutableFloatStateOf(220f) } Row(Modifier.fillMaxWidth().height(200.dp)) { Box( Modifier.width(listWidth.dp).fillMaxHeight().background(MaterialTheme.colorScheme.surfaceContainerLow), contentAlignment = Alignment.Center, ) { Text("List") } VerticalDragHandle( modifier = Modifier .padding(horizontal = 8.dp) .draggable( orientation = Orientation.Horizontal, state = rememberDraggableState { delta -> listWidth = (listWidth + delta).coerceIn(120f, 420f) }, ), ) Box(Modifier.weight(1f).fillMaxHeight().background(MaterialTheme.colorScheme.surfaceContainerHigh), contentAlignment = Alignment.Center) { Text("Detail") } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `modifier` | `Modifier` | `Modifier` | Add Modifier.draggable to give the handle behavior. | | `sizes` | `DragHandleSizes` | `VerticalDragHandleDefaults.sizes()` | Resting and pressed size. | | `shapes` | `DragHandleShapes` | `VerticalDragHandleDefaults.shapes()` | Resting and pressed shape. | | `colors` | `DragHandleColors` | `VerticalDragHandleDefaults.colors()` | Resting, pressed and dragged colors. | ## Guidelines Do: - Set sensible minimum and maximum pane sizes. - Use only between panes. Don't: - Do not use it as a decoration. - Do not hide content when a pane gets too small. ## Accessibility - Provide a keyboard way to resize the panes, for example arrow keys on the focused handle. - Keep the touch target at least 48 dp wide with padding. ## Related components - [List-detail layout](https://m3e.ljzapps.app/docs/components/list-detail.md) - [Supporting pane layout](https://m3e.ljzapps.app/docs/components/supporting-pane.md) --- # List-detail layout > Shows a list and the selected item side by side, or one after the other on small windows. - Category: Layout and structure - API status: Expressive - Compose APIs: `ListDetailPaneScaffold`, `AnimatedPane`, `rememberListDetailPaneScaffoldNavigator`, `ListDetailPaneScaffoldRole` - Interactive page: https://m3e.ljzapps.app/#components/list-detail ## When to use ListDetailPaneScaffold is the canonical layout for master and detail content such as mail or contacts. The window size decides how many panes are visible. On a compact window the detail pane replaces the list, on larger windows both are shown. The navigator remembers the selected item and the back stack, so back navigation closes the detail pane first. Resize the browser window to watch the layout adapt. ## Examples ### Contacts Resize the window to switch between one and two panes. ```kotlin val navigator = rememberListDetailPaneScaffoldNavigator() val scope = rememberCoroutineScope() val names = listOf("Maya Chen", "Jonas Weber", "Ana Silva", "Kenji Sato") Box(Modifier.fillMaxWidth().height(340.dp)) { ListDetailPaneScaffold( directive = navigator.scaffoldDirective, scaffoldState = navigator.scaffoldState, listPane = { AnimatedPane { Box(Modifier.fillMaxSize()) { LazyColumn { items(names) { name -> ListItem( onClick = { scope.launch { navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, name) } }, supportingContent = { Text("Tap to open") }, ) { Text(name) } } } } } }, detailPane = { AnimatedPane { Box(Modifier.fillMaxSize().padding(24.dp), contentAlignment = Alignment.Center) { val selected = navigator.currentDestination?.contentKey if (selected == null) { Text("Select a contact", style = MaterialTheme.typography.bodyLarge) } else { Text(selected, style = MaterialTheme.typography.headlineMedium) } } } }, ) } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `directive` | `PaneScaffoldDirective` | `navigator.scaffoldDirective` | Derived from the window size class and posture. | | `scaffoldState` | `ThreePaneScaffoldState` | `navigator.scaffoldState` | Which panes are visible. | | `listPane / detailPane` | `@Composable ThreePaneScaffoldPaneScope.() -> Unit` | | Wrap content in AnimatedPane. | | `extraPane` | `@Composable (...)?` | `null` | Optional third pane for large windows. | ## Guidelines Do: - Keep the list scroll position when going back. - Show an empty state in the detail pane. Don't: - Do not duplicate the selected item's title in both panes. - Do not use it for unrelated content. ## Accessibility - When the detail pane replaces the list, move focus to it and return focus to the selected item on back. - The back button of the browser should close the detail pane. Wire the navigator into your navigation. ## Related components - [Supporting pane layout](https://m3e.ljzapps.app/docs/components/supporting-pane.md) - [Navigation rail](https://m3e.ljzapps.app/docs/components/navigation-rail.md) - [Drag handle](https://m3e.ljzapps.app/docs/components/drag-handle.md) --- # Scaffold > The basic screen layout: top bar, bottom bar, FAB, snackbars and content. - Category: Layout and structure - API status: Expressive - Compose APIs: `Scaffold`, `FabPosition`, `ScaffoldDefaults` - Interactive page: https://m3e.ljzapps.app/#components/scaffold ## When to use Scaffold places the app bars, the FAB and the snackbar host around your content and reports the padding the content needs. Always apply that padding, otherwise content hides behind the bars. For screens that adapt to the window size, combine it with the adaptive scaffolds and a navigation rail. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Top bar and FAB ```kotlin Scaffold( modifier = Modifier.fillMaxWidth().height(320.dp), topBar = { CenterAlignedTopAppBar(title = { Text("Library") }) }, floatingActionButton = { FloatingActionButton(onClick = {}) { Icon(Icons.Rounded.Add, contentDescription = "Add") } }, ) { padding -> LazyColumn(contentPadding = padding) { items((1..20).toList()) { ListItem(headlineContent = { Text("Item $it") }) } } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `topBar / bottomBar` | `@Composable () -> Unit` | `{}` | App bar slots. | | `floatingActionButton` | `@Composable () -> Unit` | `{}` | The FAB slot. | | `floatingActionButtonPosition` | `FabPosition` | `FabPosition.End` | Start, End, Center or EndOverlay. | | `snackbarHost` | `@Composable () -> Unit` | `{}` | Where snackbars appear. | | `content` | `@Composable (PaddingValues) -> Unit` | | Receives the inner padding. | ## Guidelines Do: - Apply the content padding. - Hoist snackbar state outside the scaffold. Don't: - Do not nest scaffolds. - Do not put the FAB into the bottom bar slot. ## Accessibility - The landmark order is top bar, content, bottom bar. Keep it so keyboard focus follows the visual order. ## Related components - [Top app bar](https://m3e.ljzapps.app/docs/components/top-app-bar.md) - [Bottom app bar](https://m3e.ljzapps.app/docs/components/bottom-app-bar.md) - [Snackbar](https://m3e.ljzapps.app/docs/components/snackbar.md) - [List-detail layout](https://m3e.ljzapps.app/docs/components/list-detail.md) --- # Supporting pane layout > A primary pane with a secondary pane of related content next to it. - Category: Layout and structure - API status: Expressive - Compose APIs: `SupportingPaneScaffold`, `AnimatedPane`, `rememberSupportingPaneScaffoldNavigator` - Interactive page: https://m3e.ljzapps.app/#components/supporting-pane ## When to use Use SupportingPaneScaffold when a screen has a main task and supporting information, such as a document with comments. On large windows both panes show side by side. On small windows the supporting pane moves below the main content or into a sheet. ## Examples ### Main with related content Resize the window. ```kotlin val navigator = rememberSupportingPaneScaffoldNavigator() Box(Modifier.fillMaxWidth().height(340.dp)) { SupportingPaneScaffold( directive = navigator.scaffoldDirective, scaffoldState = navigator.scaffoldState, mainPane = { AnimatedPane { Box(Modifier.fillMaxSize().padding(24.dp), contentAlignment = Alignment.Center) { Text("Main content", style = MaterialTheme.typography.headlineMedium) } } }, supportingPane = { AnimatedPane { Box(Modifier.fillMaxSize()) { androidx.compose.foundation.layout.Column { listOf("Related article", "Comments", "Sources").forEach { label -> ListItem(headlineContent = { Text(label) }) } } } } }, ) } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `mainPane` | `@Composable ThreePaneScaffoldPaneScope.() -> Unit` | | The primary content. | | `supportingPane` | `@Composable ThreePaneScaffoldPaneScope.() -> Unit` | | Related content. | | `extraPane` | `@Composable (...)?` | `null` | Optional third pane. | ## Guidelines Do: - Keep the supporting pane optional for the task. - Let people resize the panes on large windows. Don't: - Do not move required actions into the supporting pane. - Do not use more than three panes. ## Accessibility - Keep the reading order main first, then supporting, in every layout. ## Related components - [List-detail layout](https://m3e.ljzapps.app/docs/components/list-detail.md) - [Drag handle](https://m3e.ljzapps.app/docs/components/drag-handle.md) - [Bottom sheet](https://m3e.ljzapps.app/docs/components/bottom-sheet.md) --- # Surface > The building block of every container: shape, color, border and elevation in one place. - Category: Layout and structure - API status: Stable - Compose APIs: `Surface`, `MaterialTheme.colorScheme`, `MaterialTheme.shapes` - Interactive page: https://m3e.ljzapps.app/#components/surface ## When to use Surface sets the content color from its background so text and icons stay readable, clips its content to the shape and draws a shadow. Most components are a Surface with a defined role. Choose a color role rather than a custom color, and prefer tonal container roles to shadows for depth. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Color roles ```kotlin Surface(color = MaterialTheme.colorScheme.surfaceContainerLow, shape = MaterialTheme.shapes.large) { Text("Low", Modifier.padding(20.dp)) } Surface(color = MaterialTheme.colorScheme.surfaceContainerHigh, shape = MaterialTheme.shapes.large) { Text("High", Modifier.padding(20.dp)) } Surface(color = MaterialTheme.colorScheme.primaryContainer, shape = MaterialTheme.shapes.large) { Text("Primary container", Modifier.padding(20.dp)) } ``` ### Selectable ```kotlin var selected by rememberSaveable { mutableStateOf(false) } Surface( selected = selected, onClick = { selected = !selected }, shape = MaterialTheme.shapes.large, color = if (selected) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surfaceContainerLow, ) { Text(if (selected) "Selected" else "Tap to select", Modifier.padding(20.dp)) } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `color` | `Color` | `colorScheme.surface` | Background. Content color is derived with contentColorFor. | | `shape` | `Shape` | `RectangleShape` | Clip and background shape. | | `tonalElevation / shadowElevation` | `Dp` | `0.dp` | Tonal tint and shadow. | | `onClick / selected / checked` | `overloads` | | Make the surface interactive. | ## Guidelines Do: - Use container roles for grouping. - Keep shapes from the theme. Don't: - Do not hardcode colors. - Do not stack more than two tonal levels. ## Accessibility - Interactive surfaces expose a role. Set one when you use the click overload for a non-button purpose. ## Related components - [Card](https://m3e.ljzapps.app/docs/components/card.md) - [Scaffold](https://m3e.ljzapps.app/docs/components/scaffold.md) --- # Text > Displays text with a style from the theme's type scale. - Category: Layout and structure - API status: Stable - Compose APIs: `Text`, `MaterialTheme.typography`, `TextStyle` - Interactive page: https://m3e.ljzapps.app/#components/text ## When to use Pick a style from MaterialTheme.typography instead of setting font size and weight by hand. The style carries size, line height, letter spacing and weight together and follows theme changes. Use the emphasized variants to mark selected or important text without changing its size. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Type scale in use ```kotlin Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Text("Headline", style = MaterialTheme.typography.headlineMedium) Text("Emphasized title", style = MaterialTheme.typography.titleLargeEmphasized) Text("Body text uses the body styles for paragraphs.", style = MaterialTheme.typography.bodyLarge) Text("LABEL", style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.primary) } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `text` | `String \| AnnotatedString` | | The content. | | `style` | `TextStyle` | `LocalTextStyle.current` | Take it from MaterialTheme.typography. | | `color` | `Color` | `Color.Unspecified` | Defaults to the content color of the surface. | | `maxLines / overflow` | `Int / TextOverflow` | `Int.MAX_VALUE / Clip` | Limit and truncate long text. | ## Guidelines Do: - Use theme styles. - Limit line length to about 70 characters. Don't: - Do not hardcode sp values. - Do not use color alone to convey meaning. ## Accessibility - Text scales with the user's font size setting. Do not clip it with fixed heights. - Use real heading semantics for section titles so screen readers can navigate by heading. ## Related components - [List](https://m3e.ljzapps.app/docs/components/list.md) - [Card](https://m3e.ljzapps.app/docs/components/card.md) --- # Autocomplete > A text field that suggests matching options while the user types. - Category: Selection and input - API status: Stable - Compose APIs: `ExposedDropdownMenuBox`, `ExposedDropdownMenuAnchorType.PrimaryEditable`, `ExposedDropdownMenuDefaults` - Interactive page: https://m3e.ljzapps.app/#components/autocomplete ## When to use Use an editable exposed dropdown when people choose from a long list that they can filter by typing. For short lists use a read only exposed dropdown instead. Open the menu when the text changes and filter the options. Close it when an option is chosen. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Filter countries Type to narrow the list. ```kotlin var text by remember { mutableStateOf("") } var expanded by remember { mutableStateOf(false) } val options = Countries.filter { it.contains(text, ignoreCase = true) } ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { expanded = it }) { OutlinedTextField( value = text, onValueChange = { text = it expanded = true }, label = { Text("Country") }, singleLine = true, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, modifier = Modifier.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable), ) if (options.isNotEmpty()) { ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { options.forEach { option -> DropdownMenuItem( text = { Text(option) }, onClick = { text = option expanded = false }, ) } } } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `expanded / onExpandedChange` | `Boolean / (Boolean) -> Unit` | | Menu state. | | `menuAnchor type` | `ExposedDropdownMenuAnchorType` | `PrimaryNotEditable` | Use PrimaryEditable for typing. | | `options` | `List` | | Filtered by the text field value. | ## Guidelines Do: - Allow free text if the list is not complete. - Highlight the matching part of the options. Don't: - Do not open the menu when there are no matches. - Do not require a selection unless it is necessary. ## Accessibility - Arrow keys move through the suggestions, Enter selects and Escape closes. - Announce the number of matching options. ## Related components - [Menu](https://m3e.ljzapps.app/docs/components/menu.md) - [Text field](https://m3e.ljzapps.app/docs/components/text-field.md) - [Search](https://m3e.ljzapps.app/docs/components/search.md) --- # Checkbox > Lets people select one or more items from a set, or turn an option on or off. - Category: Selection and input - API status: Stable - Compose APIs: `Checkbox`, `TriStateCheckbox`, `CheckboxDefaults` - Interactive page: https://m3e.ljzapps.app/#components/checkbox ## When to use Use checkboxes for independent choices. A parent checkbox with an indeterminate state can summarise a group of children. Make the label part of the touch target by putting the checkbox and the text in one toggleable row, and pass null as onCheckedChange to the checkbox itself. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Parent with indeterminate state ```kotlin val children = remember { mutableStateOf(listOf(true, false, false)) } val parentState = when { children.value.all { it } -> ToggleableState.On children.value.none { it } -> ToggleableState.Off else -> ToggleableState.Indeterminate } Column { Row(Modifier.clickable { children.value = List(3) { parentState != ToggleableState.On } }, verticalAlignment = Alignment.CenterVertically) { TriStateCheckbox(state = parentState, onClick = null) Text("Select all") } children.value.forEachIndexed { index, value -> Row( Modifier.padding(start = 24.dp).toggleable(value, role = Role.Checkbox) { checked -> children.value = children.value.toMutableList().also { it[index] = checked } }, verticalAlignment = Alignment.CenterVertically, ) { Checkbox(checked = value, onCheckedChange = null) Text("Option ${index + 1}") } } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `checked` | `Boolean` | | Whether the box is checked. | | `onCheckedChange` | `((Boolean) -> Unit)?` | | Pass null when a parent handles the toggle, for example Modifier.toggleable. | | `enabled` | `Boolean` | `true` | Disables interaction. | | `colors` | `CheckboxColors` | `CheckboxDefaults.colors()` | Box, check mark and border colors. | ## Guidelines Do: - Phrase labels so that checked means yes. - Group related checkboxes. Don't: - Do not use checkboxes for mutually exclusive choices. - Do not apply the change later than the user expects. ## Accessibility - Use Role.Checkbox on the toggleable parent so assistive technology announces the control type. - The label must be part of the same node as the box. - The indeterminate state is announced as partially checked. ## Related components - [Switch](https://m3e.ljzapps.app/docs/components/switch.md) - [Radio button](https://m3e.ljzapps.app/docs/components/radio-button.md) - [Chips](https://m3e.ljzapps.app/docs/components/chips.md) --- # Chips > Compact elements for filters, assistance, entered data and suggestions. - Category: Selection and input - API status: Stable - Compose APIs: `AssistChip`, `FilterChip`, `InputChip`, `SuggestionChip`, `ElevatedAssistChip`, `ElevatedFilterChip` - Interactive page: https://m3e.ljzapps.app/#components/chips ## When to use Assist chips start an action related to the current context. Filter chips narrow a collection and keep their selected state. Input chips represent entered data such as a tag and can be removed. Suggestion chips offer replies or next steps. Chips are most useful in rows. Let the row wrap or scroll, and keep labels short. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Filter chips Multiple selection. ```kotlin val options = listOf("Vegan", "Gluten free", "Low sugar") var selected by remember { mutableStateOf(setOf("Vegan")) } options.forEach { option -> val isSelected = option in selected FilterChip( selected = isSelected, onClick = { selected = if (isSelected) selected - option else selected + option }, label = { Text(option) }, leadingIcon = if (isSelected) { { Icon(Icons.Rounded.Check, contentDescription = null, modifier = Modifier.size(FilterChipDefaults.IconSize)) } } else null, ) } ``` ### Assist and suggestion ```kotlin AssistChip( onClick = {}, label = { Text("Add to calendar") }, leadingIcon = { Icon(Icons.Rounded.Event, contentDescription = null, modifier = Modifier.size(AssistChipDefaults.IconSize)) }, ) SuggestionChip(onClick = {}, label = { Text("Try a shorter route") }, icon = { Icon(Icons.Rounded.Lightbulb, contentDescription = null, modifier = Modifier.size(AssistChipDefaults.IconSize)) }) ``` ### Input chips Tap to remove. ```kotlin var tags by remember { mutableStateOf(listOf("Compose", "Material", "Android")) } tags.forEach { tag -> InputChip( selected = false, onClick = { tags = tags - tag }, label = { Text(tag) }, leadingIcon = { Icon(Icons.Rounded.Label, contentDescription = null, modifier = Modifier.size(InputChipIconSize)) }, trailingIcon = { Icon(Icons.Rounded.Close, contentDescription = "Remove $tag", modifier = Modifier.size(InputChipIconSize)) }, ) } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `selected` | `Boolean` | | Filter and input chips. Whether the chip is selected. | | `onClick` | `() -> Unit` | | Called on click. | | `label` | `@Composable () -> Unit` | | Chip text. | | `leadingIcon / trailingIcon` | `@Composable (() -> Unit)?` | `null` | Optional icons. Suggestion chips use icon. | | `elevation` | `ChipElevation?` | `default` | Use the elevated variants for chips over images. | ## Guidelines Do: - Use sentence case and few words. - Group chips of one kind. Don't: - Do not use chips as primary actions. - Do not mix chip types in one row. ## Accessibility - Filter chips expose a selected state. Show a check mark so selection does not depend on color. - Input chips with a remove icon should name the removed item in the content description. ## Related components - [Buttons](https://m3e.ljzapps.app/docs/components/button.md) - [Segmented button](https://m3e.ljzapps.app/docs/components/segmented-button.md) - [Text field](https://m3e.ljzapps.app/docs/components/text-field.md) --- # Date picker > Lets people choose a date or a range of dates, by calendar or by typing. - Category: Selection and input - API status: Stable - Compose APIs: `DatePicker`, `DatePickerDialog`, `DateRangePicker`, `rememberDatePickerState` - Interactive page: https://m3e.ljzapps.app/#components/date-picker ## When to use Use the modal date picker in a DatePickerDialog for a single date, and the date range picker for travel or booking style choices. People can switch between the calendar and text input. Limit selectable dates with SelectableDates, for example to exclude past days. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### In a dialog ```kotlin var open by rememberSaveable { mutableStateOf(false) } val state = rememberDatePickerState() Button(onClick = { open = true }) { Text("Pick a date") } if (open) { DatePickerDialog( onDismissRequest = { open = false }, confirmButton = { TextButton(onClick = { open = false }) { Text("OK") } }, dismissButton = { TextButton(onClick = { open = false }) { Text("Cancel") } }, ) { DatePicker(state = state) } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `state` | `DatePickerState` | | rememberDatePickerState(). Holds selectedDateMillis and the display mode. | | `initialDisplayMode` | `DisplayMode` | `Picker` | Start in calendar or input mode. | | `selectableDates` | `SelectableDates` | `AllDates` | Restricts selectable days and years. | | `yearRange` | `IntRange` | `DatePickerDefaults.YearRange` | Selectable years. | ## Guidelines Do: - Preselect a sensible date. - Explain why dates are disabled. Don't: - Do not use for a date far in the past, such as a birth date. Use text input. - Do not hide the confirm button. ## Accessibility - Days are announced with their full date. Arrow keys move between days. - Offer text input for users who prefer the keyboard. ## Related components - [Time picker](https://m3e.ljzapps.app/docs/components/time-picker.md) - [Text field](https://m3e.ljzapps.app/docs/components/text-field.md) - [Dialog](https://m3e.ljzapps.app/docs/components/dialog.md) --- # Date range picker > Lets people pick a start and an end date on a scrolling calendar. - Category: Selection and input - API status: Stable - Compose APIs: `DateRangePicker`, `rememberDateRangePickerState`, `DatePickerDialog` - Interactive page: https://m3e.ljzapps.app/#components/date-range-picker ## When to use Use it for stays, trips and reports. The first tap sets the start, the second sets the end. Offer the text input mode for people who prefer to type. Give the picker a fixed height or a weight, because the calendar scrolls inside it. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### In a dialog ```kotlin var open by rememberSaveable { mutableStateOf(false) } val state = rememberDateRangePickerState() Button(onClick = { open = true }) { Text("Pick a range") } if (open) { DatePickerDialog( onDismissRequest = { open = false }, confirmButton = { TextButton(onClick = { open = false }) { Text("Save") } }, dismissButton = { TextButton(onClick = { open = false }) { Text("Cancel") } }, ) { DateRangePicker(state = state, modifier = Modifier.height(500.dp)) } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `state` | `DateRangePickerState` | | Exposes selectedStartDateMillis and selectedEndDateMillis. | | `selectableDates` | `SelectableDates` | `AllDates` | Restrict dates. | | `showModeToggle` | `Boolean` | `true` | Switch between calendar and input. | ## Guidelines Do: - Show the number of nights or days. - Block unavailable dates and say why. Don't: - Do not use for a single date. - Do not hide the confirm button below the fold. ## Accessibility - Announce the selected range, for example June 3 to June 7. - Arrow keys move the focus between days. ## Related components - [Date picker](https://m3e.ljzapps.app/docs/components/date-picker.md) - [Time picker](https://m3e.ljzapps.app/docs/components/time-picker.md) - [Dialog](https://m3e.ljzapps.app/docs/components/dialog.md) --- # Menu > A temporary list of choices or actions that opens from a button or a field. - Category: Selection and input - API status: Expressive - Compose APIs: `DropdownMenu`, `DropdownMenuPopup`, `DropdownMenuGroup`, `DropdownMenuItem`, `ExposedDropdownMenuBox`, `MenuDefaults` - Interactive page: https://m3e.ljzapps.app/#components/menu ## When to use Use a dropdown menu for actions related to an element, an exposed dropdown to choose a value in a form, and the grouped expressive menu when actions belong to distinct sections. Menus close when an item is chosen, when the user clicks outside or presses Escape. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Standard ```kotlin var expanded by remember { mutableStateOf(false) } Box(Modifier.fillMaxWidth().height(220.dp), contentAlignment = Alignment.TopStart) { IconButton(onClick = { expanded = true }) { Icon(Icons.Rounded.MoreVert, contentDescription = "More options") } DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { DropdownMenuItem( text = { Text("Edit") }, onClick = { expanded = false }, leadingIcon = { Icon(Icons.Rounded.Edit, contentDescription = null) }, ) DropdownMenuItem( text = { Text("Delete") }, onClick = { expanded = false }, leadingIcon = { Icon(Icons.Rounded.Delete, contentDescription = null) }, ) } } ``` ### Grouped, expressive Items in separate rounded groups. ```kotlin var expanded by remember { mutableStateOf(false) } Box(Modifier.fillMaxWidth().height(300.dp), contentAlignment = Alignment.TopStart) { IconButton(onClick = { expanded = true }) { Icon(Icons.Rounded.MoreVert, contentDescription = "More options") } DropdownMenuPopup(expanded = expanded, onDismissRequest = { expanded = false }) { val groups = listOf( listOf("Edit" to Icons.Rounded.Edit, "Copy" to Icons.Rounded.ContentCopy), listOf("Share" to Icons.Rounded.Share, "Delete" to Icons.Rounded.Delete), ) groups.forEachIndexed { groupIndex, items -> DropdownMenuGroup(shapes = MenuDefaults.groupShape(groupIndex, groups.size)) { items.forEachIndexed { itemIndex, (label, icon) -> DropdownMenuItem( text = { Text(label) }, onClick = { expanded = false }, shape = MenuDefaults.itemShape(itemIndex, items.size).shape, leadingIcon = { Icon(icon, contentDescription = null) }, ) } } } } } ``` ### Select An exposed dropdown inside a text field. ```kotlin val options = listOf("Light", "Dark", "System default") var expanded by remember { mutableStateOf(false) } var selected by rememberSaveable { mutableStateOf(options.last()) } ExposedDropdownMenuBox( expanded = expanded, onExpandedChange = { expanded = it }, modifier = Modifier.widthIn(max = 320.dp), ) { OutlinedTextField( value = selected, onValueChange = {}, readOnly = true, label = { Text("Theme") }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, modifier = Modifier.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable), ) ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { options.forEach { option -> DropdownMenuItem( text = { Text(option) }, onClick = { selected = option expanded = false }, ) } } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `expanded` | `Boolean` | | Whether the menu is showing. | | `onDismissRequest` | `() -> Unit` | | Called on outside click or Escape. | | `offset` | `DpOffset` | `DpOffset(0.dp, 0.dp)` | Moves the popup relative to its anchor. | | `DropdownMenuItem.leadingIcon` | `@Composable (() -> Unit)?` | `null` | Icon before the text. | ## Guidelines Do: - Group related items. - Put destructive items last. Don't: - Do not nest menus more than one level. - Do not use a menu for primary navigation. ## Accessibility - Menu items are focusable and navigable by arrow keys. Focus moves to the first item on open. - Exposed dropdowns announce the expanded state of the field. - Keep labels short, and add a trailing shortcut hint only when one exists. ## Related components - [Split button](https://m3e.ljzapps.app/docs/components/split-button.md) - [Icon button](https://m3e.ljzapps.app/docs/components/icon-button.md) - [Text field](https://m3e.ljzapps.app/docs/components/text-field.md) --- # Password field > A text field that hides what is typed and can reveal it on request. - Category: Selection and input - API status: Stable - Compose APIs: `SecureTextField`, `OutlinedSecureTextField`, `TextObfuscationMode` - Interactive page: https://m3e.ljzapps.app/#components/secure-text-field ## When to use SecureTextField masks the input and blocks copy and cut, which is what a password field needs. By default it briefly reveals the last typed character, which helps on touch keyboards. Offer a visibility toggle so people can check what they typed. Describe the toggle state in its content description. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### With visibility toggle ```kotlin val password = rememberTextFieldState() var visible by remember { mutableStateOf(false) } OutlinedSecureTextField( state = password, label = { Text("Password") }, textObfuscationMode = if (visible) TextObfuscationMode.Visible else TextObfuscationMode.RevealLastTyped, trailingIcon = { IconButton(onClick = { visible = !visible }) { Icon( imageVector = if (visible) Icons.Rounded.VisibilityOff else Icons.Rounded.Visibility, contentDescription = if (visible) "Hide password" else "Show password", ) } }, modifier = Modifier.widthIn(max = 360.dp).fillMaxWidth(), ) ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `state` | `TextFieldState` | | Holds the secret text. | | `textObfuscationMode` | `TextObfuscationMode` | `RevealLastTyped` | Hidden, RevealLastTyped or Visible. | | `trailingIcon` | `@Composable (() -> Unit)?` | `null` | Place the visibility toggle here. | ## Guidelines Do: - Allow paste so password managers work. - Explain the rules before the user types. Don't: - Do not clear the field on a failed attempt. - Do not use it for non secret input. ## Accessibility - Mark the field with the right autofill hint so password managers can fill it. - Do not announce the typed characters when the field is hidden. ## Related components - [Text field](https://m3e.ljzapps.app/docs/components/text-field.md) - [Icon button](https://m3e.ljzapps.app/docs/components/icon-button.md) --- # Pull to refresh > Lets people reload a list by pulling it down, with a loading indicator as feedback. - Category: Selection and input - API status: Expressive - Compose APIs: `PullToRefreshBox`, `PullToRefreshDefaults`, `rememberPullToRefreshState` - Interactive page: https://m3e.ljzapps.app/#components/pull-to-refresh ## When to use Wrap a scrollable list in PullToRefreshBox and set isRefreshing while the reload runs. The default indicator is the Expressive loading indicator. Use the pointer to drag down or the touch gesture on supported devices. Always offer another way to refresh, because not every input device can pull. ## Examples ### Refresh a list Drag the list down with touch input, or scroll to the top and pull. ```kotlin var refreshing by remember { mutableStateOf(false) } var round by remember { mutableStateOf(1) } val scope = rememberCoroutineScope() PullToRefreshBox( isRefreshing = refreshing, onRefresh = { scope.launch { refreshing = true delay(1_500) round += 1 refreshing = false } }, modifier = Modifier.fillMaxWidth().height(320.dp), ) { LazyColumn(Modifier.fillMaxWidth()) { items((1..20).toList()) { index -> ListItem(headlineContent = { Text("Item $index, load $round") }) } } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `isRefreshing` | `Boolean` | | Show the indicator while true. | | `onRefresh` | `() -> Unit` | | Called when the pull passes the threshold. | | `indicator` | `@Composable BoxScope.() -> Unit` | `default indicator` | Replace to customise. | | `state` | `PullToRefreshState` | `rememberPullToRefreshState()` | Exposes the pull distance. | ## Guidelines Do: - Refresh only content that can be stale. - Keep the existing content visible while loading. Don't: - Do not use it on screens that are not scrollable. - Do not trigger destructive actions. ## Accessibility - Provide a visible refresh button or menu item as an alternative to the gesture. - Announce when new content has loaded. ## Related components - [Loading indicator](https://m3e.ljzapps.app/docs/components/loading-indicator.md) - [List](https://m3e.ljzapps.app/docs/components/list.md) - [Progress indicator](https://m3e.ljzapps.app/docs/components/progress-indicator.md) --- # Radio button > Lets people choose exactly one option from a short list. - Category: Selection and input - API status: Stable - Compose APIs: `RadioButton`, `RadioButtonDefaults`, `Modifier.selectableGroup` - Interactive page: https://m3e.ljzapps.app/#components/radio-button ## When to use Use radio buttons when all options should be visible at once and only one can apply. For long lists use a menu, and for two options consider a switch. Wrap the group in Modifier.selectableGroup and make each row selectable with Role.RadioButton. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Group ```kotlin val options = listOf("Small", "Medium", "Large") var selected by rememberSaveable { mutableIntStateOf(1) } Column(Modifier.selectableGroup()) { options.forEachIndexed { index, label -> Row( modifier = Modifier .fillMaxWidth() .selectable( selected = selected == index, onClick = { selected = index }, role = Role.RadioButton, ), verticalAlignment = Alignment.CenterVertically, ) { RadioButton(selected = selected == index, onClick = null) Text(label) } } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `selected` | `Boolean` | | Whether this option is selected. | | `onClick` | `(() -> Unit)?` | | Pass null when the row handles the click. | | `colors` | `RadioButtonColors` | `RadioButtonDefaults.colors()` | Selected and unselected colors. | ## Guidelines Do: - Preselect the safest option. - Put the label to the right of the control. Don't: - Do not allow deselection by tapping the selected option. - Do not use more than seven options. ## Accessibility - Screen readers announce the position in the group, for example 2 of 3. - Arrow keys move the selection inside the group. ## Related components - [Checkbox](https://m3e.ljzapps.app/docs/components/checkbox.md) - [Segmented button](https://m3e.ljzapps.app/docs/components/segmented-button.md) - [Menu](https://m3e.ljzapps.app/docs/components/menu.md) --- # Slider > Picks a value or a range along a track. Continuous or in steps. - Category: Selection and input - API status: Stable - Compose APIs: `Slider`, `RangeSlider`, `VerticalSlider`, `SliderDefaults` - Interactive page: https://m3e.ljzapps.app/#components/slider ## When to use Use a slider for values where the exact number matters less than the direction, such as volume or brightness. Add steps for discrete values and a RangeSlider to choose a minimum and maximum. Show the current value as text next to the slider when precision matters. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Continuous ```kotlin var value by rememberSaveable { mutableFloatStateOf(0.3f) } Column(Modifier.widthIn(max = 360.dp).fillMaxWidth()) { Slider(value = value, onValueChange = { value = it }) Text("Volume ${(value * 100).toInt()}") } ``` ### Range ```kotlin var range by remember { mutableStateOf(20f..70f) } Column(Modifier.widthIn(max = 360.dp).fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) { RangeSlider( value = range, onValueChange = { range = it }, valueRange = 0f..100f, ) Text("Price ${range.start.toInt()} to ${range.endInclusive.toInt()}") } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `value` | `Float` | | Current value. | | `onValueChange` | `(Float) -> Unit` | | Called continuously while dragging. | | `valueRange` | `ClosedFloatingPointRange` | `0f..1f` | Minimum and maximum. | | `steps` | `Int` | `0` | Number of discrete values between the ends. | | `onValueChangeFinished` | `(() -> Unit)?` | `null` | Called once when the drag ends. | ## Guidelines Do: - Use for approximate values. - Show the value. Don't: - Do not place a slider in a horizontal scroller. - Do not use steps with more than about ten values. ## Accessibility - Sliders expose their range and value. Arrow keys change the value, Page Up and Page Down jump. - Provide a visible label and the current value. - Do not rely on a slider for a value users must type exactly. ## Related components - [Progress indicator](https://m3e.ljzapps.app/docs/components/progress-indicator.md) - [Text field](https://m3e.ljzapps.app/docs/components/text-field.md) --- # Switch > Toggles one setting on or off, and applies the change immediately. - Category: Selection and input - API status: Stable - Compose APIs: `Switch`, `SwitchDefaults` - Interactive page: https://m3e.ljzapps.app/#components/switch ## When to use Use a switch for a setting whose effect is immediate, such as turning notifications on. Use a checkbox when the change is applied later, for example after pressing a submit button. A thumb icon makes the state visible without relying on color alone. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Basic ```kotlin var checked by rememberSaveable { mutableStateOf(false) } Row( modifier = Modifier.toggleable(value = checked, onValueChange = { checked = it }, role = Role.Switch), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp), ) { Text("Notifications") Switch(checked = checked, onCheckedChange = null) } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `checked` | `Boolean` | | Whether the switch is on. | | `onCheckedChange` | `((Boolean) -> Unit)?` | | Pass null when a parent toggleable handles the change. | | `thumbContent` | `@Composable (() -> Unit)?` | `null` | Icon inside the thumb. | | `colors` | `SwitchColors` | `SwitchDefaults.colors()` | Track, thumb and border colors. | ## Guidelines Do: - Use for immediate settings. - Label the setting, not the state. Don't: - Do not use a switch to submit a form. - Do not change the label when the state changes. ## Accessibility - Use Role.Switch on the toggleable parent so the control is announced as a switch. - The label should name the setting, not the action. ## Related components - [Checkbox](https://m3e.ljzapps.app/docs/components/checkbox.md) - [Toggle button](https://m3e.ljzapps.app/docs/components/toggle-button.md) - [Radio button](https://m3e.ljzapps.app/docs/components/radio-button.md) --- # Text field > Lets people enter and edit text. Filled or outlined, with labels, icons and helper text. - Category: Selection and input - API status: Stable - Compose APIs: `TextField`, `OutlinedTextField`, `SecureTextField`, `TextFieldState` - Interactive page: https://m3e.ljzapps.app/#components/text-field ## When to use Use filled text fields for the highest emphasis and outlined ones in dense forms. Always provide a label that stays visible, and use supporting text to explain format or errors. TextFieldState based overloads keep the text in a state holder instead of a String, which suits input transformations and secure fields. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Filled and outlined ```kotlin var text by rememberSaveable { mutableStateOf("") } Column(Modifier.widthIn(max = 360.dp).fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(16.dp)) { TextField( value = text, onValueChange = { text = it }, label = { Text("Filled") }, modifier = Modifier.fillMaxWidth(), ) OutlinedTextField( value = text, onValueChange = { text = it }, label = { Text("Outlined") }, modifier = Modifier.fillMaxWidth(), ) } ``` ### Secure Hides the input and offers a reveal toggle through the trailing icon. ```kotlin val password = rememberTextFieldState() SecureTextField( state = password, label = { Text("Password") }, modifier = Modifier.widthIn(max = 360.dp).fillMaxWidth(), ) ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `value / state` | `String \| TextFieldState` | | The text, as a value or a state holder. | | `label` | `@Composable (() -> Unit)?` | `null` | Floats above the text when focused or filled. | | `supportingText` | `@Composable (() -> Unit)?` | `null` | Helper or error message below the field. | | `isError` | `Boolean` | `false` | Switches to error colors. | | `leadingIcon / trailingIcon` | `@Composable (() -> Unit)?` | `null` | Icons inside the field. | | `keyboardOptions` | `KeyboardOptions` | `Default` | Keyboard type and IME action. | ## Guidelines Do: - Show validation after the user leaves the field. - Keep labels short. Don't: - Do not use placeholder text as the label. - Do not clear the field on error. ## Accessibility - The label is the accessible name. Do not use only a placeholder. - Link errors to the field with supporting text, and do not signal errors by color alone. - Autofill hints help users complete forms faster. ## Related components - [Search](https://m3e.ljzapps.app/docs/components/search.md) - [Menu](https://m3e.ljzapps.app/docs/components/menu.md) - [Date picker](https://m3e.ljzapps.app/docs/components/date-picker.md) --- # Time picker > Lets people choose a time on a dial or by typing it. - Category: Selection and input - API status: Expressive - Compose APIs: `TimePicker`, `TimeInput`, `TimePickerDialog`, `rememberTimePickerState` - Interactive page: https://m3e.ljzapps.app/#components/time-picker ## When to use Use the dial picker for quick selection with touch or pointer, and TimeInput when the time is known and typing is faster. TimePickerDialog hosts either one, with a toggle to switch. Honor the user's 12 or 24 hour preference through the is24Hour parameter. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### In a dialog ```kotlin var open by rememberSaveable { mutableStateOf(false) } val state = rememberTimePickerState(initialHour = 9, initialMinute = 30) Button(onClick = { open = true }) { Text("Pick a time") } if (open) { TimePickerDialog( onDismissRequest = { open = false }, title = { Text("Select time") }, confirmButton = { TextButton(onClick = { open = false }) { Text("OK") } }, dismissButton = { TextButton(onClick = { open = false }) { Text("Cancel") } }, ) { TimePicker(state = state) } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `state` | `TimePickerState` | | rememberTimePickerState(initialHour, initialMinute, is24Hour). | | `layoutType` | `TimePickerLayoutType` | `TimePickerDefaults.layoutType()` | Vertical for portrait, Horizontal for landscape. | | `colors` | `TimePickerColors` | `TimePickerDefaults.colors()` | Dial, selector and period selector colors. | ## Guidelines Do: - Show the user's preferred format. - Provide a keyboard alternative. Don't: - Do not use for durations. Use text input. - Do not hide the dialog title. ## Accessibility - The dial is also operable by keyboard. Arrow keys change values. - Offer the text input mode for users who find a dial hard to use. ## Related components - [Date picker](https://m3e.ljzapps.app/docs/components/date-picker.md) - [Text field](https://m3e.ljzapps.app/docs/components/text-field.md) - [Dialog](https://m3e.ljzapps.app/docs/components/dialog.md) --- # Vertical slider > A slider that runs top to bottom, for levels, equalizers and mixers. - Category: Selection and input - API status: Expressive - Compose APIs: `VerticalSlider`, `rememberSliderState`, `SliderState` - Interactive page: https://m3e.ljzapps.app/#components/vertical-slider ## When to use Use a vertical slider when the value is a level or amount that people compare across several controls, such as an equalizer. It is driven by a SliderState instead of a value and a callback. ## Playground The interactive page has a playground with live property controls for this component and generated code. ## Examples ### Equalizer ```kotlin Row(horizontalArrangement = Arrangement.spacedBy(24.dp)) { listOf("Bass", "Mid", "Treble").forEach { label -> androidx.compose.foundation.layout.Column(horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally) { VerticalSlider(state = rememberSliderState(value = 0.5f), modifier = Modifier.height(160.dp)) Text(label) } } } ``` ## Parameters | Name | Type | Default | Description | |---|---|---|---| | `state` | `SliderState` | | rememberSliderState(value, steps, valueRange). | | `reverseDirection` | `Boolean` | `false` | Puts the minimum at the top. | | `modifier` | `Modifier` | `Modifier` | Give the slider a height. | ## Guidelines Do: - Align sliders on a common baseline. - Label each slider. Don't: - Do not use for precise numeric input. - Do not place in a vertical scroller. ## Accessibility - Arrow keys change the value. Label every slider and expose its value as text. ## Related components - [Slider](https://m3e.ljzapps.app/docs/components/slider.md) - [Progress indicator](https://m3e.ljzapps.app/docs/components/progress-indicator.md) ---