# How to Implement Material 3 Theming with Custom Colors and Typography in Jetpack Compose

> Learn Material 3 Theming in Jetpack Compose. Explore custom colors and typography, defining Color objects and Type objects within your Theme.kt and Color.kt files for a personalized app design.

- Repository: [Shuyu Guo/gsygithubappcompose](https://github.com/carguo/gsygithubappcompose)
- Tags: tutorial
- Published: 2026-02-26

---

**Material 3 theming in Jetpack Compose works by defining custom `Color` objects in [`Color.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/Color.kt), constructing `lightColorScheme` and `darkColorScheme` instances in [`Theme.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/Theme.kt), and passing a custom `Typography` object from [`Type.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/Type.kt) into the `MaterialTheme` composable, which propagates these design tokens to all descendant composables via `MaterialTheme.colorScheme` and `MaterialTheme.typography`.**

The `carguo/gsygithubappcompose` repository demonstrates a production-ready implementation of Material Design 3 in a GitHub client application. By isolating color definitions, typography scales, and theme logic into dedicated files, the app maintains consistent visual branding across light and dark modes while supporting dynamic color capabilities on Android 12 and above.

## Defining Custom Color Palettes in Color.kt

The foundation of the theme begins with raw color constants defined in [`core/ui/src/main/java/com/shuyu/gsygithubappcompose/core/ui/theme/Color.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/core/ui/src/main/java/com/shuyu/gsygithubappcompose/core/ui/theme/Color.kt). These hexadecimal values represent the brand palette before they are assigned semantic roles.

### Creating Color Constants

Define immutable `Color` objects for your brand colors, neutral surfaces, and text colors:

```kotlin
// core/ui/src/main/java/com/shuyu/gsygithubappcompose/core/ui/theme/Color.kt
val PrimaryColor   = Color(0xFF24292E)   // main brand gray
val PrimaryLight  = Color(0xFF444D56)
val PrimaryDark   = Color(0xFF1B1F23)

val SecondaryColor = Color(0xFFADB0B6)
val SecondaryLight = Color(0xFFA3A3A8)

val BackgroundLight = Color(0xFFFAFBFC)
val BackgroundDark  = Color(0xFF24292E)

```

### Building Light and Dark Color Schemes

In [`core/ui/src/main/java/com/shuyu/gsygithubappcompose/core/ui/theme/Theme.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/core/ui/src/main/java/com/shuyu/gsygithubappcompose/core/ui/theme/Theme.kt), these raw colors are mapped to Material 3 semantic roles using `lightColorScheme()` and `darkColorScheme()`:

```kotlin
// Theme.kt
private val DarkColorScheme = darkColorScheme(
    primary = PrimaryColor,
    secondary = SecondaryColor,
    tertiary = SecondaryLight,
    background = BackgroundDark,
    surface = SurfaceDark,
    onPrimary = TextOnDark,
    onBackground = TextPrimary
)

private val LightColorScheme = lightColorScheme(
    primary = PrimaryColor,
    secondary = SecondaryColor,
    tertiary = SecondaryLight,
    background = BackgroundLight,
    surface = SurfaceLight,
    onPrimary = TextOnDark,
    onBackground = TextPrimary
)

```

## Configuring Custom Typography in Type.kt

Material 3 theming allows you to override specific text styles without redefining the entire type scale. The `gsygithubappcompose` project places these definitions in [`app/src/main/java/com/shuyu/gsygithubappcompose/ui/theme/Type.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/app/src/main/java/com/shuyu/gsygithubappcompose/ui/theme/Type.kt).

### Customizing Text Styles

Instantiate the `Typography` class and override only the slots you need. Unspecified slots automatically fall back to Material defaults:

```kotlin
// Type.kt
val Typography = Typography(
    bodyLarge = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 16.sp,
        lineHeight = 24.sp,
        letterSpacing = 0.5.sp
    )
    // Override other slots like titleLarge, labelSmall as needed
)

```

### Integrating Typography with MaterialTheme

The custom `Typography` instance is injected into the theme hierarchy alongside the color scheme:

```kotlin
// Theme.kt
MaterialTheme(
    colorScheme = colorScheme,
    typography = Typography,
    content = content
)

```

## Implementing the Theme Wrapper in Theme.kt

The `GSYGithubAppComposeTheme` composable serves as the single entry point for applying your custom Material 3 theming across the application.

### The GSYGithubAppComposeTheme Composable

This function selects the appropriate color scheme based on system settings and wraps the content with `MaterialTheme`:

```kotlin
@Composable
fun GSYGithubAppComposeTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    dynamicColor: Boolean = false, // Disabled to preserve brand colors
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) 
            else dynamicLightColorScheme(context)
        }
        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }

    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography,
        content = content
    )
}

```

### Supporting Light, Dark, and Dynamic Color Modes

The theme supports three distinct modes:

- **Dynamic colors**: Extracted from the user's wallpaper on Android 12+ (disabled by default in this repository)
- **System dark mode**: Uses `DarkColorScheme` with custom brand grays
- **System light mode**: Uses `LightColorScheme` with the same brand palette

## Consuming Theme Values in UI Components

All UI composables access the theme through `MaterialTheme` static properties, ensuring visual consistency without hardcoding values.

### Referencing Colors with MaterialTheme.colorScheme

Components read semantic color roles rather than raw hex values:

```kotlin
Button(
    onClick = { /*...*/ },
    colors = ButtonDefaults.buttonColors(
        containerColor = MaterialTheme.colorScheme.primary,
        contentColor = MaterialTheme.colorScheme.onPrimary
    )
) {
    Text(
        text = "Refresh",
        style = MaterialTheme.typography.titleMedium
    )
}

```

Surface containers use background and surface colors:

```kotlin
Card(
    colors = CardDefaults.cardColors(
        containerColor = MaterialTheme.colorScheme.surface
    )
) {
    Text(
        text = "Repository description",
        style = MaterialTheme.typography.bodyLarge,
        color = MaterialTheme.colorScheme.onSurface
    )
}

```

### Applying Typography with MaterialTheme.typography

Text components reference the type scale defined in [`Type.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/Type.kt):

```kotlin
Text(
    text = stringResource(R.string.welcome_title),
    style = MaterialTheme.typography.titleMedium,
    color = MaterialTheme.colorScheme.onBackground
)

```

## Summary

- Define raw brand colors in [`core/ui/src/main/java/com/shuyu/gsygithubappcompose/core/ui/theme/Color.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/core/ui/src/main/java/com/shuyu/gsygithubappcompose/core/ui/theme/Color.kt) as `Color` objects.
- Map colors to semantic roles using `lightColorScheme()` and `darkColorScheme()` in [`Theme.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/Theme.kt).
- Override specific text styles in [`app/src/main/java/com/shuyu/gsygithubappcompose/ui/theme/Type.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/app/src/main/java/com/shuyu/gsygithubappcompose/ui/theme/Type.kt) while inheriting defaults for unused slots.
- Wrap your application UI with `GSYGithubAppComposeTheme` to propagate `MaterialTheme` values.
- Access design tokens in composables via `MaterialTheme.colorScheme` and `MaterialTheme.typography` to ensure automatic updates when the theme changes.

## Frequently Asked Questions

### How do I add a new custom color to the Material 3 theme?

Add a new `val` declaration in [`Color.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/Color.kt) with your desired hex value, then reference it in either `DarkColorScheme` or `LightColorScheme` (or both) using the appropriate Material 3 role such as `primaryContainer`, `outline`, or `surfaceVariant`. Any composable using `MaterialTheme.colorScheme` will automatically reflect the change without modification.

### Can I enable dynamic colors while keeping specific brand colors?

Yes. In [`Theme.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/Theme.kt), allow `dynamicColor` to be `true` when `Build.VERSION.SDK_INT >= Build.VERSION_CODES.S`, which generates a scheme from the user's wallpaper. Then selectively override specific roles (such as `primary`) with your brand constants before passing the final `colorScheme` to `MaterialTheme`, ensuring critical brand elements remain consistent while surfaces adopt dynamic tints.

### How do I change only the body text style without affecting headings?

In [`Type.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/Type.kt), construct the `Typography` instance and provide values only for the specific slots you wish to customize, such as `bodyLarge` or `labelSmall`. Material 3 automatically uses default values for any unspecified text styles, so you only need to define deviations from the baseline design system.

### Where should I place theme files in a multi-module project?

Place base color definitions and the main theme wrapper in a shared module (e.g., `core/ui/src/main/java/com/shuyu/gsygithubappcompose/core/ui/theme/`) so all feature modules can access them. Application-specific typography or theme variants can reside in the `app` module (`app/src/main/java/com/shuyu/gsygithubappcompose/ui/theme/`), as demonstrated in the `gsygithubappcompose` repository structure.