- Introduced SKILL.md for KROW mobile release process detailing versioning, CHANGELOG management, GitHub Actions workflows, APK signing, and hotfix procedures. - Added SKILL.md for KROW paper design conventions covering design tokens, component patterns, screen structure, and naming rules to ensure visual consistency across design files.
17 KiB
name, description
| name | description |
|---|---|
| krow-mobile-design-system | KROW mobile design system usage rules covering colors, typography, icons, spacing, and UI component patterns. Use this when implementing UI in KROW mobile features, matching POC designs to production, creating themed widgets, enforcing visual consistency, or reviewing UI code compliance. Prevents hardcoded values and ensures brand consistency across staff and client apps. Critical for maintaining immutable design tokens. |
KROW Mobile Design System Usage
This skill defines mandatory standards for UI implementation using the shared apps/mobile/packages/design_system. All UI must consume design system tokens exclusively.
When to Use This Skill
- Implementing any UI in mobile features
- Migrating POC/prototype designs to production
- Creating new themed widgets or components
- Reviewing UI code for design system compliance
- Matching colors and typography from designs
- Adding icons, spacing, or layout elements
- Setting up theme configuration in apps
- Refactoring UI code with hardcoded values
Core Principle
Design tokens (colors, typography, spacing) are IMMUTABLE and defined centrally.
Features consume tokens but NEVER modify them. The design system maintains visual coherence across all apps.
1. Design System Ownership
Centralized Authority
apps/mobile/packages/design_systemowns:- All brand assets
- Colors and semantic color mappings
- Typography and font configurations
- Core UI components
- Icons and images
- Spacing, radius, elevation constants
No Local Overrides
✅ CORRECT:
// Feature uses design system
import 'package:design_system/design_system.dart';
Container(
color: UiColors.background,
padding: EdgeInsets.all(UiConstants.spacingL),
child: Text(
'Hello',
style: UiTypography.display1m,
),
)
❌ FORBIDDEN:
// ❌ Custom colors in feature
const myBlue = Color(0xFF1A2234);
// ❌ Custom text styles in feature
const myStyle = TextStyle(fontSize: 24, fontWeight: FontWeight.bold);
// ❌ Theme overrides in feature
Theme(
data: ThemeData(primaryColor: Colors.blue),
child: MyWidget(),
)
Extension Policy
If a required style is missing:
- FIRST: Add it to
design_systemfollowing existing patterns - THEN: Use it in your feature
DO NOT create temporary workarounds with hardcoded values.
2. Package Structure
apps/mobile/packages/design_system/
├── lib/
│ ├── src/
│ │ ├── ui_colors.dart # Color tokens
│ │ ├── ui_typography.dart # Text styles
│ │ ├── ui_icons.dart # Icon exports
│ │ ├── ui_constants.dart # Spacing, radius, elevation
│ │ ├── ui_theme.dart # ThemeData factory
│ │ └── widgets/ # Shared UI components
│ │ ├── custom_button.dart
│ │ └── custom_app_bar.dart
│ └── design_system.dart # Public exports
├── assets/
│ ├── icons/
│ ├── images/
│ └── fonts/
└── pubspec.yaml
3. Colors Usage Rules
Strict Protocol
✅ DO:
// Use UiColors for all color needs
Container(color: UiColors.background)
Text('Hello', style: TextStyle(color: UiColors.foreground))
Icon(Icons.home, color: UiColors.primary)
❌ DON'T:
// ❌ Hardcoded hex colors
Container(color: Color(0xFF1A2234))
// ❌ Material color constants
Container(color: Colors.blue)
// ❌ Opacity on hardcoded colors
Container(color: Color(0xFF1A2234).withOpacity(0.5))
Available Color Categories
Brand Colors:
UiColors.primary- Main brand colorUiColors.secondary- Secondary brand colorUiColors.accent- Accent highlights
Semantic Colors:
UiColors.background- Page backgroundUiColors.foreground- Primary text colorUiColors.card- Card/container backgroundUiColors.border- Border colorsUiColors.mutedForeground- Secondary text
Status Colors:
UiColors.success- Success statesUiColors.warning- Warning statesUiColors.error- Error statesUiColors.info- Information states
Color Matching from POCs
When migrating POC designs:
- Find closest match in
UiColors - Use existing color even if slightly different
- DO NOT add new colors without design team approval
Example Process:
// POC has: Color(0xFF2C3E50)
// Find closest: UiColors.background or UiColors.card
// Use: UiColors.card
// POC has: Color(0xFF27AE60)
// Find closest: UiColors.success
// Use: UiColors.success
Theme Access
Colors can also be accessed via theme:
// Both are valid:
Container(color: UiColors.primary)
Container(color: Theme.of(context).colorScheme.primary)
4. Typography Usage Rules
Strict Protocol
✅ DO:
// Use UiTypography for all text
Text('Title', style: UiTypography.display1m)
Text('Body', style: UiTypography.body1r)
Text('Label', style: UiTypography.caption1m)
❌ DON'T:
// ❌ Custom TextStyle
Text('Title', style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
))
// ❌ Manual font configuration
Text('Body', style: TextStyle(
fontFamily: 'Inter',
fontSize: 16,
))
// ❌ Modifying existing styles inline
Text('Title', style: UiTypography.display1m.copyWith(
fontSize: 28, // ← Don't override size
))
Available Typography Styles
Display Styles (Large Headers):
UiTypography.display1m- Display MediumUiTypography.display1sb- Display Semi-BoldUiTypography.display1b- Display Bold
Heading Styles:
UiTypography.heading1m- H1 MediumUiTypography.heading1sb- H1 Semi-BoldUiTypography.heading1b- H1 BoldUiTypography.heading2m- H2 MediumUiTypography.heading2sb- H2 Semi-Bold
Body Styles:
UiTypography.body1r- Body RegularUiTypography.body1m- Body MediumUiTypography.body1sb- Body Semi-BoldUiTypography.body2r- Body 2 Regular
Caption/Label Styles:
UiTypography.caption1m- Caption MediumUiTypography.caption1sb- Caption Semi-BoldUiTypography.label1m- Label Medium
Allowed Customizations
✅ ALLOWED (Color Only):
// You MAY change color
Text(
'Title',
style: UiTypography.display1m.copyWith(
color: UiColors.error, // ← OK
),
)
❌ FORBIDDEN (Size, Weight, Family):
// ❌ Don't change size
Text(
'Title',
style: UiTypography.display1m.copyWith(fontSize: 28),
)
// ❌ Don't change weight
Text(
'Title',
style: UiTypography.display1m.copyWith(fontWeight: FontWeight.w900),
)
// ❌ Don't change family
Text(
'Title',
style: UiTypography.display1m.copyWith(fontFamily: 'Roboto'),
)
Typography Matching from POCs
When migrating:
- Identify text role (heading, body, caption)
- Find closest matching style in
UiTypography - Use existing style even if size/weight differs slightly
5. Icons Usage Rules
Strict Protocol
✅ DO:
// Use UiIcons
Icon(UiIcons.home)
Icon(UiIcons.profile)
Icon(UiIcons.chevronLeft)
❌ DON'T:
// ❌ Direct icon library imports
import 'package:lucide_icons/lucide_icons.dart';
Icon(LucideIcons.home)
// ❌ Font Awesome direct
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
FaIcon(FontAwesomeIcons.house)
Why Centralize Icons?
- Consistency: Same icon for same action everywhere
- Branding: Unified icon set with consistent stroke weight
- Swappability: Change icon library in one place
Icon Libraries
Design system uses:
typedef _IconLib = LucideIcons;(primary)typedef _IconLib2 = FontAwesomeIcons;(secondary)
Features MUST NOT import these directly.
Adding New Icons
If icon missing:
- Add to
ui_icons.dart:
class UiIcons {
static const home = _IconLib.home;
static const newIcon = _IconLib.newIcon; // Add here
}
- Use in feature:
Icon(UiIcons.newIcon)
6. Spacing & Layout Constants
Strict Protocol
✅ DO:
// Use UiConstants for spacing
Padding(padding: EdgeInsets.all(UiConstants.spacingL))
SizedBox(height: UiConstants.spacingM)
Container(
padding: EdgeInsets.symmetric(
horizontal: UiConstants.spacingL,
vertical: UiConstants.spacingM,
),
)
// Use UiConstants for radius
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(UiConstants.radiusM),
),
)
// Use UiConstants for elevation
elevation: UiConstants.elevationLow
❌ DON'T:
// ❌ Magic numbers
Padding(padding: EdgeInsets.all(16.0))
SizedBox(height: 24.0)
BorderRadius.circular(8.0)
elevation: 2.0
Available Constants
Spacing:
UiConstants.spacingXs // Extra small
UiConstants.spacingS // Small
UiConstants.spacingM // Medium
UiConstants.spacingL // Large
UiConstants.spacingXl // Extra large
UiConstants.spacing2xl // 2x Extra large
Border Radius:
UiConstants.radiusS // Small
UiConstants.radiusM // Medium
UiConstants.radiusL // Large
UiConstants.radiusXl // Extra large
UiConstants.radiusFull // Fully rounded
Elevation:
UiConstants.elevationNone
UiConstants.elevationLow
UiConstants.elevationMedium
UiConstants.elevationHigh
7. Smart Widgets Usage
When to Use
- Prefer standard Flutter Material widgets styled via theme
- Use design system widgets for non-standard patterns
- Create new widgets in design system if reused >3 features
Navigation in Widgets
Widgets with navigation MUST use safe methods:
✅ CORRECT:
// In UiAppBar back button:
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/krow_core.dart';
IconButton(
icon: Icon(UiIcons.chevronLeft),
onPressed: () => Modular.to.popSafe(), // ← Safe pop
)
❌ FORBIDDEN:
// ❌ Direct Navigator
IconButton(
icon: Icon(UiIcons.chevronLeft),
onPressed: () => Navigator.pop(context),
)
// ❌ Unsafe Modular
IconButton(
icon: Icon(UiIcons.chevronLeft),
onPressed: () => Modular.to.pop(), // Can crash
)
Composition Over Inheritance
✅ CORRECT:
// Compose standard widgets
Container(
padding: EdgeInsets.all(UiConstants.spacingL),
decoration: BoxDecoration(
color: UiColors.card,
borderRadius: BorderRadius.circular(UiConstants.radiusM),
),
child: Column(
children: [
Text('Title', style: UiTypography.heading1sb),
SizedBox(height: UiConstants.spacingM),
Text('Body', style: UiTypography.body1r),
],
),
)
❌ AVOID:
// ❌ Deep custom widget hierarchies
class CustomCard extends StatelessWidget {
// Complex custom implementation
}
8. Theme Configuration
App Setup
Apps initialize theme ONCE in root MaterialApp:
✅ CORRECT:
// apps/mobile/apps/staff/lib/app_widget.dart
import 'package:design_system/design_system.dart';
class StaffApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
theme: StaffTheme.light, // ← Design system theme
darkTheme: StaffTheme.dark, // ← Optional dark mode
themeMode: ThemeMode.system,
// ...
);
}
}
❌ FORBIDDEN:
// ❌ Custom theme in app
MaterialApp.router(
theme: ThemeData(
primaryColor: Colors.blue, // ← NO!
),
)
// ❌ Theme override in feature
Theme(
data: ThemeData(...),
child: MyFeatureWidget(),
)
Accessing Theme
Both methods valid:
// Method 1: Direct design system import
import 'package:design_system/design_system.dart';
Text('Hello', style: UiTypography.body1r)
// Method 2: Via theme context
Text('Hello', style: Theme.of(context).textTheme.bodyMedium)
Prefer Method 1 for explicit type safety.
9. POC → Production Workflow
Step 1: Implement Structure (POC Matching)
Implement UI layout exactly matching POC:
// Temporary: Match POC visually
Container(
color: Color(0xFF1A2234), // ← POC color
padding: EdgeInsets.all(16.0), // ← POC spacing
child: Text(
'Title',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), // ← POC style
),
)
Purpose: Ensure visual parity with POC before refactoring.
Step 2: Architecture Refactor
Move to Clean Architecture:
- Extract business logic to use cases
- Move state management to BLoCs
- Implement repository pattern
- Use dependency injection
Step 3: Design System Integration
Replace hardcoded values:
// Production: Design system tokens
Container(
color: UiColors.background, // ← Found closest match
padding: EdgeInsets.all(UiConstants.spacingL), // ← Used constant
child: Text(
'Title',
style: UiTypography.heading1sb, // ← Matched typography
),
)
Color Matching:
- POC
#1A2234→UiColors.background - POC
#3498DB→UiColors.primary - POC
#27AE60→UiColors.success
Typography Matching:
- POC
24px bold→UiTypography.heading1sb - POC
16px regular→UiTypography.body1r - POC
14px medium→UiTypography.caption1m
Spacing Matching:
- POC
16px→UiConstants.spacingL - POC
8px→UiConstants.spacingM - POC
4px→UiConstants.spacingS
10. Anti-Patterns & Common Mistakes
❌ Magic Numbers
// BAD
EdgeInsets.all(12.0)
SizedBox(height: 24.0)
BorderRadius.circular(8.0)
// GOOD
EdgeInsets.all(UiConstants.spacingM)
SizedBox(height: UiConstants.spacingL)
BorderRadius.circular(UiConstants.radiusM)
❌ Local Themes
// BAD
Theme(
data: ThemeData(primaryColor: Colors.blue),
child: MyWidget(),
)
// GOOD
// Use global theme defined in app
❌ Hex Hunting
// BAD: Copy-paste from Figma
Container(color: Color(0xFF3498DB))
// GOOD: Find matching design system color
Container(color: UiColors.primary)
❌ Direct Icon Library
// BAD
import 'package:lucide_icons/lucide_icons.dart';
Icon(LucideIcons.home)
// GOOD
Icon(UiIcons.home)
❌ Custom Text Styles
// BAD
Text('Title', style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
fontFamily: 'Inter',
))
// GOOD
Text('Title', style: UiTypography.heading1sb)
11. Design System Review Checklist
Before merging UI code:
✅ Design System Compliance
- No hardcoded
Color(...)or0xFF...hex values - No custom
TextStyle(...)definitions - All spacing uses
UiConstants.spacing* - All radius uses
UiConstants.radius* - All elevation uses
UiConstants.elevation* - All icons from
UiIcons, not direct library imports - Theme consumed from design system, no local overrides
- Layout matches POC intent using design system primitives
✅ Architecture Compliance
- No business logic in widgets
- State managed by BLoCs
- Navigation uses Modular safe extensions
- Localization used for all text (no hardcoded strings)
- No direct Data Connect queries in widgets
✅ Code Quality
- Widget build methods concise (<50 lines)
- Complex widgets extracted to separate files
- Meaningful widget names
- Doc comments on reusable widgets
12. When to Extend Design System
Add New Color
When: New brand color approved by design team
Process:
- Add to
ui_colors.dart:
class UiColors {
static const myNewColor = Color(0xFF123456);
}
- Update theme if needed
- Use in features
Add New Typography Style
When: New text style pattern emerges across multiple features
Process:
- Add to
ui_typography.dart:
class UiTypography {
static const myNewStyle = TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
fontFamily: _fontFamily,
);
}
- Use in features
Add Shared Widget
When: Widget reused in 3+ features
Process:
- Create in
lib/src/widgets/:
// my_widget.dart
class MyWidget extends StatelessWidget {
// Implementation using design system tokens
}
- Export from
design_system.dart - Use across features
Summary
Core Rules:
- All colors from
UiColors- Zero hex codes in features - All typography from
UiTypography- Zero custom TextStyle - All spacing/radius/elevation from
UiConstants- Zero magic numbers - All icons from
UiIcons- Zero direct library imports - Theme defined once in app entry point
- POC → Production requires design system integration step
The Golden Rule: Design system is immutable. Features adapt to the system, not the other way around.
When implementing UI:
- Import
package:design_system/design_system.dart - Use design system tokens exclusively
- Match POC intent with available tokens
- Request new tokens only when truly necessary
- Never create temporary hardcoded workarounds
Visual consistency is non-negotiable. Every pixel must come from the design system.