The design system for the Šapni Mi app — a warm, earthy UI kit built on Flutter's ThemeExtension system.
→ Widgetbook component catalogue
Initialize the theme in main.dart before runApp to preload fonts:
await SapnimiTheme.initialize();Apply the theme to your app:
MaterialApp(
theme: SapnimiTheme.light,
...
)Access semantic colors via the theme extension:
final palette = Palette.of(context);
// or
final palette = Theme.of(context).palette;
palette.primary // deepPlum — primary brand color
palette.secondary // warmTaupe — secondary brand color
palette.neutral1 // ivoryWhite — backgrounds
palette.text1 // deepPlum — primary text
palette.text2 // espresso — body text
palette.text3 // warmTaupe — muted text
palette.other1 // vibrantRed — errorsStatic raw color constants are also available directly on Palette:
Palette.deepPlum
Palette.warmTaupe
Palette.ivoryWhite
// etc.final styles = TextStyles.of(context);
// or
final styles = Theme.of(context).textStyles;
styles.h1 // Poppins 600, 64px
styles.h2 // Poppins 600, 51px
styles.subtitle // Poppins 500, 40px
styles.label // Poppins 700, 36px
styles.emailLabel // Poppins 500, 32px
styles.message // Poppins 500, 36px
styles.button // Poppins 600, 32px
styles.indicator // Poppins 600, 28px
styles.countNumber // Playfair Display 700, 320px
styles.decorative // Caveat 400, 46pxNamed scale for consistent spacing:
Spacing.xs // 8
Spacing.sm // 12
Spacing.md // 16
Spacing.lg // 24
Spacing.xl // 32
Spacing.xxl // 40
Spacing.xxxl // 48
Spacing.section // 60Named border radius tokens:
Radii.sm // 5
Radii.md // 10
Radii.lg // 20
Radii.full // 999 (pill)
// Pre-built BorderRadius constants:
Radii.smAll // BorderRadius.all(Radius.circular(5))
Radii.mdAll // BorderRadius.all(Radius.circular(10))
Radii.lgAll // BorderRadius.all(Radius.circular(20))
Radii.fullAll // BorderRadius.all(Radius.circular(999))Shadows.cardDropdown // subtle drop shadow for cards and dropdownsGradients.background // warm diagonal background gradientfinal screen = ScreenType.of(context);
screen >= ScreenType.tablet // responsive logic
// enum values: watch (300), mobile (420), tablet (750), desktop (950)Wrap a widget to react to screen size changes:
ScreenTypeObserver(
builder: (context, screenType, child) => ...,
)Button(
text: 'Continue',
onPressed: () {},
)
Button.secondary(
text: 'Cancel',
icon: Icons.close,
onPressed: () {},
)Customize appearance with ButtonDecoration:
Button(
text: 'Custom',
decoration: ButtonDecoration(
backgroundColor: Colors.black,
foregroundColor: Colors.white,
borderRadius: Radii.lgAll,
),
onPressed: () {},
)A card container with shadow and rounded corners.
CardBox(
child: Text('Content'),
)Displays a countdown number inside a styled circular container.
Countdown(value: 3, label: 'seconds')An icon flanked by horizontal dividers.
DecoratedIcon(icon: Icons.star)A row of decorative dots used as a section divider.
DotsDivider()Fades out a child widget horizontally using a shader mask.
FadingWidget(child: Text('...'))A Scaffold with the standard background gradient applied.
GradientScaffold(body: ...)Circular indicator widgets for step counts and time displays.
StepIndicator(current: 1, total: 5)
TimeIndicator(seconds: 90, isActive: true)An animated loading widget with multiple effect options.
LoadingIndicator()
LoadingIndicator(effect: LoadingEffect.newtonCradle)A large heading displayed inside a card-like container.
PromptTitle(text: 'What is your dream?')Higher-level layout components assembled from primitives and widgets.
| Component | Description |
|---|---|
FocusScaffold |
Centered content with standard page padding |
FocusColumn |
Column with optional header and footer slots |
DecorativeScaffold |
Split layout — content left, decorative element right |
PromptScaffold |
Page layout with title and scrollable content area |
PromptColumn |
Column with optional title and bottom button row |
PromptCard |
Card with icon, title, description, and extra content slot |
Show feedback toasts from anywhere with a BuildContext:
Toasts.of(context).showSuccess('Saved!');
Toasts.of(context).showError('Something went wrong.');Wrap your app (or subtree) with Toasts to enable it:
Toasts(child: MaterialApp(...))