Material 3 UI Components in Jetpack Compose

Material Components

Jetpack Compose offers Material components that are composed of basic UI elements. These components encapsulate specific features, functionalities, or parts of your app's user interface. Using UI components promotes reusability, maintainability, and consistency in your app's design.

Components in Material Compose

  • Card

  • Scaffold

  • Button

  • App Bars

  • Floating Action Button

  • Chip

  • Dialog

  • Progress Indicators

  • Slider

  • Switch

  • Bottom Sheets

  • Navigation Drawer

  • Snackbar

  • Lists and Grids

We will be tackling each and every component one article at a time, delving into detail on what each component entails and all the various types, within the component.

For this Article, we are going to have a look at the Material Card Component.

We will design a Profile Card component using the Card Composable, look at several implementations using key component parameters for Card customization, and look at another example of a customized profile card using the Surface Composable.

Below is an example of what we will do.

Card

The Card composable serves as a Material Design container for your user interface. Typically, cards contain a single coherent piece of content. Here are several scenarios in which you might use a card:

  • A profile Card displaying your profile details

  • A product displayed in a shopping app.

  • A news item from a news app.

  • A message in a messaging app eg an error message.

Here are some key components of a Card component that allow you to customize the appearance and behavior of the component.:

@Composable
fun Card(
    // add onclick functionality to your card
    onClick: () -> Unit,
    //the Modifier to be applied to this card
    modifier: Modifier = Modifier,
    //controls the enabled state of this card
    enabled: Boolean = true,
    //shape defines the shape of this card's container
    shape: Shape = CardDefaults.shape,
    //colors defines CardColors that will be used to resolve the colors used for this card in different states
    colors: CardColors = CardDefaults.cardColors(),
    //elevation defines CardElevation used to resolve the elevation for this card in different states.
    elevation: CardElevation = CardDefaults.cardElevation(),
    //defines the border to draw around the container of this card
    border: BorderStroke? = null,
    //define the content for your card
    content: @Composable ColumnScope.() -> Unit
): Unit

The Profile Card

A Profile Card typically displays a user's profile information in a visually appealing way. It commonly consists of the following UI elements:

  1. Profile Image: A user's profile picture.

  2. Name: The user's name or title.

  3. Occupation: The user's occupation.

  4. Description: A brief description.

To create the Profile Card UI component, we combine the Profile Image, Name, Occupation, and Description within a Surface composable. The Surface provides elevation (a drop shadow), rounded corners, and background color to give the card a visually appealing appearance.

First, we will create a data class to help store, manipulate, and manage the user data, let's call this data class, UserDomain.

data class UserDomain(
//get the image from the inline link above `Profile Image` and save as profile
    val profileImageUrl: Int = R.drawable.profile,
    val name: String = "Name",
    val occupation: String,
    val description: String
)

Filled Card

The following is an example of how to use a filled card. The trick here is to modify the filled color using the colors attribute.

@Composable
fun FilledProfileCard(
    userData: UserDomain
) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        colors = CardDefaults.cardColors(
            containerColor = MaterialTheme.colorScheme.surfaceVariant,
        ),
    ) {
        Column(
            modifier =
            Modifier.padding(16.dp)
        ) {
            Image(
                painter = painterResource(id = userData.profileImageUrl),
                contentDescription = "Profile Image",
                modifier = Modifier
                    .size(120.dp)
                    .clip(CircleShape),
                contentScale = ContentScale.Crop
            )

            Spacer(modifier = Modifier.height(16.dp))

            // Name
            Text(
                text = userData.name,
                style = TextStyle(
                    fontSize = 20.sp,
                    fontWeight = FontWeight.Bold
                )
            )

            Spacer(modifier = Modifier.height(4.dp))

            // Occupation
            Text(
                text = userData.occupation,
                style = TextStyle(
                    fontSize = 16.sp,
                    color = Color.Gray
                )
            )

            Spacer(modifier = Modifier.height(8.dp))

            // Description
            Text(
                text = userData.description,
                style = TextStyle(
                    fontSize = 14.sp
                )
            )
        }
    }
}

@Preview(showBackground = true)
@Composable
fun FilledProfileCardPreview() {

    FilledProfileCard(
        userData = UserDomain(
            occupation = "Android Engineer",
            description = "This is a profile card, used to help explain UI Components in Compose"
        )
    )

}

Elevated Card

The code below explains how to use an Elevated card. Make use of the ElevatedCard composable.

The elevation attribute can be used to modify the appearance of elevation and the resulting shadow.

