Design System Conventions
How to consume the Oter design system in Compose code. The visual source of truth is the prototype set (
DesignSystem.md+ds/colors_and_type.css+ the per-feature*.jsx/*.css). The token values are already mirrored into Kotlin.
The one accessor: Oter
Read all design values through the Oter accessor
(shared/.../ui/tokens/OterTokens.kt):
import com.esteban.ruano.oter.ui.tokens.Oter
Text(
"Balance",
style = Oter.type.h2, // typography
color = Oter.colors.textPrimary, // theme-aware color (dark/light)
modifier = Modifier.padding(Oter.spacing.md),
)
Surface(
shape = Oter.radius.shapeLg, // 12dp card radius
color = Oter.colors.surface,
border = BorderStroke(1.dp, Oter.colors.border),
elevation = Oter.elevation.sm,
) { /* … */ }Oter.colors/Oter.typeare theme-aware (resolved from the currentOterTheme, dark or light).Oter.spacing(4/8/16/24/32/64),Oter.radius(sm 6 / md 8 / lg 12 / xl 16 / full),Oter.elevation(sm–xl),Oter.motion(fast/base/slow + standard easing),Oter.zIndexare constant scales.
Hard rules
- No hardcoded
Color(0x…)inui/screens/**or*_presentation/**. UseOter.colors.*. (Category/feature accent colors that aren’t tokens belong in one mapping helper, not inline.) - No magic
.dpfor spacing/radius. UseOter.spacing.*/Oter.radius.*. The only allowed raw dp are hairlines (1.dpborders) and0.dp. - No raw
MaterialTheme.typography.*. UseOter.type.*. - Compose
gapover per-child margins:Arrangement.spacedBy(Oter.spacing.md). - Tabular numerics for any time/balance/count/duration display.
- Dark-first: build and review on dark, verify the light inversion second.
These will be enforced by custom detekt rules + a CI grep guard (report-only first, error once screens are migrated).
Deprecated layers — do not use in new code
The following predate the token system and cause drift. They are kept only for not-yet-migrated call sites and will be removed:
OterColors,OterGradients(ui/Color.kt)OterDimensions(ui/Dimension.kt)OterShapes(ui/Shape.kt)OterDesignSystem+LCDS/LCColorstypealiases (ui/DesignSystem.kt)
Genuine helpers (getGreeting, getCategoryColor, createGradientBrush) remain
but should resolve their values from Oter.colors.
Fonts
- Desktop → Geist, Mobile (Android/iOS/Compose) → Lexend (per the design
system). Both are bundled in
shared/src/commonMain/composeResources/font/and exposed asgeistFontFamily()/lexendFontFamily()inui/Font.kt. - Wire the family into
OterTheme(fontFamily = …)at the platform entry point (desktopMain.kt, iOSIOSApp.kt, AndroidMainActivity).
Building screens
Compose every screen from the shared primitive kit in
shared/.../ui/components/primitives/ (OterSectionCard, OterSectionHeader,
OterListRow, OterStatCard, OterEmptyState, OterChip, OterSegmentedControl,
OterFab, OterProgressBar/OterProgressRing, OterBottomSheet, OterSearchField,
and the OterPageScaffold / OterMobileScreenScaffold shells). Do not rebuild
cards/headers/rows inline. Match the corresponding prototype *-app.jsx/*-styles.css.