Skip to content

Theme & UI

FLEX UI is a design system that provides brand tokens, theme extensions, utilities, and pre-built widgets to accelerate commerce app design and development. It ensures consistency across your application while maintaining flexibility for customization.

FLEX UI is located at /lib/shared/flex_ui/ and consists of:

  • Theme System: Brand tokens, design tokens, and theme extensions
  • Sizing System: Consistent spacing, typography, and component dimensions
  • Utilities: Context extensions and helper functions
  • Widgets: Pre-built, commerce-focused UI components

The foundation of FLEX UI is the DesignTokens class, which defines your app’s color palette and semantic meanings.

class DesignTokens {
// Primary customizable brand colors
static const Color brandPrimary = Color(0xFF013D27); // Dark green
static const Color brandSecondary = Color(0xFF2437FE); // Blue
static const Color brandAccent = Color(0xFFF1FF61); // Yellow-green
}
// Status and feedback colors
static const Color semanticSuccess = Color(0xFF22AE45);
static const Color semanticWarning = Color(0xFFFF9500);
static const Color semanticError = Color(0xFFFF3B30);
static const Color semanticInfo = Color(0xFF007AFF);
// Background, surface, and text colors
static const Color neutralWhite = Color(0xFFFAFAFA);
static const Color neutralBlack = Color(0xFF222222);
static const Color neutralGray100 = Color(0xFFF2F2F2); // Light surface
static const Color neutralGray200 = Color(0xFFE6E6E6); // Dividers
static const Color neutralGray300 = Color(0xFFE0E0E0); // Medium surface
static const Color neutralGray400 = Color(0xFF8E8E93); // Disabled
static const Color neutralGray800 = Color(0xFF282828); // Dark surface

The BrandThemeExtension extends Flutter’s Material Design theme with commerce-specific colors:

// Access brand colors in your widgets
final brandColors = context.brandColors;
// Available colors:
brandColors.success // Success states
brandColors.onSuccess // Text on success backgrounds
brandColors.warning // Warning states
brandColors.onWarning // Text on warning backgrounds
brandColors.info // Info states
brandColors.onInfo // Text on info backgrounds
brandColors.brandAccent // Brand accent color
brandColors.onBrandAccent // Text on accent backgrounds
brandColors.disabled // Disabled states
brandColors.onDisabled // Text on disabled elements
brandColors.divider // Divider lines

In your main.dart, configure the theme:

MaterialApp.router(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: DesignTokens.brandPrimary,
secondary: DesignTokens.brandSecondary,
),
extensions: [BrandThemeExtension.light], // or .dark
),
)

FLEX UI provides a comprehensive sizing system through the FlexSizes class:

FlexSizes.xxs // 4.0 - Minimal spacing
FlexSizes.xs // 8.0 - Small spacing
FlexSizes.sm // 12.0 - Small-medium spacing
FlexSizes.md // 16.0 - Medium spacing (most common)
FlexSizes.lg // 24.0 - Large spacing
FlexSizes.xl // 32.0 - Extra large spacing
FlexSizes.xxl // 40.0 - Maximum spacing
FlexSizes.fontSizeXs // 12.0 - Small text
FlexSizes.fontSizeSm // 14.0 - Body text
FlexSizes.fontSizeMd // 16.0 - Default text
FlexSizes.fontSizeLg // 18.0 - Subheadings
FlexSizes.fontSizeXl // 20.0 - Headings
// Buttons
FlexSizes.buttonHeight // 48.0 - Standard button height
FlexSizes.buttonRadius // 12.0 - Button border radius
FlexSizes.buttonWidth // 120.0 - Minimum button width
// Cards
FlexSizes.cardRadiusXs // 6.0 - Minimal card radius
FlexSizes.cardRadiusSm // 10.0 - Small card radius
FlexSizes.cardRadiusMd // 12.0 - Standard card radius
FlexSizes.cardRadiusLg // 16.0 - Large card radius
// Icons
FlexSizes.iconXs // 12.0 - Small icons
FlexSizes.iconSm // 16.0 - Standard icons
FlexSizes.iconMd // 24.0 - Medium icons
FlexSizes.iconLg // 32.0 - Large icons

FLEX UI provides powerful context extensions for easy access to theme data and device information:

