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) throughxxxl(48). the default page/section padding isAppSpacing.lg.AppRadius—sm(8) throughpill(999). cards uselg, inputs usemd, buttons usepill/StadiumBorder.AppSizes— touch targets (minTouchTarget= 48), icon sizes (iconMd,iconLg,iconXl,iconXxl), and content max-widths (maxContentWidth= 720).AppTypography— Manrope-basedTextThemewith tabular figures for numerals and aScaledTextScalerthat 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 ambientbreath(6 s) cycle.MotionScopecollapses 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— standardScaffoldwith app bar,AppSpacing.lgbody padding, optional scroll view, centered max-width support, and abottomNavigationBarslot for bottom action rows. the body is wrapped inSafeAreaand abottomNavigationBargets an extra bottom-onlySafeArea, 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 rawScaffold; a bareScaffold.bottomNavigationBaris not inset by the framework — if a screen cannot useAppScaffold, wrap the bar inSafeAreayourself.AppSection— roundedsurfaceContainerLowgrouping with a title, optional subtitle, and optional tappable trailing.CollapsibleSectionis the expandable variant used on dense settings screens.AppIconButton—IconButtonthat guarantees atooltipand a 48×48 touch target. use theAppIconButtonStylevariants for emphasis.SoundDropdown— consistently styled dropdown for bells/soundscapes.DurationPicker/CountPicker— wheel pickers built onNumberWheelfor 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
AppIconButtonwidget whenever possible: it enforces atooltipand a minimum 48×48 touch target. MaterialIconButtonis acceptable if you explicitly set atooltipand 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.filledorAppIconButtonStyle.filledTonalinstead of a text label. - dialog actions are icon-only too. every
AlertDialog/Dialogactions:list usesDialogCancelButton(dismissive,Icons.close, left) andDialogConfirmButton(affirmative, filled, right) fromlib/core/widgets/dialog_action_buttons.dart. useIcons.checkfor save/ok/done andIcons.delete_outlinefor 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.darttest enforces the all-lowercase rule for the English source. - every icon-only button must have a
tooltiporSemantics.labelso 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
AppIconButtonrequires a tooltip and a minimum 48×48 hit area.AppTypographyuses aScaledTextScalerso the user's font-scale choice composes with the platform scale.MotionScopehonors both the user'sreduceMotionpreference andMediaQueryData.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
AppBaror 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.4at 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
AutoFitTextso 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
- wrap the route with
AppScaffoldunless it needs a custom layout. - use
AppSectionto group related controls. - use
AppLocalizationsfor all text. - take spacing from
AppSpacingand radii fromAppRadius. - put bottom action rows in
AppScaffold.bottomNavigationBar— it applies the system-nav-barSafeAreafor you. never hand a bare row toScaffold.bottomNavigationBar. - if the screen is the player or editor, wrap it with
MeditationThemeto respect the meditation's color seed.