Jetpack Compose IconButton

A Jetpack Compose icon is one of the smallest composables you'll use, but it shows up in almost every screen you build with Jetpack Compose. Whenever a Jetpack Compose UI needs to show a symbol instead of text — a star, a settings gear, a trash can — Jetpack's Icon composable is the function that draws it. Jetpack ships this composable inside both the material and material3 packages, which is why a Jetpack Compose icon automatically follows whatever theme your Jetpack app is using. Once you understand how Jetpack's Icon composable accepts its graphic, its size, its color, and its accessibility label, reaching for a Jetpack Compose icon becomes second nature.

This blog walks through every parameter Jetpack's Icon composable supports, how to load a Jetpack Compose vector icon versus a drawable-based one, how Jetpack lets you size and tint a Jetpack Compose icon, and how a Jetpack Compose icon fits into a real layout. Every code sample below is self-contained, so you can paste it into an online Kotlin compiler that supports Jetpack Compose and see the Jetpack Compose Icon composable render exactly as described.

What Is the Jetpack Compose Icon Composable

The Icon composable is a small, non-interactive function that Jetpack Compose provides purely to render a graphic on screen. Unlike Row or Column, a Jetpack Compose icon holds no children and arranges nothing — its entire job is to paint one piece of artwork using the color it's told to use. Jetpack ships the Icon composable in the material and material3 packages, so a Jetpack Compose icon automatically follows your app's Material theme without any extra wiring.

Here's the most basic Jetpack Compose icon example you'll come across, showing Jetpack's Icon composable at its simplest:

kotlin
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable

@Composable
fun BasicIconExample() {
    Icon(
        imageVector = Icons.Filled.Favorite,
        contentDescription = "Favorite"
    )
}

Running this composable draws a solid heart glyph at Jetpack's default icon size, tinted with whatever color the surrounding Material theme currently uses for content. Notice that this Jetpack Compose icon only needed two arguments: the graphic itself and a description for accessibility. Every other parameter on Jetpack's Icon composable — modifier and tint — has a sensible default, which is why a minimal Jetpack Compose icon example like this one is so short.

Setting Up Material Icons for a Jetpack Compose Icon

Before a Jetpack Compose icon can reference something like Icons.Filled.Favorite, your project needs the Material icons artifact on the classpath. Jetpack splits these material design icons into a small core set and a much larger extended set, so a Jetpack Compose icon only pulls in what you actually use.

  • material-icons-core: bundled automatically with Jetpack Compose Material and Material3, it contains a few hundred of the most common glyphs a Jetpack Compose icon can reference.
  • material-icons-extended: an optional dependency with several thousand additional icons, useful once the core set doesn't cover a Jetpack Compose icon you need.
  • Icons.Default: an alias Jetpack provides that points at the filled style, handy when you don't care which specific variant a Jetpack Compose icon uses.
kotlin
// build.gradle.kts (module level)
dependencies {
    implementation("androidx.compose.material:material-icons-core")
    implementation("androidx.compose.material:material-icons-extended")
}

Adding the extended dependency simply widens the pool of icons the Icons object exposes — it doesn't change how a Jetpack Compose icon is written or how Jetpack's Icon composable behaves once it's on screen. Most teams add the extended artifact early, since sifting through jetpack compose material icons options later is much easier when Jetpack's whole catalog is already available.

Displaying Vector Icons With ImageVector

The most common way to draw a Jetpack Compose vector icon is through the imageVector parameter, which accepts an ImageVector object. Jetpack's built-in icon set is already stored as ImageVector instances, so referencing one from Icons.Filled, Icons.Outlined, or a similar namespace is all a vector-based Jetpack Compose icon needs.

kotlin
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Home
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable

@Composable
fun VectorIconExample() {
    Icon(
        imageVector = Icons.Filled.Home,
        contentDescription = "Home"
    )
}

When this runs, Jetpack Compose reads the path data baked into the Home ImageVector and rasterizes it at the requested size, scaling cleanly at any resolution because vector graphics describe shapes with paths rather than fixed pixels. This is exactly why a Jetpack Compose vector icon looks crisp on every screen density without you shipping separate PNG files. Any time you're pulling a glyph straight from Jetpack's Icons object, you're building a Jetpack Compose vector icon this way.

Displaying Icons From Drawable Resources With Painter

Sometimes the graphic a Jetpack Compose icon needs to show lives in your app's res/drawable folder instead of Jetpack's built-in Icons object — maybe it's a custom brand mark exported as a vector drawable XML. For that case, Jetpack's Icon composable accepts a Painter instead of an ImageVector.