// Quick access to theme data
context.theme // Full ThemeData
context.textTheme // TextTheme
context.colorScheme // Material ColorScheme
context.brandColors // FLEX brand colors
// Screen dimensions
context.deviceSize // Size of the device screen
context.screenWidth // Screen width
context.screenHeight // Screen height
// Safe area and padding
context.padding // MediaQuery padding
context.safeAreaPadding // Safe area insets
context.statusBarHeight // Status bar height
context.bottomSafeArea // Bottom safe area
// Theme and input states
context.isDarkMode // Dark mode detection
context.isKeyboardVisible // Keyboard visibility
// Responsive breakpoints
context.isMobile // Width < 768
context.isTablet // Width 768-1024
context.isDesktop // Width >= 1024

Show branded snackbars that automatically use your theme colors:

// Generic snackbar
context.showAppSnackBar(
message: "Operation completed",
isSuccess: true,
duration: Duration(seconds: 3),
);
// Success feedback
context.showSuccessSnackBar("Item added to cart!");
// Error handling
context.showErrorSnackBar("Network connection failed");
// Warnings
context.showWarningSnackBar("Low inventory for this item");

For use in StatefulWidget states:

class _MyWidgetState extends State<MyWidget> {
void _handleAction() {
// Automatically checks if widget is mounted
showSuccessSnackBar("Action completed");
}
}

FLEX UI includes pre-built widgets optimized for commerce applications:

FlexAppBar: Standardized app bar with consistent styling and behavior.

FlexButton: Enhanced button component with multiple variants.

FlexCard: Consistent card component for product displays and content containers.

FlexConnectivityIndicator: Network connectivity status indicator.

FlexImage / FlexTappableImage: Optimized image components for product catalogs and media.

FlexSearch: Search input component with commerce-specific features.

To customize your app’s appearance, modify the values in design_tokens.dart:

class DesignTokens {
// Update these colors to match your brand
static const Color brandPrimary = Color(0xFF1976D2); // Your primary color
static const Color brandSecondary = Color(0xFFF57C00); // Your secondary color
static const Color brandAccent = Color(0xFF4CAF50); // Your accent color
// Customize semantic colors if needed
static const Color semanticSuccess = Color(0xFF4CAF50);
// ... other customizations
}

Add custom sizes to sizes.dart:

class FlexSizes {
// Your custom sizes
static const double customHeaderHeight = 120.0;
static const double customCardWidth = 280.0;
// Override existing sizes if needed
static const double buttonHeight = 52.0; // Taller buttons
}

Extend the theme system for additional properties:

class CustomThemeExtension extends ThemeExtension<CustomThemeExtension> {
final Color specialColor;
final double specialSpacing;
const CustomThemeExtension({
required this.specialColor,
required this.specialSpacing,
});
// Implement required methods...
}
// ✅ Good - Use design tokens
Container(
color: DesignTokens.brandPrimary,
padding: EdgeInsets.all(FlexSizes.md),
)
// ❌ Avoid - Hard-coded values
Container(
color: Colors.blue,
padding: EdgeInsets.all(16.0),
)
// ✅ Good - Use context extensions
Container(
color: context.brandColors.success,
width: context.screenWidth * 0.8,
)
// ❌ Avoid - Manual theme access
Container(
color: Theme.of(context).extension<BrandThemeExtension>()?.success,
width: MediaQuery.of(context).size.width * 0.8,
)
// ✅ Good - Use responsive helpers
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(
context.isMobile ? FlexSizes.sm : FlexSizes.lg,
),
);
}
// ✅ Good - Use FLEX components
FlexButton(
text: "Add to Cart",
onPressed: _handleAddToCart,
variant: FlexButtonVariant.primary,
)
// ❌ Avoid - Custom implementations for standard patterns
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
padding: EdgeInsets.symmetric(vertical: 12, horizontal: 24),
),
onPressed: _handleAddToCart,
child: Text("Add to Cart"),
)
class ProductCard extends StatelessWidget {
final Product product;
Widget build(BuildContext context) {
return FlexCard(
child: Column(
children: [
FlexImage(
imageUrl: product.imageUrl,
height: FlexSizes.imageThumbSize,
width: double.infinity,
),
SizedBox(height: FlexSizes.sm),
Text(
product.name,
style: context.textTheme.titleMedium,
),
SizedBox(height: FlexSizes.xs),
Text(
product.price,
style: context.textTheme.headlineSmall?.copyWith(
color: context.brandColors.brandAccent,
),
),
SizedBox(height: FlexSizes.md),
FlexButton(
text: "Add to Cart",
onPressed: () => _addToCart(product),
variant: FlexButtonVariant.primary,
),
],
),
);
}
}
class OrderService {
Future<void> placeOrder() async {
try {
await _processOrder();
context.showSuccessSnackBar("Order placed successfully!");
} catch (error) {
context.showErrorSnackBar("Failed to place order. Please try again.");
}
}
}