Create a Button in Jetpack Compose

Jyoti MauryaJyoti Maurya
12 min read

Introduction

Jetpack Compose has revolutionized Android UI development by introducing a modern, declarative approach to building user interfaces. Unlike traditional XML-based layouts, Jetpack Compose allows developers to define UI components directly in Kotlin code, making the development process more intuitive, concise, and scalable.

One of the most commonly used UI elements in any application is the Button—a fundamental component for user interaction. Whether it's submitting a form, navigating to another screen, or triggering an action, buttons play a crucial role in enhancing user experience.

In this blog post, we’ll explore how to create and customize buttons using Jetpack Compose. From defining a simple button to applying styles, handling click events, and adding icons, this guide will walk you through the essentials you need to effectively use buttons in your Compose-based Android apps.

What is Jetpack Compose?

Jetpack Compose is Android’s modern toolkit for building native user interfaces. Developed by Google, it offers a declarative programming model, meaning you describe what the UI should look like, and the framework takes care of updating it based on state changes.

Traditionally, Android developers used XML for UI design and coupled it with Java or Kotlin for behavior. This often led to boilerplate code, complex view hierarchies, and tight coupling between logic and presentation. Jetpack Compose addresses these limitations by enabling UI to be built directly in Kotlin, improving readability, modularity, and productivity.

In this blog, we will focus specifically on how to create and customize buttons, one of the most essential interactive elements, using Jetpack Compose.

Setting Up Jetpack Compose

Before you start building buttons (or any UI components) with Jetpack Compose, it’s important to ensure your development environment is correctly set up. This includes using the appropriate version of Android Studio, configuring your project settings, and adding the necessary dependencies.

  1. Pre-Requisites

    Make sure you have the following:

    • Android Studio Flamingo or higher

    • Kotlin 1.9.0+

    • Minimum SDK version 21

  1. Create a New Project with Compose Support

    When creating a new project in Android Studio:

    • Choose “Empty Activity” as the template.

    • This will auto-generate the necessary files and basic Compose setup.

  1. Configuring your build.gradle

    If you're adding Jetpack Compose to an existing project, make sure your build.gradle (app-level) includes the following:

android {
    ...
    buildFeatures {
        compose true
    }

    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.3"
    }
}

dependencies {
    implementation("androidx.compose.ui:ui:1.5.3")
    implementation("androidx.compose.material:material:1.5.3")
    implementation("androidx.compose.ui:ui-tooling-preview:1.5.3")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
    implementation("androidx.activity:activity-compose:1.7.2")
    debugImplementation("androidx.compose.ui:ui-tooling:1.5.3")
}

Once the setup is complete, you are ready to start building UI components using Jetpack Compose.

Creating a Basic Button

  • Syntax for creating a simple Button.

  • Code snippet with explanation.

  • Preview in Compose.

Creating a button in Jetpack Compose is straightforward and requires minimal boilerplate code. Compose provides a built-in Button composable that you can use to create interactive UI elements with just a few lines of Kotlin.

Basic Button Syntax

Here’s a simple example of a button with a click listener:

@Composable
fun LearnButtons(innerPadding) {
    val context = LocalContext.current.applicationContext
    Column(modifier = Modifier.padding(innerPadding).fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
        Button(onClick = { Toast.makeText(context, "Button Clicked successfully !!!", Toast.LENGTH_SHORT).show()},
            shape = RoundedCornerShape(size = 16.dp)
        ) {
            Text(text = "Click Me ")
        }
    }
}

    • @Composable: Marks this function as a composable, meaning it can be used in Jetpack Compose's UI tree and can emit UI elements.

      * innerPadding: PaddingValues: A parameter that provides padding values—typically passed from a Scaffold or another composable to handle spacing (e.g., around a top bar or navigation bar).

      * This line gets the current Android context, which is required for things like displaying a Toast message.

      * LocalContext.current provides access to the Compose-aware context, and .applicationContext ensures that you're using a long-lived application context.

      *

      * Column: A vertical layout composable that arranges children in a column.

      * modifier:

      * .padding(innerPadding): Applies the padding passed into the function.

      * .fillMaxSize(): Makes the column occupy the full size of the parent container.

      * verticalArrangement: Centers children vertically.

      * horizontalAlignment: Centers children horizontally.

      *

      * onClick: When the button is clicked, it triggers a Toast message displaying: "Button Clicked successfully !!!".

      * shape: Gives the button rounded corners with a radius of 16.dp

      *

      This is the label displayed on the button.

