body1 = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.sp,
)
body2 = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.15.sp,
)
body3 = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 12.sp,
lineHeight = 16.sp,
letterSpacing = 0.15.sp,
)
Theme Architecture
TheAppTheme 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 inColor.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
TheColors 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
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)
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),
)
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
- Headings
- Body Text
- Labels & Controls
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,
)
label1 = TextStyle(
fontWeight = FontWeight.W500,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.1.sp,
)
label2 = TextStyle(
fontWeight = FontWeight.W500,
fontSize = 12.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp,
)
button = TextStyle(
fontWeight = FontWeight.W500,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 1.sp,
)
input = TextStyle(
fontWeight = FontWeight.Normal,
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 thefontFamily() function:
Add Font Resources
- Android
- Multiplatform
Add your font files to
src/main/res/font/:res/
└── font/
├── inter_regular.ttf
├── inter_medium.ttf
├── inter_semibold.ttf
└── inter_bold.ttf
Add fonts to
commonMain/composeResources/font/:commonMain/
└── composeResources/
└── font/
├── inter_regular.ttf
├── inter_medium.ttf
├── inter_semibold.ttf
└── inter_bold.ttf
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),
)
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