@Composable
fun ElevatedProfileCard(
    userData: UserDomain
) {
    ElevatedCard(
        elevation = CardDefaults.cardElevation(
            defaultElevation = 10.dp
        ),
        modifier = Modifier
            .padding(16.dp)
    ) {
        Column(
            modifier =
            Modifier.padding(16.dp)
        ) {
            Image(
                painter = painterResource(id = userData.profileImageUrl),
                contentDescription = "Profile Image",
                modifier = Modifier
                    .size(120.dp)
                    .clip(CircleShape),
                contentScale = ContentScale.Crop
            )

            Spacer(modifier = Modifier.height(16.dp))

            // Name
            Text(
                text = userData.name,
                style = TextStyle(
                    fontSize = 20.sp,
                    fontWeight = FontWeight.Bold
                )
            )

            Spacer(modifier = Modifier.height(4.dp))

            // Occupation
            Text(
                text = userData.occupation,
                style = TextStyle(
                    fontSize = 16.sp,
                    color = Color.Gray
                )
            )

            Spacer(modifier = Modifier.height(8.dp))

            // Description
            Text(
                text = userData.description,
                style = TextStyle(
                    fontSize = 14.sp
                )
            )
        }
    }
}

@Preview(showBackground = true)
@Composable
fun ElevatedProfileCardPreview() {
    ElevatedProfileCard(
        userData = UserDomain(
            occupation = "Android Engineer",
            description = "This is a profile card, used to help explain UI Components in Compose"
        )
    )
}

Outlined Card

An example of an outlined card is shown below. Make use of the OutlinedCard composable.

@Composable
fun OutlinedProfileCard(
    userData: UserDomain
) {
    OutlinedCard(
        colors = CardDefaults.cardColors(
            containerColor = MaterialTheme.colorScheme.surface,
        ),
        border = BorderStroke(1.dp, Color.Black),
        modifier = Modifier
            .padding(16.dp)
    ) {
        Column(
            modifier =
            Modifier.padding(16.dp)
        ) {
            Image(
                painter = painterResource(id = userData.profileImageUrl),
                contentDescription = "Profile Image",
                modifier = Modifier
                    .size(120.dp)
                    .clip(CircleShape),
                contentScale = ContentScale.Crop
            )

            Spacer(modifier = Modifier.height(16.dp))

            // Name
            Text(
                text = userData.name,
                style = TextStyle(
                    fontSize = 20.sp,
                    fontWeight = FontWeight.Bold
                )
            )

            Spacer(modifier = Modifier.height(4.dp))

            // Occupation
            Text(
                text = userData.occupation,
                style = TextStyle(
                    fontSize = 16.sp,
                    color = Color.Gray
                )
            )

            Spacer(modifier = Modifier.height(8.dp))

            // Description
            Text(
                text = userData.description,
                style = TextStyle(
                    fontSize = 14.sp
                )
            )
        }
    }
}

@Preview(showBackground = true)
@Composable
fun OutlinedProfileCardPreview() {
    OutlinedProfileCard(
        userData = UserDomain(
            occupation = "Android Engineer",
            description = "This is a profile card, used to help explain UI Components in Compose"
        )
    )
}

The Customized Profile Card UI Component

Let's delve into our example, the Customized Profile Card UI component.

@Composable
fun ProfileCard(
    userData: UserDomain
) {
    // Surface composable creates a card-like container
    Surface(
        modifier = Modifier
            .fillMaxWidth() //used to make the card span the full width of its parent
            .padding(16.dp), //add spacing around the card
        shadowElevation = 4.dp,
        shape = RoundedCornerShape(8.dp), //give rounded shaped corners
        color = Color.White
    ) {
        Column(
            modifier = Modifier
                .padding(16.dp)
        ) {
            // Profile Image
            Image(
                painter = painterResource(id = userData.profileImageUrl),
                contentDescription = "Profile Image",
                modifier = Modifier
                    .size(120.dp)
                    .clip(CircleShape),
                contentScale = ContentScale.Crop
            )

            Spacer(modifier = Modifier.height(16.dp))

            // Name
            Text(
                text = userData.name,
                style = TextStyle(
                    fontSize = 20.sp,
                    fontWeight = FontWeight.Bold
                )
            )

            Spacer(modifier = Modifier.height(4.dp))

            // Occupation
            Text(
                text = userData.occupation,
                style = TextStyle(
                    fontSize = 16.sp,
                    color = Color.Gray
                )
            )

            Spacer(modifier = Modifier.height(8.dp))

            // Description
            Text(
                text = userData.description,
                style = TextStyle(
                    fontSize = 14.sp
                )
            )
        }
    }

Limitations

Cards do not have their own scroll or dismiss actions, but they can be integrated into composables that do. To implement swipe to dismiss on a card, for example, use the SwipeToDismiss composable. Use scroll modifiers like, verticalScroll to integrate with scroll. More information can be found in the Scroll documentation.

Recommended Reads:

1
Subscribe to my newsletter

Read articles from Michelle and Jeremy directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Michelle and Jeremy
Michelle and Jeremy