Skip to main content
Lumo UI provides a comprehensive theming system that allows you to customize your app’s visual identity across all components.

Theme Architecture

The AppTheme composable wraps your app and provides:
  • Colors - Light and dark color schemes
  • Typography - Text styles and font families
  • Shapes - Corner radius and borders
  • Elevation - Shadow and depth effects
import com.nomanr.lumo.ui.AppTheme

@Composable
fun App() {
    AppTheme(
        isDarkTheme = isSystemInDarkTheme()
    ) {
        // Your app content
    }
}

Color System

Default Color Palette

Lumo UI includes a comprehensive color palette defined in Color.kt:
Color.kt
package com.nomanr.lumo.ui

import androidx.compose.ui.graphics.Color

// Grayscale
val Black = Color(0xFF000000)
val Gray900 = Color(0xFF282828)
val Gray800 = Color(0xFF4b4b4b)
val Gray700 = Color(0xFF5e5e5e)
val Gray600 = Color(0xFF727272)
val Gray500 = Color(0xFF868686)
val Gray400 = Color(0xFFC7C7C7)
val Gray300 = Color(0xFFDFDFDF)
val Gray200 = Color(0xFFE2E2E2)
val Gray100 = Color(0xFFF7F7F7)
val White = Color(0xFFFFFFFF)

// Error/Red Scale
val Red900 = Color(0xFF520810)
val Red800 = Color(0xFF950f22)
val Red700 = Color(0xFFbb032a)
val Red600 = Color(0xFFde1135)
val Red500 = Color(0xFFf83446)
val Red400 = Color(0xFFfc7f79)
val Red300 = Color(0xFFffb2ab)
val Red200 = Color(0xFFffd2cd)
val Red100 = Color(0xFFffe1de)
val Red50 = Color(0xFFfff0ee)

// Tertiary/Blue Scale
val Blue900 = Color(0xFF276EF1)
val Blue800 = Color(0xFF3F7EF2)
val Blue700 = Color(0xFF578EF4)
val Blue600 = Color(0xFF6F9EF5)
val Blue500 = Color(0xFF87AEF7)
val Blue400 = Color(0xFF9FBFF8)
val Blue300 = Color(0xFFB7CEFA)
val Blue200 = Color(0xFFCFDEFB)
val Blue100 = Color(0xFFE7EEFD)

// Success/Green Scale
val Green950 = Color(0xFF0B4627)
val Green900 = Color(0xFF16643B)
val Green800 = Color(0xFF1A7544)
val Green700 = Color(0xFF178C4E)
val Green600 = Color(0xFF1DAF61)
val Green500 = Color(0xFF1FC16B)
val Green400 = Color(0xFF3EE089)
val Green300 = Color(0xFF84EBB4)
val Green200 = Color(0xFFC2F5DA)
val Green100 = Color(0xFFD0FBE9)
val Green50 = Color(0xFFE0FAEC)

Color Scheme Structure

The Colors data class defines semantic color tokens:
@Immutable
data class Colors(
    val primary: Color,           // Primary brand color
    val onPrimary: Color,         // Text/icons on primary
    val secondary: Color,         // Secondary brand color
    val onSecondary: Color,       // Text/icons on secondary
    val tertiary: Color,          // Accent color
    val onTertiary: Color,        // Text/icons on tertiary
    val error: Color,             // Error state color
    val onError: Color,           // Text/icons on error
    val success: Color,           // Success state color
    val onSuccess: Color,         // Text/icons on success
    val disabled: Color,          // Disabled background
    val onDisabled: Color,        // Disabled content
    val surface: Color,           // Surface backgrounds
    val onSurface: Color,         // Text/icons on surface
    val background: Color,        // App background
    val onBackground: Color,      // Text/icons on background
    val outline: Color,           // Borders and dividers
    val text: Color,              // Primary text
    val textSecondary: Color,     // Secondary text
    val textDisabled: Color,      // Disabled text
    val scrim: Color,             // Overlay backgrounds
    val elevation: Color,         // Shadow color
)

