Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
62bf075
Add snippets for Styles documentation
riggaroo Mar 3, 2026
6107458
Update design system style snippets to use Foundation Style API
riggaroo Mar 3, 2026
b8ffda2
Supress unused
riggaroo Mar 3, 2026
2677e8a
Apply Spotless
riggaroo Mar 3, 2026
6a6e46a
Add state and animation snippets
riggaroo Mar 3, 2026
27e37d8
Merge branch 'dac-snippets-styles-documentation-2026-03-03' of https:…
riggaroo Mar 3, 2026
7153c1a
Apply Spotless
riggaroo Mar 3, 2026
c1c9b16
Clean up snippets and make new files
riggaroo Mar 3, 2026
8a16556
Merge branch 'dac-snippets-styles-documentation-2026-03-03' of https:…
riggaroo Mar 3, 2026
1e8aca9
Apply Spotless
riggaroo Mar 3, 2026
7528f9e
Fix lint
riggaroo Mar 3, 2026
c6299e7
Changed tags for animating staet
riggaroo Mar 4, 2026
fd29c16
Fix tags to not have trailing space :(
riggaroo Mar 4, 2026
9ac4736
Fix snippets for custom state
riggaroo Mar 4, 2026
b85ee90
Cleanup example animations
riggaroo Mar 5, 2026
f4ef61c
Cleanup example animations
riggaroo Mar 5, 2026
17ea69a
Switch back to minSdk 23 for Compose snippets
riggaroo Mar 5, 2026
cbdf6bb
Fix basic button
riggaroo Mar 5, 2026
f9dc2ec
Cleanup and move CustomStates to new file
riggaroo Mar 5, 2026
b88c5fa
Apply Spotless
riggaroo Mar 5, 2026
c689c05
add info about gradient + shape not working. cleanup
riggaroo Mar 5, 2026
83fee48
Merge branch 'dac-snippets-styles-documentation-2026-03-03' of https:…
riggaroo Mar 5, 2026
28f20e5
Fix up composition local snippet
riggaroo Mar 10, 2026
fcf06d9
Add semantics = Role.Button
riggaroo Mar 10, 2026
133dfaa
Added a do example for GoodButton
riggaroo Mar 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compose/snippets/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ android {

defaultConfig {
applicationId = "com.example.compose.snippets"
minSdk = libs.versions.minSdk.get().toInt()
minSdk = 23
targetSdk = libs.versions.targetSdk.get().toInt()
versionCode = 1
versionName = "1.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import com.example.compose.snippets.ui.theme.SnippetsTheme
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import androidx.compose.ui.platform.LocalLocale

@Preview
@Composable
Expand Down Expand Up @@ -105,7 +106,7 @@ fun DatePickerExamples() {
// [END_EXCLUDE]
if (selectedDate != null) {
val date = Date(selectedDate!!)
val formattedDate = SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()).format(date)
val formattedDate = SimpleDateFormat("MMM dd, yyyy", LocalLocale.current.platformLocale).format(date)
Text("Selected date: $formattedDate")
} else {
Text("No date selected")
Expand All @@ -121,8 +122,8 @@ fun DatePickerExamples() {
if (selectedDateRange.first != null && selectedDateRange.second != null) {
val startDate = Date(selectedDateRange.first!!)
val endDate = Date(selectedDateRange.second!!)
val formattedStartDate = SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()).format(startDate)
val formattedEndDate = SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()).format(endDate)
val formattedStartDate = SimpleDateFormat("MMM dd, yyyy", LocalLocale.current.platformLocale).format(startDate)
val formattedEndDate = SimpleDateFormat("MMM dd, yyyy", LocalLocale.current.platformLocale).format(endDate)
Text("Selected date range: $formattedStartDate - $formattedEndDate")
} else {
Text("No date range selected")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/*
* Copyright 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

@file:OptIn(ExperimentalFoundationStyleApi::class)

package com.example.compose.snippets.designsystems.styles

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.style.ExperimentalFoundationStyleApi
import androidx.compose.foundation.style.MutableStyleState
import androidx.compose.foundation.style.Style
import androidx.compose.foundation.style.StyleScope
import androidx.compose.foundation.style.StyleStateKey
import androidx.compose.foundation.style.styleable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp


// [START android_compose_styles_custom_key_1]
enum class PlayerState {
Stopped,
Playing,
Paused
}

val playerStateKey = StyleStateKey(PlayerState.Stopped)
// [END android_compose_styles_custom_key_1]

// [START android_compose_styles_custom_key_2]
// Extension Function on MutableStyleState to query and set the current playState
var MutableStyleState.playerState
get() = this[playerStateKey]
set(value) { this[playerStateKey] = value }

fun StyleScope.playerPlaying(value: Style) {
state(playerStateKey, value, { key, state -> state[key] == PlayerState.Playing })
}
fun StyleScope.playerPaused(value: Style) {
state(playerStateKey, value, { key, state -> state[key] == PlayerState.Paused })
}
// [END android_compose_styles_custom_key_2]

// [START android_compose_styles_link_to_custom_state]
@Composable
fun MediaPlayer(
url: String,
modifier: Modifier = Modifier,
style: Style = Style,
state: PlayerState = remember { PlayerState.Paused }
) {
// Hoist style state, set playerState as a parameter,
val styleState = remember { MutableStyleState(null) }
// Set equal to incoming state to link the two together
styleState.playerState = state
Box(
//..
) {
///..
}
}
// [END android_compose_styles_link_to_custom_state]

private object Step2StyleState {
// [START android_compose_styles_link_to_custom_state_pass]
@Composable
fun MediaPlayer(
url: String,
modifier: Modifier = Modifier,
style: Style = Style,
state: PlayerState = remember { PlayerState.Paused }
) {
// Hoist style state, set playstate as a parameter,
val styleState = remember { MutableStyleState(null) }
// Set equal to incoming state to link the two together
styleState.playerState = state
Box(
modifier = modifier.styleable(styleState, style)) {
///..
}
}
// [END android_compose_styles_link_to_custom_state_pass]
}
private object Step3StyleState {
// [START android_compose_styles_link_to_custom_state_key]
@Composable
fun StyleStateKeySample() {
// Using the extension function to change the border color to green while playing
val style = Style {
borderColor(Color.Gray)
playerPlaying {
animate {
borderColor(Color.Green)
}
}
playerPaused {
animate {
borderColor(Color.Blue)
}
}
}
val styleState = remember { MutableStyleState(null) }
styleState[playerStateKey] = PlayerState.Playing

// Using the style in a composable that sets the state -> notice if you change the state parameter, the style changes. You can link this up to an ViewModel and change the state from there too.
MediaPlayer(url = "https://example.com/media/video",
style = style,
state = PlayerState.Stopped)
}
// [END android_compose_styles_link_to_custom_state_key]
}

private object Step4FullSnippetState {
// [START android_compose_styles_state_full_snippet]
enum class PlayerState {
Stopped,
Playing,
Paused
}
val playerStateKey = StyleStateKey<PlayerState>(PlayerState.Stopped)
var MutableStyleState.playerState
get() = this[playerStateKey]
set(value) { this[playerStateKey] = value }

fun StyleScope.playerPlaying(value: Style) {
state(playerStateKey, value, { key, state -> state[key] == PlayerState.Playing })
}
fun StyleScope.playerPaused(value: Style) {
state(playerStateKey, value, { key, state -> state[key] == PlayerState.Paused })

}

@Composable
fun MediaPlayer(
url: String,
modifier: Modifier = Modifier,
style: Style = Style,
state: PlayerState = remember { PlayerState.Paused }
) {
// Hoist style state, set playstate as a parameter,
val styleState = remember { MutableStyleState(null) }
// Set equal to incoming state to link the two together
styleState.playerState = state
Box(
modifier = modifier.styleable(styleState, Style {
size(100.dp)
border(2.dp, Color.Red)

}, style, )) {

///..
}
}
@Composable
fun StyleStateKeySample() {
// Using the extension function to change the border color to green while playing
val style = Style {
borderColor(Color.Gray)
playerPlaying {
animate {
borderColor(Color.Green)
}
}
playerPaused {
animate {
borderColor(Color.Blue)
}
}
}
val styleState = remember { MutableStyleState(null) }
styleState[playerStateKey] = PlayerState.Playing

// Using the style in a composable that sets the state -> notice if you change the state parameter, the style changes. You can link this up to an ViewModel and change the state from there too.
MediaPlayer(url = "https://example.com/media/video",
style = style,
state = PlayerState.Stopped)
}
// [END android_compose_styles_state_full_snippet]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

@file:OptIn(ExperimentalFoundationStyleApi::class)

package com.example.compose.snippets.designsystems.styles

import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.style.ExperimentalFoundationStyleApi
import androidx.compose.foundation.style.MutableStyleState
import androidx.compose.foundation.style.Style
import androidx.compose.foundation.style.styleable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color


// [START android_compose_styles_dos_expose_style]
@Composable
fun GradientButton(
modifier: Modifier = Modifier,
// ✅ DO: for design system components, expose a style modifier to consumers to be able to customize the components
style: Style = Style
) {
// Consume the style
}
// [END android_compose_styles_dos_expose_style]

// [START android_compose_styles_dos_replace_params]
// Before
@Composable
fun OldButton(background: Color, fontColor: Color) {
}

// After
// ✅ DO: Replace visual-based parameters with a style that includes same properties
@Composable
fun NewButton(style: Style = Style) {
}
// [END android_compose_styles_dos_replace_params]

// [START android_compose_styles_dos_wrapper]
@Composable
fun BaseButton(
modifier: Modifier = Modifier,
style: Style = Style
) {
// Uses LocalTheme.appStyles.button + incoming style
}

// ✅ Do create wrapper composables that expose common implementations of the same component
@Composable
fun SpecialGradientButton(
modifier: Modifier = Modifier,
style: Style = Style
) {
// Uses LocalTheme.appStyles.button + LocalTheme.appStyles.gradientButton + incoming style - merge these styles
}
// [END android_compose_styles_dos_wrapper]

// [START android_compose_styles_donts_default_style]
@Composable
fun BadButton(
modifier: Modifier = Modifier,
// ❌ DON'T set a default style here as a parameter
style: Style = Style { background(Color.Red) }
) {
}
// [END android_compose_styles_donts_default_style]

// [START android_compose_styles_do_default_style]
@Composable
fun GoodButton(
modifier: Modifier = Modifier,
// ✅ Do: always pass it as a Style, do not pass other defaults
style: Style = Style
) {
// [START_EXCLUDE]
// this is a snippet of the BaseButton - see the full snippet in /components/Button.kt
val effectiveInteractionSource = remember {
MutableInteractionSource()
}
val styleState = remember(effectiveInteractionSource) {
MutableStyleState(effectiveInteractionSource)
}
// [END_EXCLUDE]
val defaultStyle = Style { background(Color.Red) }
// ✅ Do Combine defaults inside with incoming parameter
Box(modifier = modifier.styleable(styleState, defaultStyle, style)) {
// your logic
}
}
// [END android_compose_styles_do_default_style]
Loading
Loading