This document provides detailed information about the theming system in the Summon library. The theme system provides consistent colors, typography, spacing, and visual design elements across your application.
- Theme System Overview
- Color System
- Typography
- Spacing System
- Theme Configuration
- Predefined Themes
- Theme Extensions
- Usage Examples
The Summon theme system consists of several interconnected components:
- ColorSystem: Manages color palettes with light/dark mode support
- Typography: Defines text styles and type scales
- Spacing: Provides consistent spacing values
- Theme: Orchestrates all theme elements together
object Theme {
data class ThemeConfig(
val colorPalette: ColorSystem.ColorPalette = ColorSystem.default,
val typography: Map<String, TextStyle> = emptyMap(),
val spacing: Map<String, String> = emptyMap(),
val borderRadius: Map<String, String> = emptyMap(),
val elevation: Map<String, String> = emptyMap(),
val customValues: Map<String, String> = emptyMap(),
val typographyTheme: TypographyTheme = TypographyTheme(),
val spacingTheme: SpacingTheme = SpacingTheme(),
val borderRadiusTheme: BorderRadiusTheme = BorderRadiusTheme(),
val elevationTheme: ElevationTheme = ElevationTheme()
)
}The ColorSystem provides semantic color names with automatic light/dark mode support.
object ColorSystem {
enum class ThemeMode { LIGHT, DARK, SYSTEM }
data class ColorPalette(
val light: Map<String, String>,
val dark: Map<String, String>
) {
fun forMode(themeMode: ThemeMode): Map<String, String>
}
}The default color palette includes these semantic colors:
Backgrounds:
background- Primary background colorsurface- Surface/card backgroundssurfaceVariant- Alternative surface color
Text:
onBackground- Text on backgroundonSurface- Text on surfaceonSurfaceVariant- Secondary text on surfacedisabled- Disabled text color
Brand Colors:
primary- Primary brand colorprimaryVariant- Primary color variantonPrimary- Text on primary colorsecondary- Secondary brand coloronSecondary- Text on secondary color
Status Colors:
error- Error stateswarning- Warning statessuccess- Success statesinfo- Information states
// Get colors
val primaryColor = Theme.getColor("primary")
val backgroundColor = Theme.getColor("background", ColorSystem.ThemeMode.DARK)
// Apply to modifiers
modifier.themeColor("primary")
modifier.themeBackgroundColor("surface")
modifier.themeStyleBorder("1px", "solid", "primary")
// Set theme mode
ColorSystem.setThemeMode(ColorSystem.ThemeMode.DARK)object ColorSystem {
val default: ColorPalette // Default blue-based palette
val blue: ColorPalette // Blue theme
val green: ColorPalette // Green theme
val purple: ColorPalette // Purple theme
}The typography system provides consistent text styles with both string-based and type-safe properties.
data class TextStyle(
// String-based properties (backward compatibility)
val fontFamily: String? = null,
val fontSize: String? = null,
val fontWeight: String? = null,
val fontStyle: String? = null,
val color: String? = null,
val textDecoration: String? = null,
val lineHeight: String? = null,
val letterSpacing: String? = null,
// Type-safe properties
val fontWeightEnum: FontWeight? = null,
val fontSizeNumber: Number? = null,
val fontSizeUnit: String? = null,
val lineHeightNumber: Number? = null,
val letterSpacingNumber: Number? = null,
val letterSpacingUnit: String? = null,
val colorValue: Color? = null
) {
companion object {
fun create(
fontFamily: String? = null,
fontSize: Number? = null,
fontSizeUnit: String? = "rem",
fontWeight: FontWeight? = null,
fontStyle: String? = null,
color: Color? = null,
textDecoration: String? = null,
lineHeight: Number? = null,
letterSpacing: Number? = null,
letterSpacingUnit: String? = "em"
): TextStyle
}
}data class TypographyTheme(
val h1: TextStyle = TextStyle.create(fontSize = 2.5, fontWeight = FontWeight.Bold),
val h2: TextStyle = TextStyle.create(fontSize = 2.0, fontWeight = FontWeight.Bold),
val h3: TextStyle = TextStyle.create(fontSize = 1.75, fontWeight = FontWeight.Bold),
val h4: TextStyle = TextStyle.create(fontSize = 1.5, fontWeight = FontWeight.Bold),
val h5: TextStyle = TextStyle.create(fontSize = 1.25, fontWeight = FontWeight.Bold),
val h6: TextStyle = TextStyle.create(fontSize = 1.0, fontWeight = FontWeight.Bold),
val subtitle: TextStyle = TextStyle.create(fontSize = 1.1, fontWeight = FontWeight.Medium),
val body: TextStyle = TextStyle.create(fontSize = 1.0, fontWeight = FontWeight.Normal),
val bodyLarge: TextStyle = TextStyle.create(fontSize = 1.1, fontWeight = FontWeight.Normal),
val bodySmall: TextStyle = TextStyle.create(fontSize = 0.9, fontWeight = FontWeight.Normal),
val caption: TextStyle = TextStyle.create(fontSize = 0.8, fontWeight = FontWeight.Normal),
val button: TextStyle = TextStyle.create(fontSize = 1.0, fontWeight = FontWeight.Medium),
val overline: TextStyle = TextStyle.create(fontSize = 0.75, fontWeight = FontWeight.SemiBold, letterSpacing = 0.05),
val link: TextStyle = TextStyle.create(fontSize = 1.0, fontWeight = FontWeight.Normal, textDecoration = "underline"),
val code: TextStyle = TextStyle.create(fontSize = 0.9, fontFamily = "monospace")
)// Get text styles
val headingStyle = Theme.getTextStyle("h1")
val bodyStyle = Theme.getTextStyle("body")
// Apply to modifiers
modifier.themeTextStyle("h2")
// Access typed typography theme
val typography = Theme.getTypographyTheme()
val h1Style = typography.h1The Spacing system provides consistent spacing values based on a 4px base unit.
object Spacing {
private const val BASE_UNIT = 4
const val xs = "4px" // BASE_UNIT
const val sm = "8px" // BASE_UNIT * 2
const val md = "16px" // BASE_UNIT * 4 (default)
const val lg = "24px" // BASE_UNIT * 6
const val xl = "32px" // BASE_UNIT * 8
const val xxl = "48px" // BASE_UNIT * 12
fun custom(multiplier: Int): String = "${BASE_UNIT * multiplier}px"
fun padding(top: String = "0", right: String = "0", bottom: String = "0", left: String = "0"): String
fun padding(vertical: String = "0", horizontal: String = "0"): String
fun margin(top: String = "0", right: String = "0", bottom: String = "0", left: String = "0"): String
fun margin(vertical: String = "0", horizontal: String = "0"): String
}data class SpacingTheme(
val xs: String = Spacing.xs, // 4px
val sm: String = Spacing.sm, // 8px
val md: String = Spacing.md, // 16px
val lg: String = Spacing.lg, // 24px
val xl: String = Spacing.xl, // 32px
val xxl: String = Spacing.xxl // 48px
)// Spacer components
@Composable
fun Spacer(modifier: Modifier)
// Convenience spacers
@Composable
fun HorizontalSpacerXS()
@Composable
fun HorizontalSpacerS()
@Composable
fun HorizontalSpacerM()
@Composable
fun VerticalSpacerXS()
@Composable
fun VerticalSpacerS()
@Composable
fun VerticalSpacerM()
// Spacing utilities
@Composable
fun Spacing.verticalSpace(size: String = md)
@Composable
fun Spacing.horizontalSpace(size: String = md)// Direct spacing values
val smallPadding = Spacing.sm
val customSpacing = Spacing.custom(3) // 12px
// Spacing utilities
val padding = Spacing.padding("8px", "16px")
val margin = Spacing.margin(vertical = "12px", horizontal = "24px")
// Apply to modifiers
modifier.themePadding("md")
modifier.themeMargin("lg")
modifier.spacingPadding(Spacing.xl)
// Spacer components
Spacer(Modifier().height(Spacing.md))
VerticalSpacerM()data class BorderRadiusTheme(
val none: String = "0",
val sm: String = "4px",
val md: String = "8px",
val lg: String = "16px",
val xl: String = "24px",
val pill: String = "9999px",
val circle: String = "50%"
)
data class ElevationTheme(
val none: String = "none",
val xs: String = "0 1px 2px rgba(0, 0, 0, 0.05)",
val sm: String = "0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06)",
val md: String = "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
val lg: String = "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)",
val xl: String = "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
val xxl: String = "0 25px 50px -12px rgba(0, 0, 0, 0.25)"
)// Set active theme
Theme.setTheme(Theme.Themes.dark)
// Get current theme
val currentTheme = Theme.getTheme()
// Access theme values
val primaryColor = Theme.getColor("primary")
val headingStyle = Theme.getTextStyle("h1")
val mediumSpacing = Theme.getSpacing("md")
val roundedCorners = Theme.getBorderRadius("md")
val cardElevation = Theme.getElevation("sm")
val customValue = Theme.getCustomValue("myCustomProperty", "defaultValue")object Theme.Themes {
val light: ThemeConfig // Default light theme
val dark: ThemeConfig // Default dark theme
val blue: ThemeConfig // Blue color theme
val green: ThemeConfig // Green color theme
val purple: ThemeConfig // Purple color theme
}// Switch to dark theme
Theme.setTheme(Theme.Themes.dark)
// Switch to colored theme
Theme.setTheme(Theme.Themes.green)
// Use system preference
ColorSystem.setThemeMode(ColorSystem.ThemeMode.SYSTEM)The theme system provides convenient modifier extensions:
// Color extensions
fun Modifier.themeColor(colorName: String, themeMode: ColorSystem.ThemeMode = ColorSystem.getThemeMode()): Modifier
fun Modifier.themeBackgroundColor(colorName: String, themeMode: ColorSystem.ThemeMode = ColorSystem.getThemeMode()): Modifier
fun Modifier.themeStyleBorder(width: String, style: String, colorName: String, themeMode: ColorSystem.ThemeMode = ColorSystem.getThemeMode()): Modifier
// Typography extensions
fun Modifier.themeTextStyle(styleName: String): Modifier
// Spacing extensions
fun Modifier.themePadding(spacingName: String): Modifier
fun Modifier.themeMargin(spacingName: String): Modifier
fun Modifier.themePadding(top: String? = null, right: String? = null, bottom: String? = null, left: String? = null): Modifier
fun Modifier.themeMargin(top: String? = null, right: String? = null, bottom: String? = null, left: String? = null): Modifier
// Other extensions
fun Modifier.themeBorderRadius(radiusName: String): Modifier
fun Modifier.themeElevation(elevationName: String): Modifier
// Spacing modifier extensions
fun Modifier.spacingPadding(value: String): Modifier
fun Modifier.spacingPadding(top: String, right: String, bottom: String, left: String): Modifier
fun Modifier.spacingPaddingHorizontal(value: String): Modifier
fun Modifier.spacingPaddingVertical(value: String): Modifier
fun Modifier.spacingMargin(value: String): Modifier
fun Modifier.spacingMarginHorizontal(value: String): Modifier
fun Modifier.spacingMarginVertical(value: String): Modifier// Create custom theme extending existing theme
val customTheme = Theme.createTheme(Theme.Themes.light) {
copy(
customValues = customValues + ("brandColor" to "#FF6B35"),
typography = typography + ("brandHeading" to TextStyle.create(
fontSize = 2.2,
fontWeight = FontWeight.Bold,
color = Color.hex("#FF6B35")
))
)
}
// Apply custom theme
Theme.setTheme(customTheme)@Composable
fun ThemedApp() {
// Set up theme
Theme.setTheme(Theme.Themes.dark)
ColorSystem.setThemeMode(ColorSystem.ThemeMode.SYSTEM)
// Use themed components
Column(
modifier = Modifier()
.themeBackgroundColor("background")
.themePadding("lg")
) {
Text(
text = "Welcome",
modifier = Modifier()
.themeTextStyle("h1")
.themeColor("onBackground")
)
VerticalSpacerM()
Button(
onClick = { },
label = "Get Started",
modifier = Modifier()
.themeBackgroundColor("primary")
.themeColor("onPrimary")
.themeBorderRadius("md")
.themeElevation("sm")
)
}
}@Composable
fun ResponsiveThemedComponent() {
val typography = Theme.getTypographyTheme()
val spacing = Theme.getSpacingTheme()
Column {
Text(
text = "Heading",
modifier = Modifier().themeTextStyle("h2")
)
Spacer(Modifier().height(spacing.md))
Text(
text = "Body text with theme-aware styling",
modifier = Modifier()
.themeTextStyle("body")
.spacingPaddingHorizontal(spacing.sm)
)
}
}@Composable
fun ThemeSwitcher() {
var isDark by remember { mutableStateOf(false) }
Button(
onClick = {
isDark = !isDark
val newTheme = if (isDark) Theme.Themes.dark else Theme.Themes.light
Theme.setTheme(newTheme)
},
label = if (isDark) "Light Mode" else "Dark Mode",
modifier = Modifier()
.themeBackgroundColor("primary")
.themeColor("onPrimary")
)
}The Theme Variable Injection system enables instant theme switching without requiring a component tree re-render. Theme values are converted to CSS custom properties and injected into :root.
Platform-specific object for injecting CSS variables into the document root:
expect object ThemeVariableInjector {
fun injectVariables(variables: Map<String, String>)
fun getVariable(name: String): String
fun removeVariable(name: String)
fun clearVariables()
}injectVariables(variables)- Injects all theme CSS variables into document rootgetVariable(name)- Gets a CSS variable value (with--prefix)removeVariable(name)- Removes a CSS variableclearVariables()- Clears all theme CSS variables
Generates CSS variable maps from ThemeConfig:
object ThemeVariableGenerator {
fun generateVariables(config: Theme.ThemeConfig): Map<String, String>
}Generated variables include:
--colors-*- Color palette values--typography-*-*- Typography properties (font-size, font-weight, etc.)--spacing-*- Spacing values (xs, sm, md, lg, xl, xxl)--border-radius-*- Border radius values--shadow-*- Elevation/shadow values--custom-*- Custom theme values
// Apply theme variables from a reactive state
@Composable
fun ApplyThemeVariables(themeState: SummonMutableState<Theme.ThemeConfig>)
// Apply current global theme variables
@Composable
fun ApplyCurrentThemeVariables()@Composable
fun App() {
val themeState = remember { mutableStateOf(Theme.Themes.light) }
// Apply theme variables - updates CSS whenever themeState changes
ApplyThemeVariables(themeState)
Column {
// Use CSS variables in your styles
Text(
text = "Hello",
modifier = Modifier().style("color", "var(--colors-primary)")
)
// Theme switcher
Button(onClick = {
themeState.value = if (themeState.value == Theme.Themes.light)
Theme.Themes.dark else Theme.Themes.light
}) {
Text("Toggle Theme")
}
}
}Once injected, theme variables can be used in CSS:
.my-component {
color: var(--colors-primary);
padding: var(--spacing-md);
border-radius: var(--border-radius-md);
box-shadow: var(--shadow-sm);
font-size: var(--typography-body-font-size);
}- Use semantic color names: Prefer
"primary"over hex colors for consistency - Leverage spacing system: Use predefined spacing values for visual harmony
- Apply theme extensions: Use modifier extensions for clean, readable code
- Support theme modes: Design for both light and dark modes
- Create custom themes sparingly: Extend existing themes rather than creating from scratch
- Test theme switching: Ensure your UI works with all theme modes
- Use typed properties: Prefer type-safe TextStyle.create() over string-based styles
When upgrading theme usage:
- Replace hardcoded colors with semantic theme colors
- Update spacing to use the consistent spacing system
- Use theme modifier extensions for cleaner code
- Test with different theme modes (light/dark/system)
- Components API - Using themes with UI components
- Modifier API - Styling system that integrates with themes
- Color API - Color system and utilities
- State API - Managing theme state in your application