Customizing the Button

Jetpack Compose provides flexible customization options to tailor buttons to match your app's design. You can modify the button’s color, shape, elevation, padding, and text styling using parameters and modifiers.

Button Parameters:

  1. onClick: The onClick parameter is a lambda function that defines the behavior when the button is tapped or clicked. It is mandatory and usually contains actions like navigation, function calls, or UI updates. For example, you might display a toast or trigger an event when the user interacts with the button.

  2. modifier: The modifier parameter is used to apply layout and styling instructions to the button. This can include size, padding, background, click listeners, alignment, and more. It helps in customizing the button’s appearance and behavior within the Compose UI hierarchy.

  3. enabled: The enabled parameter is a Boolean that determines whether the button is interactive. If set to false, the button appears visually disabled (usually dimmed) and does not respond to click events. It is useful for conditional UI logic, such as disabling a button when a form is incomplete.

  4. shape: The shape parameter defines the corner shape of the button using a Shape object like RoundedCornerShape, CutCornerShape, etc. This allows for creating circular, rounded, or custom shapes to match the desired UI design.

  5. colors: The colors parameter allows customization of the button’s color scheme using ButtonDefaults.buttonColors(). You can specify different colors for background, content (text/icon), and disabled states. It provides flexibility to match the app’s theme or branding.

  6. elevation: The elevation parameter sets the visual depth (shadow) of the button using ButtonDefaults.elevation(). It enhances the UI by adding dimensionality. You can define different elevations for default, pressed, focused, and disabled states.

  7. border: The border parameter allows you to specify a BorderStroke, which draws a border around the button. This can be used to create outlined buttons or emphasize interaction regions using color and width.

  8. contentPadding: The contentPadding parameter defines the internal padding around the button's content (such as text or icon). It controls the spacing between the button’s edge and its content, improving readability and touch target size.

  9. interactionSource: The interactionSource parameter is used to observe or control the button’s interaction states (like pressed, focused, or hovered). It enables advanced UI behavior, such as showing animations or effects based on user interaction.

  10. content: The content parameter is a composable lambda that defines what will be displayed inside the button, such as Text, Icon, or any combination of composables. It offers full flexibility to design the internal layout of the button.

1. Customizing Colors

You can change the button’s background and content (text/icon) colors using the colors parameter:

@Composable
fun LearnButtons(innerPadding: PaddingValues){

    val context = LocalContext.current.applicationContext

    Column(modifier = Modifier.padding(innerPadding).fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
        Button(
            onClick = { Toast.makeText(context, "Button Clicked successfully !!!", Toast.LENGTH_SHORT).show()},
            shape = RoundedCornerShape(size = 16.dp),
            colors = ButtonColors(
                containerColor = Color.Magenta,
                contentColor = Color.White,
                disabledContentColor = Color.Gray,
                disabledContainerColor = Color.LightGray
            )
        ) {
            Text(text = "Click Me ")
        }
    }
}

2. Adjusting Shape and Elevation

You can modify the button’s shape (e.g., rounded corners) and elevation (shadow):

@Composable
fun LearnButtons(innerPadding: PaddingValues){

    val context = LocalContext.current.applicationContext

    Column(modifier = Modifier.padding(innerPadding).fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
        Button(
            onClick = { Toast.makeText(context, "Button Clicked successfully !!!", Toast.LENGTH_SHORT).show()},
            shape = RoundedCornerShape(size = 16.dp),
            colors = ButtonColors(
                containerColor = Color.Magenta,
                contentColor = Color.White,
                disabledContentColor = Color.Gray,
                disabledContainerColor = Color.LightGray
            ),
            elevation = buttonElevation(
                defaultElevation = 8.dp,
                pressedElevation = 18.dp
            )
        ) {
            Text(text = "Click Me ")
        }
    }
}

3. Customizing Text Style

Text inside the button can also be styled using the Text composable:

@Composable
fun LearnButtons(innerPadding: PaddingValues){

    val context = LocalContext.current.applicationContext

    Column(modifier = Modifier.padding(innerPadding).fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
        Button(
            onClick = { Toast.makeText(context, "Button Clicked successfully !!!", Toast.LENGTH_SHORT).show()},
            shape = RoundedCornerShape(size = 16.dp),
            colors = ButtonColors(
                containerColor = Color.Magenta,
                contentColor = Color.White,
                disabledContentColor = Color.Gray,
                disabledContainerColor = Color.LightGray
            ),
            elevation = buttonElevation(
                defaultElevation = 8.dp,
                pressedElevation = 18.dp
            )
        ) {
            Text(text = "Click Me ",
                fontWeight = FontWeight.Bold,
                color = Color.Cyan,
                fontSize = 18.sp)
        }
    }
}