kotlin
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.painterResource

@Composable
fun PainterIconExample(iconResId: Int) {
    Icon(
        painter = painterResource(id = iconResId),
        contentDescription = "Custom brand mark"
    )
}

Here, painterResource loads a drawable resource by its resource id and wraps it in a Painter object that Jetpack's Icon composable can draw. This drawable resource path covers vector drawables, and it's the bridge many teams use when migrating icon assets from the older Android View system into a Jetpack Compose icon without redrawing every glyph from scratch. Whether the source is a hand-exported SVG-based vector or a simple shape drawable, painterResource turns it into something Jetpack's Icon composable understands.

Sizing a Jetpack Compose Icon

By default, a Jetpack Compose icon renders at 24dp, matching the standard size Jetpack's Material Design guidance recommends. You control jetpack compose icon size explicitly through the modifier parameter, the same way Jetpack expects you to size nearly every other composable.

kotlin
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Star
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun SizedIconExample() {
    Icon(
        imageVector = Icons.Filled.Star,
        contentDescription = "Star rating",
        modifier = Modifier.size(40.dp)
    )
}

Passing Modifier.size(40.dp) tells the Jetpack Compose icon to render 40 density-independent pixels wide and tall instead of the 24dp default, and the vector path scales up to fill that new box cleanly. Getting the jetpack compose icon size right matters for touch targets and visual hierarchy — a small status glyph next to body text usually stays near 16-20dp, while a hero icon on an empty state screen might grow past 48dp. Because sizing goes through Jetpack's ordinary modifier system, the same width, height, and size functions you'd use on any other Jetpack Compose composable apply directly to a Jetpack Compose icon.

Tinting and Coloring a Jetpack Compose Icon

The tint parameter controls what color a Jetpack Compose icon is drawn with, and Jetpack defaults it to whatever LocalContentColor currently resolves to in your composition. Setting jetpack compose icon tint explicitly is how you make a glyph match a status, a brand color, or a selected state in your Jetpack app.

kotlin
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color

@Composable
fun TintedIconExample() {
    Icon(
        imageVector = Icons.Filled.Warning,
        contentDescription = "Warning",
        tint = MaterialTheme.colorScheme.error
    )
}

This example paints the warning glyph using the current theme's error color, so the Jetpack Compose icon automatically adapts if the app switches between light and dark color schemes. You can just as easily pass a literal Color, like Color.Red or Color(0xFF00AA55), when a jetpack compose icon tint needs to be fixed rather than theme-driven. One special case worth knowing: passing Color.Unspecified tells Jetpack's Icon composable to skip tinting entirely and draw the source graphic's own original colors, which matters for multi-color bitmap icons that shouldn't be flattened to a single hue.

Adding a Content Description for Accessibility

The contentDescription parameter exists so screen readers can announce what a Jetpack Compose icon represents to users who can't see it. Jetpack requires this argument on every Icon composable overload, though it's nullable by design.

  • Meaningful icons: pass a short, clear string describing the action or state, such as "Delete item" or "Sync in progress" — screen readers read this text aloud.
  • Decorative icons: pass null when a Jetpack Compose icon sits right next to a text label that already says the same thing, so accessibility tools don't announce the same information twice.
  • Dynamic descriptions: build the string based on state, for example switching between "Play" and "Pause" depending on playback status.
kotlin
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable

@Composable
fun DecorativeIconExample() {
    Icon(
        imageVector = Icons.Filled.Delete,
        contentDescription = null
    )
}

Passing null here is intentional, not an oversight — it tells accessibility services to skip narrating this particular Jetpack Compose icon on its own. Getting this parameter right on every icon in your app is one of the simplest ways to keep a Jetpack Compose UI usable for people relying on TalkBack or similar screen readers.

Icon Style Variants and Other Ways to Load Icons

A handful of smaller details round out what Jetpack's Icon composable can do, each useful in narrower situations that don't need a full section of their own in a Jetpack app.

  • Style families: Jetpack's built-in icon set ships five visual styles for the same shapes — Icons.Filled, Icons.Outlined, Icons.Rounded, Icons.Sharp, and Icons.TwoTone — letting a jetpack compose custom icon look match your app's overall visual language.
  • Bitmap-based icons: Jetpack's Icon composable also accepts a bitmap parameter of type ImageBitmap, useful when a graphic originates from a raster source like a decoded PNG rather than a vector.
  • Fully custom vector paths: building an ImageVector by hand with ImageVector.Builder lets you construct a jetpack compose custom icon path-by-path when neither Jetpack's built-in catalog nor a drawable resource has what you need.
  • Icons.AutoMirrored variants: several directional glyphs, like arrows, ship an AutoMirrored counterpart that Jetpack flips automatically in right-to-left layouts.
