나미래 Android 개발자

[Jetpack Compose] 테마 간단하게 설정하기 본문

안드로이드/Jetpack Compose

[Jetpack Compose] 테마 간단하게 설정하기

Moimeme Futur 2026. 1. 7. 07:30

Jetpack Compose로 UI를 구성하다 보면 MaterialTheme.colorScheme.primary 같은 코드를 자연스럽게 쓰게 된다.

하지만 막상 이렇게 생각해보면 의문이 든다.

  • 이 테마는 어디서 정의되는 걸까?
  • 색상들은 어떤 기준으로 만들어졌을까?
  • 디자인 시스템이라는 건 정확하게 무엇을 의미할까?

이 글은 Jetpack Compose에서 Material Design 3 테마 구조를 학습하면서 정리한 내용이다.
특히 Material Theme Builder를 활용해 커스텀 테마를 생성하고 Compose에 적용하는 흐름을 중심으로 정리해보려고 한다.

학습 프로젝트 소개: Compose Theming Study

이 글에서 정리한 내용은 아래 학습용 프로젝트를 기반으로 한다.

학습용 프로젝틍 앱 화면

 

GitHub - JuhyeokLee97/ComposeThemingStudy

Contribute to JuhyeokLee97/ComposeThemingStudy development by creating an account on GitHub.

github.com

 

이 프로젝트의 목적은 명확하다.
Jetpack Compose에서 디자인 시스템과 테마 구조를 이해하는 것이다
기능 구현보다는, Material Design 3 테마가 어떻게 구성되고 Compose에서 어떻게 사용되는지를 중심으로 살펴보는 데 초점을 맞췄다.

이 글을 통해 Jetpack Compose에서 Theme이 어떤 구조로 구성되고, 어떤 흐름으로 프로젝트에 적용되는지 전체적인 그림을 잡을 수 있기를 바란다.

 

Jetpack Compose에서 Theme은 무엇을 담당할까?

Compose에서 MaterialTheme는 단순한 색상 묶음이 아니다. 앱 전체 UI의 기준점에 해당한다.

대표적으로 다음 요소들을 포함한다.

  • ColorScheme
    • primary / secondary / tertiary
    • background / surface / error
  • Typography
    • body, title, label 계열의 텍스트 스타일
  • Shape
    • 버튼, 카드 등 기본 컴포넌트 형태

Theme의 중요한 점은, UI 코드에서 직접 색상이나 스타일을 결정하지 않도록 유도한다는 것이다.

다시 말해서 Compose에서 Theme는 "디자인 값을 직접 쓰지 않게 만드는 장치"에 가깝다.

 

테마를 직접 정의하려다 느낀 어려움

Material Design 3 문서를 기준으로 테마를 직접 구성해보려고 하면 생각보다 고려해야 할 요소가 많다.

  • Light / Dark 테마 각각의 색상
  • 대비 문제
  • 각 Color token의 역할

학습 단계에서는 "이게 맞는 방향인가?"라는 의문이 계속 생기곤 했다.

이 과정에서 사용해본 도구가 Material Theme Builder였다.

 

Material Theme Builder로 테마 생성하기

Material Theme Builder는 Material Design 3 기준에 맞는 테마를 자동으로 생성해주는 도구다.

핵심 특징은 다음과 같다.

  • Primary 색상 기준으로 ColorScheme 생성
  • Light / Dark 테마 자동 구성
  • Material 3 가이드라인 준수

학습 목적에서 가장 좋았던 점은 완성된 결과물을 먼저 보고 구조를 이해할 수 있다는 점이다.

 

Compose 프로젝트로 테마 코드 가져오기

Material Theme Builder에서는 Export 옵션으로 Jetpack Compose(Theme)를 선택할 수 있다.

Export된 코드에는 보통 다음과 같은 파일이 포함된다.

  • Color.kt
  • Theme.kt
  • (Typography / Shape 관련 설정)

Material Theme Builder를 통해 Export 된 파일들

 

학습 프로젝트에서는 이 구조를 그대로 가져와 Compose Theme이 어떻게 정의되고 연결되는지를 중심으로 살펴봤다.

 

이 과정을 통해 자연스럽게 다음을 이해할 수 있었다.

  • ColorScheme이 어떻게 분리되는지
  • Light / Dark 테마가 어떻게 선택되는지

 

프로젝트에서 Theme 설정 구조

ui/theme/Color.kt

Color.kt 파일은 테마에서 사용할 색상을 정의하고 관리하는 파일이다.
즉, 색상 값 자체보다 "이 색이 어떤 역할을 하는지"에 집중하도록 설계되어 있다.

import androidx.compose.ui.graphics.Color

val primaryLight = Color(0xFF4C662B)
val onPrimaryLight = Color(0xFFFFFFFF)
val primaryContainerLight = Color(0xFFCDEDA3)
val onPrimaryContainerLight = Color(0xFF354E16)
...
val primaryDark = Color(0xFFB1D18A)
val onPrimaryDark = Color(0xFF1F3701)
val primaryContainerDark = Color(0xFF354E16)
val onPrimaryContainerDark = Color(0xFFCDEDA3)