4. Applying Padding and Size

Use the modifier parameter to control layout-related properties like padding, width, or height.

@Composable
fun LearnButtons(innerPadding: PaddingValues){

    val context = LocalContext.current.applicationContext

    Column(modifier = Modifier.padding(innerPadding).fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
        Button(
            onClick = { Toast.makeText(context, "Button Clicked successfully !!!", Toast.LENGTH_SHORT).show()},
            modifier = Modifier.padding(16.dp).height(50.dp).fillMaxWidth(),
            shape = RoundedCornerShape(size = 16.dp),
            colors = ButtonColors(
                containerColor = Color.Magenta,
                contentColor = Color.White,
                disabledContentColor = Color.Gray,
                disabledContainerColor = Color.LightGray
            ),
            elevation = buttonElevation(
                defaultElevation = 8.dp,
                pressedElevation = 18.dp
            )
        ) {
            Text(text = "Click Me ",
                fontWeight = FontWeight.Bold,
                color = Color.Cyan,
                fontSize = 18.sp)
        }
    }
}

Tip: Reusability

For consistency, you can create a custom button composable and reuse it across your app:

@Composable
fun MyCustomButton(text: String, onClick: () -> Unit) {
    Button(
        onClick = onClick,
        colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF6200EE))
    ) {
        Text(text = text, color = Color.White)
    }
}

Handling Click Events

Buttons are inherently interactive components, and the primary reason we use them is to trigger actions when clicked. In Jetpack Compose, handling button clicks is simple and intuitive using the onClick parameter.

1. Basic Click Handling

Here's a minimal example where a message is logged on button click:

Button(
    onClick = { 
        println("Button clicked") 
    }
) {
    Text("Click Me")
}

In a real application, instead of logging, you'd typically update the UI state or call a function.

2. Using a State Variable

You can manage dynamic UI changes using Compose’s remember and mutableStateOf:

@Composable
fun StatefulButton() {
    var clicked by remember { mutableStateOf(false) }

    Button(onClick = { clicked = !clicked }) {
        Text(if (clicked) "Clicked!" else "Click Me")
    }
}

The StatefulButton composable demonstrates how to manage and respond to user interaction using state in Jetpack Compose. Inside the function, a Boolean variable clicked is declared using var clicked by remember { mutableStateOf(false) }, which initializes the state as false and remembers its value across recompositions. The remember function ensures that the state survives recompositions of the composable, while mutableStateOf allows the value to be observable so that UI updates automatically when the value changes. The Button composable uses this state in its onClick lambda to toggle the clicked value between true and false whenever the button is pressed. Inside the button, a Text composable displays the label conditionally—showing "Clicked!" when the state is true, and "Click Me" when false. This example effectively illustrates how Compose handles stateful UI by binding the UI’s appearance directly to state changes, eliminating the need for manual view updates.

3. Triggering a ViewModel Action

In a clean architecture setup, you often handle clicks by invoking a function from a ViewModel. This ensures a separation of UI and business logic:

@Composable
fun ButtonWithViewModel(viewModel: MyViewModel) {
    Button(onClick = { viewModel.onSubmit() }) {
        Text("Submit")
    }
}

And in your ViewModel:

class MyViewModel : ViewModel() {
    fun onSubmit() {
        // Handle logic here
    }
}

By effectively handling click events, you can create responsive and dynamic interactions that enhance the user experience.

Button with Text/Icons

Buttons often need to convey more than just text—icons can help reinforce the action visually, improving usability and clarity. Jetpack Compose makes it easy to create buttons that combine icons and text using composables like Icon, Row, and Spacer.

1. Button with Icon and Text Using Row

Here's how you can add an icon next to text inside a button:

@Composable
fun IconTextButton() {
    Button(onClick = { /* Handle click */ }) {
        Row(verticalAlignment = Alignment.CenterVertically) {
            Icon(
                imageVector = Icons.Default.Favorite,
                contentDescription = "Favorite",
                modifier = Modifier.size(20.dp)
            )
            Spacer(modifier = Modifier.width(8.dp))
            Text("Like")
        }
    }
}

