Custom Theming in Jetpack Compose: Building a Scalable Design System
Mircea Ioan Soit
Senior Android Developer | Business Owner | Contractor | Team Builder | Founder of IC & Codertal
Part of the series "Android Development Series by Mircea Ioan Soit"
One of the key advantages of Jetpack Compose is its flexibility when it comes to UI design. Compose not only simplifies UI development but also makes it easier to create a consistent, maintainable, and scalable design system across your entire app. With its declarative approach, you can easily define and apply custom themes that align with your brand and deliver a unified user experience.
In this article, we’ll explore how to create and manage custom themes in Jetpack Compose, allowing you to build visually cohesive apps with ease.
1. Understanding Themes in Jetpack Compose
Jetpack Compose offers a built-in theming system based on Material Design principles. The MaterialTheme composable provides a default theme that includes colors, typography, and shapes. However, most apps need a custom look and feel, tailored to specific design requirements. Compose makes it simple to override these default values and create your own theme.
Basic Structure of MaterialTheme:
MaterialTheme(
colors = myCustomColors,
typography = myCustomTypography,
shapes = myCustomShapes
) {
// Your app's UI
}
2. Defining Custom Colors
Colors play a central role in any app’s theme. Jetpack Compose allows you to define a custom color palette by creating a Color object for primary, secondary, and other key UI elements like backgrounds and text.
a) Creating a Color Palette
Define your custom color palette by overriding the lightColors and darkColors functions based on your app’s brand guidelines. This gives you control over both light and dark modes.
Example:
private val MyLightColors = lightColors(
primary = Color(0xFF6200EA),
primaryVariant = Color(0xFF3700B3),
secondary = Color(0xFF03DAC5),
background = Color.White,
surface = Color.White,
onPrimary = Color.White,
onSecondary = Color.Black,
onBackground = Color.Black,
onSurface = Color.Black
)
b) Applying Your Custom Colors
Once your color palette is defined, apply it to the MaterialTheme as follows:
@Composable
fun MyAppTheme(content: @Composable () -> Unit) {
MaterialTheme(
colors = MyLightColors,
typography = myCustomTypography,
shapes = myCustomShapes
) {
content()
}
}
This ensures that the entire app uses the same color scheme, creating a cohesive experience across all screens.
3. Custom Typography
Typography is another key element of any app’s visual identity. Compose makes it easy to define a set of custom font styles for various UI components like headings, body text, and captions.
a) Creating a Custom Typography Set
You can customize the typography by creating a Typography object that defines different text styles such as h1, h2, body1, button, etc.
Example:
val myCustomTypography = Typography(
h1 = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Bold,
fontSize = 30.sp
),
body1 = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp
),
button = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.W500,
fontSize = 14.sp
)
)
b) Applying Custom Typography
Once defined, your custom typography can be applied to the MaterialTheme in the same way as the color palette:
MaterialTheme(
typography = myCustomTypography
) {
// UI components inherit typography from the theme
}
4. Custom Shapes for UI Components
Jetpack Compose also allows you to customize the shape of UI elements, such as buttons, cards, and dialogs. The Shapes object can define corner radius and other properties for different surface elements.
领英推荐
a) Defining Custom Shapes
You can define custom shapes for small, medium, and large components.
Example:
val myCustomShapes = Shapes(
small = RoundedCornerShape(8.dp),
medium = RoundedCornerShape(16.dp),
large = RoundedCornerShape(24.dp)
)
b) Applying Shapes
Just like colors and typography, shapes can be applied through MaterialTheme:
MaterialTheme(
shapes = myCustomShapes
) {
// UI components will use the custom shapes
}
5. Theming at Component Level
While the global theme applies to the entire app, Compose also allows you to override theme settings at a more granular level, such as for individual components. This gives you flexibility to make certain components stand out or adhere to specific design rules.
a) Overriding Typography for Specific Components
If you want to apply a different text style to a single Text composable, you can override the typography like this:
Example:
Text(
text = "Hello, Custom Typography",
style = MaterialTheme.typography.h1.copy(
fontSize = 40.sp,
color = Color.Red
)
)
This applies a larger font size and changes the text color for this specific instance of the Text component.
6. Supporting Light and Dark Themes
Jetpack Compose makes it easy to support both light and dark themes in your app by defining separate color palettes for each. You can dynamically switch between the two themes based on the system setting.
a) Define Light and Dark Color Schemes
private val MyDarkColors = darkColors(
primary = Color(0xFFBB86FC),
primaryVariant = Color(0xFF3700B3),
secondary = Color(0xFF03DAC5)
)
b) Applying Dynamic Theme Switching
You can toggle between light and dark themes using the isSystemInDarkTheme() function to automatically adjust the theme based on the system’s current setting:
Example:
@Composable
fun MyAppTheme(content: @Composable () -> Unit) {
val colors = if (isSystemInDarkTheme()) MyDarkColors else MyLightColors
MaterialTheme(
colors = colors,
typography = myCustomTypography,
shapes = myCustomShapes
) {
content()
}
}
7. Extending Themes with Custom Attributes
Jetpack Compose’s theming system allows you to go beyond the basic Material Design properties and add custom attributes that your UI components can access. For example, you can create a custom dimension or padding value.
Example:
val LocalPadding = staticCompositionLocalOf { PaddingValues(8.dp) }
@Composable
fun MyAppTheme(content: @Composable () -> Unit) {
CompositionLocalProvider(LocalPadding provides PaddingValues(16.dp)) {
MaterialTheme(
colors = MyLightColors,
typography = myCustomTypography,
shapes = myCustomShapes
) {
content()
}
}
}
@Composable
fun CustomComponent() {
val padding = LocalPadding.current
Box(modifier = Modifier.padding(padding)) {
Text("This component uses custom padding")
}
}
8. Conclusion: Building Scalable Design Systems with Compose
Custom theming in Jetpack Compose provides a powerful way to enforce consistency and maintainability across your app’s UI. By leveraging custom colors, typography, shapes, and supporting both light and dark modes, you can build a cohesive design system that scales across screens and components.
As your app grows, you can extend your theme to handle additional design requirements, making Compose a versatile tool for both small and large-scale applications.