kotlin
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Star
import androidx.compose.material.icons.outlined.Star
import androidx.compose.material.icons.rounded.Star
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable

@Composable
fun IconStyleVariantsExample() {
    Icon(imageVector = Icons.Filled.Star, contentDescription = "Filled star")
    Icon(imageVector = Icons.Outlined.Star, contentDescription = "Outlined star")
    Icon(imageVector = Icons.Rounded.Star, contentDescription = "Rounded star")
}

Each line here renders the same star shape through a different visual family, and swapping between them is as simple as changing the import and the qualifying package. Because every style still returns an ImageVector, none of the other parameters on Jetpack's Icon composable — size, tint, contentDescription — change based on which style family you pick.

Comparing the Icon Composable Overloads

Overload parameterSource typeTypical use case
imageVectorImageVectorBuilt-in Material glyphs or hand-built vector paths
painterPainterDrawable resources, including custom vector drawable XML
bitmapImageBitmapRaster graphics decoded from a PNG or similar source

Picking the right overload for a Jetpack Compose icon usually comes down to where the artwork lives — reach for imageVector when it's already an ImageVector, painter when it's a drawable resource, and bitmap when you're working with decoded raster pixels. All three overloads Jetpack provides accept the same modifier, tint, and contentDescription parameters, so the rest of the Icon composable's behavior stays identical no matter which source you choose.

Practical Use Case: A Settings Row With a Jetpack Compose Icon

A very common real-world pattern in Jetpack apps pairs a Jetpack Compose icon with a text label inside a Row, building a tappable settings entry. This combines several of the pieces Jetpack's Icon composable covers above — imageVector, size, tint, and contentDescription — into one small, reusable layout.

kotlin
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Notifications
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun SettingsRowWithIcon(onRowClick: () -> Unit) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .clickable { onRowClick() }
            .padding(16.dp),
        verticalAlignment = Alignment.CenterVertically
    ) {
        Icon(
            imageVector = Icons.Filled.Notifications,
            contentDescription = "Notification settings",
            modifier = Modifier.size(24.dp),
            tint = MaterialTheme.colorScheme.primary
        )
        androidx.compose.foundation.layout.Spacer(modifier = Modifier.width(16.dp))
        Text("Notifications")
    }
}

Tapping anywhere on this row triggers onRowClick, and the Jetpack Compose icon sits at a fixed 24dp size tinted with the theme's primary color, giving the row a consistent, on-brand look Jetpack apps rely on. The contentDescription on the icon stays meaningful here because the row is interactive, so a screen reader announcing "Notification settings" gives real information even before it reaches the text label. Stack a handful of rows like this one and you've got the backbone of a typical settings screen built with Jetpack Compose, each entry built around a differently sourced Jetpack Compose icon.

Complete Working Example

This final example ties several Jetpack Compose concepts together — vector icons, sizing, tinting, and contentDescription — inside one runnable screen that lists a few notification-style rows.

kotlin
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MaterialTheme {
                Surface {
                    StatusListScreen()
                }
            }
        }
    }
}

@Composable
fun StatusListScreen() {
    Column(modifier = Modifier.padding(16.dp)) {
        StatusRow(Icons.Filled.CheckCircle, Color(0xFF2E7D32), "Build passed")
        Spacer(modifier = Modifier.width(0.dp))
        StatusRow(Icons.Filled.Warning, Color(0xFFF9A825), "Two tests skipped")
        StatusRow(Icons.Filled.Info, Color(0xFF1565C0), "Deploy scheduled")
    }
}

@Composable
fun StatusRow(icon: androidx.compose.ui.graphics.vector.ImageVector, tintColor: Color, label: String) {
    Row(
        modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
        verticalAlignment = Alignment.CenterVertically
    ) {
        Icon(
            imageVector = icon,
            contentDescription = label,
            modifier = Modifier.size(28.dp),
            tint = tintColor
        )
        Spacer(modifier = Modifier.width(12.dp))
        Text(label)
    }
}

When this activity launches, three status rows stack vertically, each pairing a differently colored Jetpack Compose icon with a short label describing a build status. The StatusRow function reuses the same Jetpack Icon composable call with a different imageVector, tint, and contentDescription passed in each time, which is exactly the kind of reusable pattern a Jetpack Compose icon is designed to support across a real Jetpack app.