Skip to Content
ArchitectureDesign-system conventions

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.type are theme-aware (resolved from the current OterTheme, 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.zIndex are constant scales.

Hard rules

  1. No hardcoded Color(0x…) in ui/screens/** or *_presentation/**. Use Oter.colors.*. (Category/feature accent colors that aren’t tokens belong in one mapping helper, not inline.)
  2. No magic .dp for spacing/radius. Use Oter.spacing.* / Oter.radius.*. The only allowed raw dp are hairlines (1.dp borders) and 0.dp.
  3. No raw MaterialTheme.typography.*. Use Oter.type.*.
  4. Compose gap over per-child margins: Arrangement.spacedBy(Oter.spacing.md).
  5. Tabular numerics for any time/balance/count/duration display.
  6. 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/LCColors typealiases (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 as geistFontFamily() / lexendFontFamily() in ui/Font.kt.
  • Wire the family into OterTheme(fontFamily = …) at the platform entry point (desktop Main.kt, iOS IOSApp.kt, Android MainActivity).

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.