이 구조 덕분에 테마 변경이 필요할 때도 UI 코드에 손대지 않고 색상 정의만 수정할 수 있다.

ui/theme/Type.kt

Type.kt 파일은 테마에서 사용할 TextStyle에 해당하는 typography를 정의하고 관리하는 파일이다.

val provider = GoogleFont.Provider(
    providerAuthority = "com.google.android.gms.fonts",
    providerPackage = "com.google.android.gms",
    certificates = R.array.com_google_android_gms_fonts_certs
)

val bodyFontFamily = FontFamily(
    Font(
        googleFont = GoogleFont("Roboto"),
        fontProvider = provider,
    )
)

val displayFontFamily = FontFamily(
    Font(
        googleFont = GoogleFont("Roboto"),
        fontProvider = provider,
    )
)

val baseline = Typography(
    headlineSmall = TextStyle(
        fontWeight = FontWeight.SemiBold,
        fontSize = 24.sp,
        lineHeight = 32.sp,
        letterSpacing = 0.sp
    ),
    titleLarge = TextStyle(
        fontWeight = FontWeight.Normal,
        fontSize = 18.sp,
        lineHeight = 32.sp,
        letterSpacing = 0.sp
    ),
    ...
)

val typography = Typography(
    displayLarge = baseline.displayLarge.copy(fontFamily = displayFontFamily),
    displayMedium = baseline.displayMedium.copy(fontFamily = displayFontFamily),
    displaySmall = baseline.displaySmall.copy(fontFamily = displayFontFamily),
    ...
)

ui/theme/Theme.kt

Theme.kt 파일은 앱에서 사용할 커스텀한 MaterialTheme을 설정하는 파일이다.
Color.kt에 정의된 값들을 참조해 lightColorScheme, darkColorScheme을 정의하고, Type.kt에 정의된 typography를 함께 설정해 커스텀한 ComposeThemingStudyTheme 내부의 MaterialTheme에 적용한다.

private val LightColorScheme = lightColorScheme(
    primary = primaryLight,
    onPrimary = onPrimaryLight,
    ...
)
private val DarkColorScheme = darkColorScheme(
    primary = primaryDark,
    onPrimary = onPrimaryDark,
    ...
)

@Composable
fun ComposeThemingStudyTheme(
    useDarkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        useDarkTheme -> DarkColorScheme
        else -> LightColorScheme
    }
    ...
    MaterialTheme(
        colorScheme = colorScheme,
        typography = typography,
        content = content
    )
}

아래처럼 Theme 레벨에서 colorScheme을 분기함으로써, 개별 Composable에서는 다크 모드 여부를 전혀 신경 쓰지 않아도 된다.

val colorScheme = when {
    useDarkTheme -> DarkColorScheme
    else -> LightColorScheme
}

 

Theme 사용하기

학습 프로젝트에서는 위와 같은 형태로 Theme을 정의하고, setContent{} 내부에서 Composable 함수 전체를 감싼다.

setContent {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    ComposeThemingStudyTheme {
        Surface(tonalElevation = 5.dp) {
            ReplyApp(
                replyHomeUiState = uiState,
                closeDetailScreen = {
                    viewModel.closeDetailScreen()
                },
                navigateToDetail = { emailId ->
                    viewModel.setSelectedEmail(emailId)
                }
            )
        }
    }
}

이 구조를 기준으로 setContent{} 내부의 모든 Composable은 MaterialTheme.colorSchemeMaterialTheme.typography를 통해서 ComposeThemingStudyTheme에 정의된 colorScheme과 typography에 접근하도록 구성했다.

이 구조 덕분에 Composable에서는 "어떤 색을 쓸지"가 아니라 "이 UI가 어떤 역할인지"만 표현하면 되도록 구성할 수 있었다.

Theme 적용 전 vs 후 화면

Theme 적용 전 vs 적용 후 화면

 

마무리

Jetpack Compose에서 Theme을 이해하는 것은 단순히 색상을 바꾸는 법을 배우는 것이 아니다.

  • Theme은 UI를 예쁘게 만드는 도구가 아니라 일관성을 강제하는 구조
  • Material Design 3의 Color token들은 "역할 기반"으로 이해해야 한다
  • "디자인 시스템이 무엇인지", "UI를 구조적으로 관리한다는 것이 어떤 의미인지"를 함께 이해하는 과정에 가깝다

Material Theme Builder는 그 과정을 시작하기에 좋은 도구였고, 학습 단계에서 "올바른 기준을 가진 결과물"을 빠르게 확인할 수 있게 도와줬다.

Compose Theme 구조가 막연하게 느껴졌다면, 한 번쯤은 테마만을 목적으로 한 작은 학습 프로젝트를 만들어보는 것도 좋은 방법이라고 생각한다.

Comments