Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Design Android app UI/UX following Material Design 3 guidelines with Jetpack Compose layout patterns.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/compose-components.md
1# Jetpack Compose Component Library23## Lists and Collections45### Basic LazyColumn67```kotlin8@Composable9fun ItemList(10items: List<Item>,11onItemClick: (Item) -> Unit,12modifier: Modifier = Modifier13) {14LazyColumn(15modifier = modifier.fillMaxSize(),16contentPadding = PaddingValues(16.dp),17verticalArrangement = Arrangement.spacedBy(8.dp)18) {19items(20items = items,21key = { it.id }22) { item ->23ItemRow(24item = item,25onClick = { onItemClick(item) }26)27}28}29}30```3132### Pull to Refresh3334```kotlin35@OptIn(ExperimentalMaterial3Api::class)36@Composable37fun RefreshableList(38items: List<Item>,39isRefreshing: Boolean,40onRefresh: () -> Unit41) {42val pullToRefreshState = rememberPullToRefreshState()4344PullToRefreshBox(45state = pullToRefreshState,46isRefreshing = isRefreshing,47onRefresh = onRefresh48) {49LazyColumn(50modifier = Modifier.fillMaxSize()51) {52items(items) { item ->53ItemRow(item = item)54}55}56}57}58```5960### Swipe to Dismiss6162```kotlin63@OptIn(ExperimentalMaterial3Api::class)64@Composable65fun SwipeableItem(66item: Item,67onDelete: () -> Unit68) {69val dismissState = rememberSwipeToDismissBoxState(70confirmValueChange = { value ->71if (value == SwipeToDismissBoxValue.EndToStart) {72onDelete()73true74} else {75false76}77}78)7980SwipeToDismissBox(81state = dismissState,82backgroundContent = {83Box(84modifier = Modifier85.fillMaxSize()86.background(MaterialTheme.colorScheme.errorContainer)87.padding(horizontal = 20.dp),88contentAlignment = Alignment.CenterEnd89) {90Icon(91Icons.Default.Delete,92contentDescription = "Delete",93tint = MaterialTheme.colorScheme.onErrorContainer94)95}96}97) {98ItemRow(item = item)99}100}101```102103### Sticky Headers104105```kotlin106@OptIn(ExperimentalFoundationApi::class)107@Composable108fun GroupedList(109groups: Map<String, List<Item>>110) {111LazyColumn {112groups.forEach { (header, items) ->113stickyHeader {114Surface(115modifier = Modifier.fillMaxWidth(),116color = MaterialTheme.colorScheme.surfaceVariant117) {118Text(119text = header,120modifier = Modifier.padding(16.dp),121style = MaterialTheme.typography.titleSmall,122color = MaterialTheme.colorScheme.onSurfaceVariant123)124}125}126items(items, key = { it.id }) { item ->127ItemRow(item = item)128}129}130}131}132```133134## Forms and Input135136### Text Fields137138```kotlin139@Composable140fun LoginForm(141onLogin: (email: String, password: String) -> Unit142) {143var email by rememberSaveable { mutableStateOf("") }144var password by rememberSaveable { mutableStateOf("") }145var passwordVisible by rememberSaveable { mutableStateOf(false) }146var emailError by remember { mutableStateOf<String?>(null) }147148Column(149modifier = Modifier.padding(16.dp),150verticalArrangement = Arrangement.spacedBy(16.dp)151) {152OutlinedTextField(153value = email,154onValueChange = {155email = it156emailError = if (it.isValidEmail()) null else "Invalid email"157},158label = { Text("Email") },159placeholder = { Text("[email protected]") },160leadingIcon = { Icon(Icons.Default.Email, null) },161isError = emailError != null,162supportingText = emailError?.let { { Text(it) } },163keyboardOptions = KeyboardOptions(164keyboardType = KeyboardType.Email,165imeAction = ImeAction.Next166),167singleLine = true,168modifier = Modifier.fillMaxWidth()169)170171OutlinedTextField(172value = password,173onValueChange = { password = it },174label = { Text("Password") },175leadingIcon = { Icon(Icons.Default.Lock, null) },176trailingIcon = {177IconButton(onClick = { passwordVisible = !passwordVisible }) {178Icon(179if (passwordVisible) Icons.Default.VisibilityOff180else Icons.Default.Visibility,181contentDescription = "Toggle password visibility"182)183}184},185visualTransformation = if (passwordVisible)186VisualTransformation.None187else188PasswordVisualTransformation(),189keyboardOptions = KeyboardOptions(190keyboardType = KeyboardType.Password,191imeAction = ImeAction.Done192),193singleLine = true,194modifier = Modifier.fillMaxWidth()195)196197Button(198onClick = { onLogin(email, password) },199modifier = Modifier.fillMaxWidth(),200enabled = email.isNotEmpty() && password.isNotEmpty() && emailError == null201) {202Text("Sign In")203}204}205}206```207208### Search Bar209210```kotlin211@OptIn(ExperimentalMaterial3Api::class)212@Composable213fun SearchableScreen(214items: List<Item>,215onItemClick: (Item) -> Unit216) {217var query by rememberSaveable { mutableStateOf("") }218var expanded by rememberSaveable { mutableStateOf(false) }219220val filteredItems = remember(query, items) {221if (query.isEmpty()) items222else items.filter { it.name.contains(query, ignoreCase = true) }223}224225SearchBar(226query = query,227onQueryChange = { query = it },228onSearch = { expanded = false },229active = expanded,230onActiveChange = { expanded = it },231placeholder = { Text("Search items") },232leadingIcon = { Icon(Icons.Default.Search, null) },233trailingIcon = {234if (query.isNotEmpty()) {235IconButton(onClick = { query = "" }) {236Icon(Icons.Default.Clear, "Clear search")237}238}239},240modifier = Modifier241.fillMaxWidth()242.padding(horizontal = if (expanded) 0.dp else 16.dp)243) {244LazyColumn(245modifier = Modifier.fillMaxWidth(),246contentPadding = PaddingValues(16.dp)247) {248items(filteredItems) { item ->249ListItem(250headlineContent = { Text(item.name) },251supportingContent = { Text(item.description) },252modifier = Modifier.clickable {253onItemClick(item)254expanded = false255}256)257}258}259}260}261```262263### Selection Controls264265```kotlin266@Composable267fun SettingsScreen() {268var notificationsEnabled by rememberSaveable { mutableStateOf(true) }269var selectedOption by rememberSaveable { mutableStateOf(0) }270var expandedDropdown by remember { mutableStateOf(false) }271var selectedLanguage by rememberSaveable { mutableStateOf("English") }272val languages = listOf("English", "Spanish", "French", "German")273274Column {275// Switch276ListItem(277headlineContent = { Text("Enable Notifications") },278supportingContent = { Text("Receive push notifications") },279trailingContent = {280Switch(281checked = notificationsEnabled,282onCheckedChange = { notificationsEnabled = it }283)284}285)286287HorizontalDivider()288289// Radio buttons290Column {291Text(292"Theme",293modifier = Modifier.padding(16.dp),294style = MaterialTheme.typography.titleSmall295)296listOf("System", "Light", "Dark").forEachIndexed { index, option ->297Row(298modifier = Modifier299.fillMaxWidth()300.selectable(301selected = selectedOption == index,302onClick = { selectedOption = index },303role = Role.RadioButton304)305.padding(horizontal = 16.dp, vertical = 12.dp),306verticalAlignment = Alignment.CenterVertically307) {308RadioButton(309selected = selectedOption == index,310onClick = null311)312Spacer(Modifier.width(16.dp))313Text(option)314}315}316}317318HorizontalDivider()319320// Dropdown321ExposedDropdownMenuBox(322expanded = expandedDropdown,323onExpandedChange = { expandedDropdown = it },324modifier = Modifier.padding(16.dp)325) {326OutlinedTextField(327value = selectedLanguage,328onValueChange = {},329readOnly = true,330label = { Text("Language") },331trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expandedDropdown) },332modifier = Modifier333.fillMaxWidth()334.menuAnchor()335)336ExposedDropdownMenu(337expanded = expandedDropdown,338onDismissRequest = { expandedDropdown = false }339) {340languages.forEach { language ->341DropdownMenuItem(342text = { Text(language) },343onClick = {344selectedLanguage = language345expandedDropdown = false346},347contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding348)349}350}351}352}353}354```355356## Dialogs and Bottom Sheets357358### Alert Dialog359360```kotlin361@Composable362fun DeleteConfirmationDialog(363itemName: String,364onConfirm: () -> Unit,365onDismiss: () -> Unit366) {367AlertDialog(368onDismissRequest = onDismiss,369icon = {370Icon(371Icons.Default.Warning,372contentDescription = null,373tint = MaterialTheme.colorScheme.error374)375},376title = {377Text("Delete Item?")378},379text = {380Text("Are you sure you want to delete \"$itemName\"? This action cannot be undone.")381},382confirmButton = {383TextButton(384onClick = onConfirm,385colors = ButtonDefaults.textButtonColors(386contentColor = MaterialTheme.colorScheme.error387)388) {389Text("Delete")390}391},392dismissButton = {393TextButton(onClick = onDismiss) {394Text("Cancel")395}396}397)398}399```400401### Modal Bottom Sheet402403```kotlin404@OptIn(ExperimentalMaterial3Api::class)405@Composable406fun OptionsBottomSheet(407onDismiss: () -> Unit,408onOptionSelected: (String) -> Unit409) {410val sheetState = rememberModalBottomSheetState()411412ModalBottomSheet(413onDismissRequest = onDismiss,414sheetState = sheetState,415dragHandle = { BottomSheetDefaults.DragHandle() }416) {417Column(418modifier = Modifier419.fillMaxWidth()420.padding(bottom = 32.dp)421) {422Text(423"Options",424modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),425style = MaterialTheme.typography.titleMedium426)427428listOf(429Triple(Icons.Default.Share, "Share", "share"),430Triple(Icons.Default.Edit, "Edit", "edit"),431Triple(Icons.Default.FileCopy, "Duplicate", "duplicate"),432Triple(Icons.Default.Delete, "Delete", "delete")433).forEach { (icon, label, action) ->434ListItem(435headlineContent = { Text(label) },436leadingContent = {437Icon(438icon,439contentDescription = null,440tint = if (action == "delete")441MaterialTheme.colorScheme.error442else443MaterialTheme.colorScheme.onSurfaceVariant444)445},446modifier = Modifier.clickable { onOptionSelected(action) }447)448}449}450}451}452```453454### Date and Time Pickers455456```kotlin457@OptIn(ExperimentalMaterial3Api::class)458@Composable459fun DateTimePickerExample() {460var showDatePicker by remember { mutableStateOf(false) }461var showTimePicker by remember { mutableStateOf(false) }462val datePickerState = rememberDatePickerState()463val timePickerState = rememberTimePickerState()464465Column(modifier = Modifier.padding(16.dp)) {466OutlinedButton(onClick = { showDatePicker = true }) {467Icon(Icons.Default.CalendarToday, null)468Spacer(Modifier.width(8.dp))469Text(470datePickerState.selectedDateMillis?.let {471SimpleDateFormat("MMM dd, yyyy", Locale.getDefault())472.format(Date(it))473} ?: "Select Date"474)475}476477Spacer(Modifier.height(16.dp))478479OutlinedButton(onClick = { showTimePicker = true }) {480Icon(Icons.Default.Schedule, null)481Spacer(Modifier.width(8.dp))482Text(483String.format("%02d:%02d", timePickerState.hour, timePickerState.minute)484)485}486}487488if (showDatePicker) {489DatePickerDialog(490onDismissRequest = { showDatePicker = false },491confirmButton = {492TextButton(onClick = { showDatePicker = false }) {493Text("OK")494}495},496dismissButton = {497TextButton(onClick = { showDatePicker = false }) {498Text("Cancel")499}500}501) {502DatePicker(state = datePickerState)503}504}505506if (showTimePicker) {507AlertDialog(508onDismissRequest = { showTimePicker = false },509confirmButton = {510TextButton(onClick = { showTimePicker = false }) {511Text("OK")512}513},514dismissButton = {515TextButton(onClick = { showTimePicker = false }) {516Text("Cancel")517}518},519text = {520TimePicker(state = timePickerState)521}522)523}524}525```526527## Loading States528529### Progress Indicators530531```kotlin532@Composable533fun LoadingStates() {534Column(535modifier = Modifier.padding(16.dp),536verticalArrangement = Arrangement.spacedBy(24.dp)537) {538// Indeterminate circular539CircularProgressIndicator()540541// Determinate circular542CircularProgressIndicator(543progress = { 0.7f },544strokeWidth = 4.dp545)546547// Indeterminate linear548LinearProgressIndicator(modifier = Modifier.fillMaxWidth())549550// Determinate linear551LinearProgressIndicator(552progress = { 0.7f },553modifier = Modifier.fillMaxWidth()554)555}556}557```558559### Skeleton Loading560561```kotlin562@Composable563fun SkeletonLoader(564modifier: Modifier = Modifier565) {566val infiniteTransition = rememberInfiniteTransition(label = "skeleton")567val alpha by infiniteTransition.animateFloat(568initialValue = 0.3f,569targetValue = 0.7f,570animationSpec = infiniteRepeatable(571animation = tween(1000, easing = LinearEasing),572repeatMode = RepeatMode.Reverse573),574label = "alpha"575)576577Column(578modifier = modifier.padding(16.dp),579verticalArrangement = Arrangement.spacedBy(12.dp)580) {581repeat(5) {582Row(583verticalAlignment = Alignment.CenterVertically,584horizontalArrangement = Arrangement.spacedBy(12.dp)585) {586Box(587modifier = Modifier588.size(48.dp)589.clip(CircleShape)590.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = alpha))591)592Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {593Box(594modifier = Modifier595.height(16.dp)596.fillMaxWidth(0.7f)597.clip(RoundedCornerShape(4.dp))598.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = alpha))599)600Box(601modifier = Modifier602.height(12.dp)603.fillMaxWidth(0.5f)604.clip(RoundedCornerShape(4.dp))605.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = alpha))606)607}608}609}610}611}612```613614### Content Loading Pattern615616```kotlin617@Composable618fun <T> AsyncContent(619state: AsyncState<T>,620onRetry: () -> Unit,621content: @Composable (T) -> Unit622) {623when (state) {624is AsyncState.Loading -> {625Box(626modifier = Modifier.fillMaxSize(),627contentAlignment = Alignment.Center628) {629CircularProgressIndicator()630}631}632is AsyncState.Success -> {633content(state.data)634}635is AsyncState.Error -> {636Column(637modifier = Modifier638.fillMaxSize()639.padding(32.dp),640horizontalAlignment = Alignment.CenterHorizontally,641verticalArrangement = Arrangement.Center642) {643Icon(644Icons.Default.Error,645contentDescription = null,646modifier = Modifier.size(64.dp),647tint = MaterialTheme.colorScheme.error648)649Spacer(Modifier.height(16.dp))650Text(651"Something went wrong",652style = MaterialTheme.typography.titleMedium653)654Spacer(Modifier.height(8.dp))655Text(656state.message,657style = MaterialTheme.typography.bodyMedium,658color = MaterialTheme.colorScheme.onSurfaceVariant,659textAlign = TextAlign.Center660)661Spacer(Modifier.height(24.dp))662Button(onClick = onRetry) {663Text("Try Again")664}665}666}667}668}669670sealed class AsyncState<out T> {671object Loading : AsyncState<Nothing>()672data class Success<T>(val data: T) : AsyncState<T>()673data class Error(val message: String) : AsyncState<Nothing>()674}675```676677## Animations678679### Animated Visibility680681```kotlin682@Composable683fun ExpandableCard(684title: String,685content: String686) {687var expanded by rememberSaveable { mutableStateOf(false) }688689Card(690modifier = Modifier.fillMaxWidth()691) {692Column(693modifier = Modifier694.clickable { expanded = !expanded }695.padding(16.dp)696) {697Row(698modifier = Modifier.fillMaxWidth(),699horizontalArrangement = Arrangement.SpaceBetween,700verticalAlignment = Alignment.CenterVertically701) {702Text(title, style = MaterialTheme.typography.titleMedium)703Icon(704if (expanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore,705contentDescription = if (expanded) "Collapse" else "Expand"706)707}708709AnimatedVisibility(710visible = expanded,711enter = expandVertically() + fadeIn(),712exit = shrinkVertically() + fadeOut()713) {714Text(715text = content,716modifier = Modifier.padding(top = 12.dp),717style = MaterialTheme.typography.bodyMedium,718color = MaterialTheme.colorScheme.onSurfaceVariant719)720}721}722}723}724```725726### Animated Content727728```kotlin729@Composable730fun AnimatedCounter(count: Int) {731AnimatedContent(732targetState = count,733transitionSpec = {734if (targetState > initialState) {735slideInVertically { -it } + fadeIn() togetherWith736slideOutVertically { it } + fadeOut()737} else {738slideInVertically { it } + fadeIn() togetherWith739slideOutVertically { -it } + fadeOut()740}.using(SizeTransform(clip = false))741},742label = "counter"743) { targetCount ->744Text(745text = "$targetCount",746style = MaterialTheme.typography.displayMedium747)748}749}750```751752### Gesture-Based Animation753754```kotlin755@Composable756fun SwipeableCard(757onSwipeLeft: () -> Unit,758onSwipeRight: () -> Unit,759content: @Composable () -> Unit760) {761var offsetX by remember { mutableFloatStateOf(0f) }762val animatedOffset by animateFloatAsState(763targetValue = offsetX,764animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy),765label = "offset"766)767768Card(769modifier = Modifier770.fillMaxWidth()771.offset { IntOffset(animatedOffset.roundToInt(), 0) }772.pointerInput(Unit) {773detectHorizontalDragGestures(774onDragEnd = {775when {776offsetX > 200f -> {777onSwipeRight()778offsetX = 0f779}780offsetX < -200f -> {781onSwipeLeft()782offsetX = 0f783}784else -> offsetX = 0f785}786},787onHorizontalDrag = { _, dragAmount ->788offsetX += dragAmount789}790)791}792) {793content()794}795}796```797