Light Theme Colors

internal val LightColors = Colors(
    primary = Black,
    onPrimary = White,
    secondary = Gray400,
    onSecondary = Black,
    tertiary = Blue900,
    onTertiary = White,
    surface = Gray200,
    onSurface = Black,
    error = Red600,
    onError = White,
    success = Green600,
    onSuccess = White,
    disabled = Gray100,
    onDisabled = Gray500,
    background = White,
    onBackground = Black,
    outline = Gray300,
    text = Black,
    textSecondary = Gray700,
    textDisabled = Gray400,
    scrim = Color.Black.copy(alpha = 0.32f),
    elevation = Gray700,
)

Dark Theme Colors

internal val DarkColors = Colors(
    primary = White,
    onPrimary = Black,
    secondary = Gray400,
    onSecondary = White,
    tertiary = Blue300,
    onTertiary = Black,
    surface = Gray900,
    onSurface = White,
    error = Red400,
    onError = Black,
    success = Green700,
    onSuccess = Black,
    disabled = Gray700,
    onDisabled = Gray500,
    background = Black,
    onBackground = White,
    outline = Gray800,
    text = White,
    textSecondary = Gray300,
    textDisabled = Gray600,
    scrim = Color.Black.copy(alpha = 0.72f),
    elevation = Gray200,
)

Accessing Theme Colors

import com.nomanr.lumo.ui.AppTheme

@Composable
fun MyComponent() {
    // Access colors from theme
    val backgroundColor = AppTheme.colors.background
    val primaryColor = AppTheme.colors.primary
    val textColor = AppTheme.colors.text

    Surface(
        color = backgroundColor
    ) {
        Text(
            text = "Themed Text",
            color = textColor
        )
        Button(
            variant = ButtonVariant.Primary,
            onClick = { }
        ) {
            // Button uses AppTheme.colors.primary automatically
        }
    }
}

Creating Custom Color Schemes

1

Define Custom Colors

Create your brand color palette:
MyColors.kt
package com.example.yourapp.ui.theme

import androidx.compose.ui.graphics.Color

// Brand colors
val BrandPurple = Color(0xFF6200EE)
val BrandPurpleLight = Color(0xFFBB86FC)
val BrandTeal = Color(0xFF03DAC6)
val BrandTealDark = Color(0xFF018786)
2

Create Custom Color Scheme

Build your own Colors instance:
MyTheme.kt
import com.nomanr.lumo.ui.Colors

val MyLightColors = Colors(
    primary = BrandPurple,
    onPrimary = Color.White,
    secondary = BrandTeal,
    onSecondary = Color.Black,
    tertiary = BrandPurpleLight,
    onTertiary = Color.Black,
    surface = Color(0xFFF5F5F5),
    onSurface = Color(0xFF1C1B1F),
    error = Red600,
    onError = Color.White,
    success = Green600,
    onSuccess = Color.White,
    disabled = Gray100,
    onDisabled = Gray500,
    background = Color.White,
    onBackground = Color.Black,
    outline = Gray300,
    text = Color(0xFF1C1B1F),
    textSecondary = Gray700,
    textDisabled = Gray400,
    scrim = Color.Black.copy(alpha = 0.32f),
    elevation = Gray700,
)

val MyDarkColors = Colors(
    primary = BrandPurpleLight,
    onPrimary = Color.Black,
    secondary = BrandTeal,
    onSecondary = Color.Black,
    tertiary = BrandPurple,
    onTertiary = Color.White,
    surface = Color(0xFF1C1B1F),
    onSurface = Color(0xFFE6E1E5),
    error = Red400,
    onError = Color.Black,
    success = Green700,
    onSuccess = Color.Black,
    disabled = Gray700,
    onDisabled = Gray500,
    background = Color(0xFF121212),
    onBackground = Color.White,
    outline = Gray700,
    text = Color.White,
    textSecondary = Gray300,
    textDisabled = Gray600,
    scrim = Color.Black.copy(alpha = 0.72f),
    elevation = Color.White.copy(alpha = 0.05f),
)
3

