Files
Krow-workspace/.claude/skills/krow-mobile-design-system/SKILL.md

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_system owns:
    • 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:

  1. FIRST: Add it to design_system following existing patterns
  2. 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 color
  • UiColors.secondary - Secondary brand color
  • UiColors.accent - Accent highlights

Semantic Colors:

  • UiColors.background - Page background
  • UiColors.foreground - Primary text color
  • UiColors.card - Card/container background
  • UiColors.border - Border colors
  • UiColors.mutedForeground - Secondary text

Status Colors:

  • UiColors.success - Success states
  • UiColors.warning - Warning states
  • UiColors.error - Error states
  • UiColors.info - Information states

Color Matching from POCs

When migrating POC designs:

  1. Find closest match in UiColors
  2. Use existing color even if slightly different
  3. 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 Medium
  • UiTypography.display1sb - Display Semi-Bold
  • UiTypography.display1b - Display Bold

Heading Styles:

  • UiTypography.heading1m - H1 Medium
  • UiTypography.heading1sb - H1 Semi-Bold
  • UiTypography.heading1b - H1 Bold
  • UiTypography.heading2m - H2 Medium
  • UiTypography.heading2sb - H2 Semi-Bold

Body Styles:

  • UiTypography.body1r - Body Regular
  • UiTypography.body1m - Body Medium
  • UiTypography.body1sb - Body Semi-Bold
  • UiTypography.body2r - Body 2 Regular

Caption/Label Styles:

  • UiTypography.caption1m - Caption Medium
  • UiTypography.caption1sb - Caption Semi-Bold
  • UiTypography.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:

  1. Identify text role (heading, body, caption)
  2. Find closest matching style in UiTypography
  3. 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?

  1. Consistency: Same icon for same action everywhere
  2. Branding: Unified icon set with consistent stroke weight
  3. 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:

  1. Add to ui_icons.dart:
class UiIcons {
  static const home = _IconLib.home;
  static const newIcon = _IconLib.newIcon;  // Add here
}
  1. 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 #1A2234UiColors.background
  • POC #3498DBUiColors.primary
  • POC #27AE60UiColors.success

Typography Matching:

  • POC 24px boldUiTypography.heading1sb
  • POC 16px regularUiTypography.body1r
  • POC 14px mediumUiTypography.caption1m

Spacing Matching:

  • POC 16pxUiConstants.spacingL
  • POC 8pxUiConstants.spacingM
  • POC 4pxUiConstants.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(...) or 0xFF... 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:

  1. Add to ui_colors.dart:
class UiColors {
  static const myNewColor = Color(0xFF123456);
}
  1. Update theme if needed
  2. Use in features

Add New Typography Style

When: New text style pattern emerges across multiple features

Process:

  1. Add to ui_typography.dart:
class UiTypography {
  static const myNewStyle = TextStyle(
    fontSize: 20,
    fontWeight: FontWeight.w600,
    fontFamily: _fontFamily,
  );
}
  1. Use in features

Add Shared Widget

When: Widget reused in 3+ features

Process:

  1. Create in lib/src/widgets/:
// my_widget.dart
class MyWidget extends StatelessWidget {
  // Implementation using design system tokens
}
  1. Export from design_system.dart
  2. Use across features

Summary

Core Rules:

  1. All colors from UiColors - Zero hex codes in features
  2. All typography from UiTypography - Zero custom TextStyle
  3. All spacing/radius/elevation from UiConstants - Zero magic numbers
  4. All icons from UiIcons - Zero direct library imports
  5. Theme defined once in app entry point
  6. 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:

  1. Import package:design_system/design_system.dart
  2. Use design system tokens exclusively
  3. Match POC intent with available tokens
  4. Request new tokens only when truly necessary
  5. Never create temporary hardcoded workarounds

Visual consistency is non-negotiable. Every pixel must come from the design system.