Theming
How a screen reads the theme's colours, type, gaps and corners, how a brand gives its own look in one preset, and how a part of a screen looks different. Nothing in a screen names a colour or a size.
What the theme holds
PantrixTheme.colors | The colour roles: primary.color, surface, textSecondary, divider |
PantrixTheme.typography | The type scale: title, body, caption |
PantrixTheme.spacing | The gaps: xs 4, sm 8, md 12, lg 16, xl 24, xxl 32dp, scaled by the density |
PantrixTheme.shapes | The corners: xs 2, sm 4, md 8, lg 12, xl 16dp, and none and full |
PantrixTheme.motion | The durations, fast 100, normal 200 and slow 350ms, nought where the system reduces motion |
PantrixTheme.style(ButtonStyle) | A component's style as it holds at that place |
Summary.kt
@Composable
fun Summary(title: String, detail: String) {
PantrixSurface(border = PantrixTheme.colors.border, shape = PantrixTheme.shapes.lg) {
Column(
modifier = Modifier.padding(PantrixTheme.spacing.lg),
verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.xs)
) {
PantrixText(text = title, style = PantrixTheme.typography.title)
PantrixText(text = detail, color = PantrixTheme.colors.textSecondary)
}
}
}
Colours by their job
A component asks for a colour by its job, never by its name: the light and the dark theme change the colour, not the code.
- Roles
primaryis the brand's: the main action, what is chosen.success,info,warninganddangersay a status,neutraldraws no eye. Each hascolor,container, andonColorandonContainerfor what is written on them.- Grounds and text
backgroundis the app's backdrop,surfacea card's or a sheet's ground. Text istextPrimary,textSecondary,textDisabledandtextInverse.- Lines
borderis a box's soft edge,borderStrongthe edge of a control that is only its edge,dividerthe line between items.- Content colour
- A fill tells what is written on it which colour to take: a text or an icon given no colour takes it.
Type
Nine styles: display, headline and title for headings; callout, body, label, footnote and caption for text; code for monospace. Text given no style is label. A style is a look, not a role: a heading is one to a screen reader with role = TextRole.Heading(level). The sizes are in sp, so text grows with the system's font size: Typography.
Name the brand's typeface
BrandType.ktval Brand = PantrixTypeface( PantrixFont(Res.font.brand_regular), PantrixFont(Res.font.brand_bold, FontWeight.Bold) )Give it to the theme
App.ktPantrixTheme(typography = rememberPantrixTypography(Brand)) { App() }rememberPantrixTypography(Brand, codeTypeface = BrandMono)sets code in a face of its own.
A brand's look in one preset
A DesignPreset holds a brand's colours for light and dark, its type, its shapes and its components' overrides. Its colours are written once, here: a Color(0xFF…) belongs in a preset, never in a screen. The theme builder writes one for you.
Harbor.kt
object Harbor : DesignPreset {
override val name: String = "Harbor"
override fun colors(dark: Boolean): PantrixColors {
val teal = PantrixPalette.Teal
return if (dark) {
PantrixColors.Dark.copy(
primary = PantrixColor(
color = teal.shade400,
onColor = teal.shade950,
container = teal.shade900,
onContainer = teal.shade100
)
)
} else {
PantrixColors.Light.copy(
primary = PantrixColor(
color = teal.shade700,
onColor = Color.White,
container = teal.shade100,
onContainer = teal.shade900
)
)
}
}
@Composable
override fun typography(): PantrixTypography = rememberPantrixTypography()
@Composable
override fun shapes(): PantrixShapes = remember { PantrixShapes(md = RoundedCornerShape(4.dp)) }
}
PantrixTheme(preset = Harbor) { App() }
Light and dark
The theme follows the system's dark mode unless it is told. An app with a switch of its own gives the reader's choice, and the system's bars, keyboard and controls follow it on Android and iOS: System bars that follow the theme.
App.kt
PantrixTheme(preset = Harbor, colors = rememberPantrixColors(Harbor, darkTheme = settings.dark)) { App() }
A part that looks different
A PantrixTheme inside another changes only what it is given and takes the rest from the outer one.
- A dark part
PantrixTheme(darkTheme = true) { … }in a light screen. Its ground is the surface or the card inside it: a theme draws none.- A compact table
PantrixTheme(density = PantrixDensity.Compact) { … }. The touch target does not shrink.- Another language
PantrixTheme(locale = PantrixLocale.Arabic) { … }speaks Arabic and reads from right to left.- Another platform
PantrixTheme(platform = PantrixPlatform.forType(PlatformType.Ios)) { … }draws iOS's look anywhere.
Component styles
A component's measures and form are its style, with a preset on each platform. On top of it come the brand's overrides, the theme's styles, a section's and, last, the component's own style. A style names only what it changes.
StyledApp.kt
@Composable
fun StyledApp(content: @Composable () -> Unit) {
val styles = remember {
pantrixStyles(
ButtonStyle.override(
all = ButtonStyle(shape = ShapeRole.Small),
ios = ButtonStyle(shape = ShapeRole.Large)
)
)
}
PantrixTheme(styles = styles, content = content)
}
Where next
- Platforms: Each platform's entry point and what differs.
- Forms: Fields, rules and a form's submission.
- Typography: The scale and the size of what is typed.
- The Material 3 bridge: Two themes that speak the same colours.