Apply Custom Theme

Use CompositionLocalProvider to override the default colors:
import androidx.compose.runtime.CompositionLocalProvider
import com.nomanr.lumo.ui.AppTheme
import com.nomanr.lumo.ui.LocalColors

@Composable
fun MyApp() {
    val colors = if (isSystemInDarkTheme()) MyDarkColors else MyLightColors

    CompositionLocalProvider(
        LocalColors provides colors
    ) {
        AppTheme {
            // Your app content
        }
    }
}

Typography System

Default Typography Scale

Lumo UI provides a comprehensive type scale:
Typography.kt
data class Typography(
    val h1: TextStyle,      // 24sp, Bold - Page titles
    val h2: TextStyle,      // 20sp, Bold - Section headers
    val h3: TextStyle,      // 16sp, Bold - Subsection headers
    val h4: TextStyle,      // 16sp, SemiBold - Card titles
    val body1: TextStyle,   // 16sp, Normal - Primary body text
    val body2: TextStyle,   // 14sp, Normal - Secondary body text
    val body3: TextStyle,   // 12sp, Normal - Captions
    val label1: TextStyle,  // 14sp, Medium - Large labels
    val label2: TextStyle,  // 12sp, Medium - Small labels
    val label3: TextStyle,  // 10sp, Medium - Tiny labels
    val button: TextStyle,  // 14sp, Medium - Button text
    val input: TextStyle,   // 16sp, Normal - Input text
)

Typography Specifications

h1 = TextStyle(
    fontWeight = FontWeight.Bold,
    fontSize = 24.sp,
    lineHeight = 32.sp,
    letterSpacing = 0.sp,
)

h2 = TextStyle(
    fontWeight = FontWeight.Bold,
    fontSize = 20.sp,
    lineHeight = 28.sp,
    letterSpacing = 0.sp,
)

h3 = TextStyle(
    fontWeight = FontWeight.Bold,
    fontSize = 16.sp,
    lineHeight = 24.sp,
    letterSpacing = 0.sp,
)

h4 = TextStyle(
    fontWeight = FontWeight.SemiBold,
    fontSize = 16.sp,
    lineHeight = 24.sp,
    letterSpacing = 0.sp,
)

Using Typography

import com.nomanr.lumo.ui.AppTheme
import com.nomanr.lumo.ui.components.Text

@Composable
fun TypographyExample() {
    Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
        Text(
            text = "Heading 1",
            style = AppTheme.typography.h1
        )
        Text(
            text = "Heading 2",
            style = AppTheme.typography.h2
        )
        Text(
            text = "Body text for content",
            style = AppTheme.typography.body1
        )
        Text(
            text = "Secondary body text",
            style = AppTheme.typography.body2
        )
        Text(
            text = "Caption text",
            style = AppTheme.typography.body3
        )
    }
}

Custom Font Families

To use custom fonts, override the fontFamily() function:
1

Add Font Resources

Add your font files to src/main/res/font/:
res/
└── font/
    ├── inter_regular.ttf
    ├── inter_medium.ttf
    ├── inter_semibold.ttf
    └── inter_bold.ttf
2

Define Font Family

MyFonts.kt
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight

// Android
val InterFontFamily = FontFamily(
    Font(R.font.inter_regular, FontWeight.Normal),
    Font(R.font.inter_medium, FontWeight.Medium),
    Font(R.font.inter_semibold, FontWeight.SemiBold),
    Font(R.font.inter_bold, FontWeight.Bold),
)

// Multiplatform
val InterFontFamily = FontFamily(
    Font(Res.font.inter_regular, FontWeight.Normal),
    Font(Res.font.inter_medium, FontWeight.Medium),
    Font(Res.font.inter_semibold, FontWeight.SemiBold),
    Font(Res.font.inter_bold, FontWeight.Bold),
)
3

Override Typography Provider

import androidx.compose.runtime.CompositionLocalProvider
import com.nomanr.lumo.ui.LocalTypography
import com.nomanr.lumo.ui.provideTypography