2. Using Predefined IconButton (for Icon-only Buttons)

If you want a button that only shows an icon, IconButton is the appropriate composable:

@Composable
fun IconOnlyButton() {
    IconButton(onClick = { /* Handle click */ }) {
        Icon(
            imageVector = Icons.Default.Share,
            contentDescription = "Share"
        )
    }
}

3. Customizing Icon Color and Size

You can style the icon to match your app’s design:

Icon(
    imageVector = Icons.Default.Send,
    contentDescription = "Send",
    tint = Color.Blue,
    modifier = Modifier.size(24.dp)
)

With icon and text combinations, you can create buttons that are both functional and visually informative, enhancing the user interface and experience.

Common Use Cases

Buttons serve a wide range of purposes in Android applications, and Jetpack Compose makes it easy to implement them for various interaction scenarios. Below are some common button use cases along with practical implementation tips.

1. Submit or Login Button

These are typically full-width buttons placed at the bottom of a form or screen.

@Composable
fun SubmitButton(onClick: () -> Unit) {
    Button(
        onClick = onClick,
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizontal = 16.dp, vertical = 8.dp)
    ) {
        Text("Submit")
    }
}

2. Buttons with Loading Indicators

When performing long-running actions (like API calls), it’s good UX to show a loading indicator:

@Composable
fun LoadingButton(isLoading: Boolean, onClick: () -> Unit) {
    Button(
        onClick = onClick,
        enabled = !isLoading
    ) {
        if (isLoading) {
            CircularProgressIndicator(
                color = Color.White,
                strokeWidth = 2.dp,
                modifier = Modifier.size(16.dp)
            )
            Spacer(modifier = Modifier.width(8.dp))
        }
        Text(if (isLoading) "Loading..." else "Submit")
    }
}

3. Navigation Button

You can use buttons to navigate between screens using NavController from Jetpack Navigation Compose:

@Composable
fun NavigateButton(navController: NavController) {
    Button(onClick = { navController.navigate("next_screen") }) {
        Text("Go to Next Screen")
    }
}

4. Toggle or State Buttons

Use buttons to switch states, such as turning features on or off:

@Composable
fun ToggleFeatureButton(isEnabled: Boolean, onToggle: () -> Unit) {
    Button(onClick = onToggle) {
        Text(if (isEnabled) "Disable Feature" else "Enable Feature")
    }
}

These use cases demonstrate how flexible and powerful Compose buttons can be when tailored to meet common UI interaction patterns.

Best Practices

While Jetpack Compose makes it easy to build and customize buttons, following best practices ensures that your UI remains accessible, consistent, and maintainable. Here are some key guidelines to follow when working with buttons in Compose:

1. Maintain Visual Consistency

  • Use a consistent button style across your app to reinforce your brand identity.

  • Define reusable composables for frequently used button types (e.g., primary, secondary, disabled).

  • Leverage MaterialTheme for consistent color and typography usage.

2. Ensure Accessibility

  • Always provide meaningful contentDescription for icons to support screen readers.

  • Maintain sufficient contrast between button text and background color.

  • Use enabled/disabled states to give clear feedback on button availability.

3. Avoid Over-Customization

  • Stick with Material Design guidelines unless there's a strong use case for deviation.

  • Avoid unnecessary overrides that make the UI inconsistent or harder to maintain.

4. Optimize for Reusability

  • Wrap frequently used configurations into custom composables.

  • Accept parameters like text, onClick, modifier, etc., to maintain flexibility.

5. Responsive Design

  • Use layout modifiers like fillMaxWidth() and wrapContentSize() to ensure buttons scale properly across devices.

  • Avoid hardcoded dimensions where possible.

After Words

Buttons are essential interactive elements in any Android application, and Jetpack Compose offers a modern, flexible, and expressive way to create them.

Jetpack Compose not only simplifies UI development but also encourages clean, maintainable, and reusable code patterns. With its declarative nature and powerful composables, building responsive and dynamic buttons becomes a seamless experience.

As you continue working with Compose, try to create a UI that uses buttons like a counter program.

0
Subscribe to my newsletter

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

Written by

Jyoti Maurya
Jyoti Maurya

I create cross platform mobile apps with AI functionalities. Currently a PhD Scholar at Indira Gandhi Delhi Technical University for Women, Delhi. M.Tech in Artificial Intelligence (AI).