Skip to content

design system

this document is the practical reference for the shared design tokens and widgets used across know that you are breathing. it is intended for developers adding or modifying screens.

summary

the design system is built on Material 3, scoped through a single AppTheme builder that generates a full ThemeData from one accent color. all spacing, sizing, typography, and motion values live as named tokens under lib/core/theming/, and shared widgets live under lib/core/widgets/.

design tokens

all raw numbers should be avoided in UI code. use these token classes instead.

  • AppSpacing — xxs (2) through xxxl (48). the default page/section padding is AppSpacing.lg.
  • AppRadius — sm (8) through pill (999). cards use lg, inputs use md, buttons use pill/StadiumBorder.
  • AppSizes — touch targets (minTouchTarget = 48), icon sizes (iconMd, iconLg, iconXl, iconXxl), and content max-widths (maxContentWidth = 720).
  • AppTypography — Manrope-based TextTheme with tabular figures for numerals and a ScaledTextScaler that layers the user font-scale preference on top of the platform scale.
  • AppDurations / AppCurves — fast (120 ms), medium (240 ms), slow (400 ms), and the ambient breath (6 s) cycle. MotionScope collapses these to zero when reduced motion is requested.

theme

AppTheme.light() and AppTheme.dark() are the only two theme builders. they accept a seedColor, visualDensity, and (for dark) an amoled flag. the router wraps the player and recitation-player routes in MeditationTheme, which applies a meditation's colorSeed through the same builder, so per-meditation themes keep every component default. screens that need a themed preview (the meditation editor previews the seed being edited) call MeditationTheme.themeForSeed(...) to get the same resolved ThemeData — do not build an ad-hoc ThemeData from ColorScheme.fromSeed, which silently drops every component override AppTheme installs.

global appearance preferences (fontScale, reduceMotion, compactLayout, trueBlackDarkMode) are managed by UiPreferences and exposed in SettingsScreen.

shared widgets

use these widgets instead of hand-building common patterns.

  • AppScaffold — standard Scaffold with app bar, AppSpacing.lg body padding, optional scroll view, centered max-width support, and a bottomNavigationBar slot for bottom action rows. the body is wrapped in SafeArea and a bottomNavigationBar gets an extra bottom-only SafeArea, so the android system navigation bar (enforced edge-to-edge since android 15) and the ios home indicator can never cover them. prefer it over a raw Scaffold; a bare Scaffold.bottomNavigationBar is not inset by the framework — if a screen cannot use AppScaffold, wrap the bar in SafeArea yourself.
  • AppSection — rounded surfaceContainerLow grouping with a title, optional subtitle, and optional tappable trailing. CollapsibleSection is the expandable variant used on dense settings screens.
  • AppIconButton — IconButton that guarantees a tooltip and a 48×48 touch target. use the AppIconButtonStyle variants for emphasis.
  • SoundDropdown — consistently styled dropdown for bells/soundscapes.
  • DurationPicker / CountPicker — wheel pickers built on NumberWheel for time and integer input.
  • AutoFitText — single-line text that shrinks to fit the available width, then falls back to a horizontal marquee if it cannot shrink below [minFontSize]. use this for any long label, especially on Wear OS where round screens have very little horizontal space.

buttons and controls

  • icon-only buttons are the default for any toolbar, list row, or compact editing control. use the shared AppIconButton widget whenever possible: it enforces a tooltip and a minimum 48×48 touch target. Material IconButton is acceptable if you explicitly set a tooltip and hit target.
  • text buttons (TextButton, ElevatedButton, FilledButton, OutlinedButton) should only be used when an icon alone would be ambiguous. dense editing screens such as list editors, the meditation editor, and the sound library are especially prone to accumulating text buttons; keep the UI icon-first.
  • when the primary action in a screen needs extra emphasis, use AppIconButtonStyle.filled or AppIconButtonStyle.filledTonal instead of a text label.
  • dialog actions are icon-only too. every AlertDialog/Dialog actions: list uses DialogCancelButton (dismissive, Icons.close, left) and DialogConfirmButton (affirmative, filled, right) from lib/core/widgets/dialog_action_buttons.dart. use Icons.check for save/ok/done and Icons.delete_outline for destructive confirms, passing the localized label as the tooltip.

text and icon conventions

  • all English UI source strings are lowercase and live in lib/l10n/app_en.arb. never hardcode user-facing text in a widget.
  • the test/l10n/app_en_arb_test.dart test enforces the all-lowercase rule for the English source.
  • every icon-only button must have a tooltip or Semantics.label so screen-reader users know what it does.

completion visualizer

PondRippleVisualizer shares the tree and wave visualizers' translucent primary/secondary layers, curved forms, and theme-derived highlights. a shaded water surface remains between drops, without a center dot or stationary rings; a highlighted teardrop lands with a small splash, followed by gently irregular ripple bands with shaded troughs and reflective crests. geometry scales with the canvas, including on wear, without changing the completion view's control layout. the nine-second cycle is deterministic and reduced motion holds the same mid-ripple still frame. painter delegates capture the color scheme so a theme change also repaints the still image. golden tests cover the falling drop, splash, spreading rings, and settled water in light/dark themes at phone and watch sizes.

accessibility

  • AppIconButton requires a tooltip and a minimum 48×48 hit area.
  • AppTypography uses a ScaledTextScaler so the user's font-scale choice composes with the platform scale.
  • MotionScope honors both the user's reduceMotion preference and MediaQueryData.disableAnimations.
  • all custom colors used as backgrounds must maintain contrast; use Color.computeLuminance() for simple light/dark checks when overlaying text/icons.

Wear OS

round Wear OS watches have very little horizontal margin but a large vertical canvas. wear screens should follow this pattern:

  • keep horizontal insets small (AppSpacing.sm). let the scrollable content use almost the full width of the circle.
  • put the top icon or title inside the scrollable body, not in a fixed AppBar or scaffold header. it should scroll with the list.
  • for the main menu, the first scrollable item is the app icon.
  • for other screens, the first scrollable item is the matching menu icon.
  • add generous top and bottom padding inside the scroll view (e.g. shortestSide * 0.4 at the bottom) so the first and last items can be scrolled into the round display's safe area.
  • list tiles should be full width. long labels should stay on one line using AutoFitText so they shrink and, if necessary, marquee instead of wrapping or clipping.
  • round-screen corners are allowed to clip the top and bottom items while scrolling. that is fine because the user can scroll them into the safe area.

adding a new screen

  1. wrap the route with AppScaffold unless it needs a custom layout.
  2. use AppSection to group related controls.
  3. use AppLocalizations for all text.
  4. take spacing from AppSpacing and radii from AppRadius.
  5. put bottom action rows in AppScaffold.bottomNavigationBar — it applies the system-nav-bar SafeArea for you. never hand a bare row to Scaffold.bottomNavigationBar.
  6. if the screen is the player or editor, wrap it with MeditationTheme to respect the meditation's color seed.