@Composable
fun MyApp() {
    // Create typography with custom font
    val customTypography = provideTypography().copy(
        h1 = provideTypography().h1.copy(fontFamily = InterFontFamily),
        h2 = provideTypography().h2.copy(fontFamily = InterFontFamily),
        h3 = provideTypography().h3.copy(fontFamily = InterFontFamily),
        h4 = provideTypography().h4.copy(fontFamily = InterFontFamily),
        body1 = provideTypography().body1.copy(fontFamily = InterFontFamily),
        body2 = provideTypography().body2.copy(fontFamily = InterFontFamily),
        body3 = provideTypography().body3.copy(fontFamily = InterFontFamily),
        label1 = provideTypography().label1.copy(fontFamily = InterFontFamily),
        label2 = provideTypography().label2.copy(fontFamily = InterFontFamily),
        label3 = provideTypography().label3.copy(fontFamily = InterFontFamily),
        button = provideTypography().button.copy(fontFamily = InterFontFamily),
        input = provideTypography().input.copy(fontFamily = InterFontFamily),
    )

    CompositionLocalProvider(
        LocalTypography provides customTypography
    ) {
        AppTheme {
            // Your app content
        }
    }
}

Shapes System

Shapes define corner radius and border styles across components.

Default Component Shapes

// Buttons
val ButtonShape = RoundedCornerShape(12.dp)

// Cards
val CardShape = RoundedCornerShape(12.dp)

// Text Fields
val TextFieldShape = RoundedCornerShape(8.dp)

// Chips
val ChipShape = RoundedCornerShape(16.dp)

Customizing Shapes

import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.shape.CutCornerShape

// Fully rounded
Button(
    text = "Rounded",
    // Override via component style
)

// Different corners
Card(
    shape = RoundedCornerShape(
        topStart = 16.dp,
        topEnd = 16.dp,
        bottomStart = 0.dp,
        bottomEnd = 0.dp
    )
) { }

// Cut corners
Card(
    shape = CutCornerShape(8.dp)
) { }

Dark Mode Support

Automatic Dark Mode

import androidx.compose.foundation.isSystemInDarkTheme

@Composable
fun App() {
    AppTheme(
        isDarkTheme = isSystemInDarkTheme()
    ) {
        // Content automatically adapts
    }
}

Manual Dark Mode Toggle

@Composable
fun App() {
    var isDark by remember { mutableStateOf(false) }

    AppTheme(
        isDarkTheme = isDark
    ) {
        Scaffold(
            topBar = {
                TopBar(
                    title = "My App",
                    actions = {
                        IconButton(
                            onClick = { isDark = !isDark }
                        ) {
                            Icon(
                                if (isDark) Icons.Default.LightMode 
                                else Icons.Default.DarkMode,
                                "Toggle theme"
                            )
                        }
                    }
                )
            }
        ) {
            // Content
        }
    }
}

Complete Custom Theme Example

MyAppTheme.kt
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.foundation.isSystemInDarkTheme
import com.nomanr.lumo.ui.AppTheme
import com.nomanr.lumo.ui.LocalColors
import com.nomanr.lumo.ui.LocalTypography

@Composable
fun MyAppTheme(
    isDarkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colors = if (isDarkTheme) MyDarkColors else MyLightColors
    val typography = MyTypography

    CompositionLocalProvider(
        LocalColors provides colors,
        LocalTypography provides typography,
    ) {
        AppTheme(isDarkTheme = isDarkTheme) {
            content()
        }
    }
}

Best Practices

Use Semantic Tokens

Always use AppTheme.colors.primary instead of hardcoded Color(0xFF...)

Test Both Themes

Verify your UI works in both light and dark modes

Consistent Spacing

Use multiples of 4dp or 8dp for spacing

Accessibility

Ensure color contrast ratios meet WCAG standards

Next Steps

Customize Components

Learn how to customize individual components

Component Reference

Explore all components using your theme

Build docs developers (and LLMs) love