| name | flutter-design-skill |
| description | This skill provides the Comic style UI design guideline and code to make the Flutter app look better. ALWAYS use this skill when the user asks to create, update, design, modify, or build any page, screen, widget, or UI component in a Flutter application. |
Flutter Design Overview
- This skill provides the Comic style UI design guideline and code.
- It ensures that the Flutter application will have Comic UI style.
When to Use
- Use this skill when the user requests to design or improve the UI of a Flutter application.
- Use this skill when the user mention Comic style UI design.
Design Guidelines
- Refer to the comic-theme.md for detailed design guidelines and code examples.
Comic Design Rules
Follow these critical design rules when implementing the Comic style UI in Flutter.
Reusable Flutter Widgets: Always re-use or create a re-usable widgets to not repeat code.
- For example, create a
./lib/widgets/theme/ComicButtonwidget instead of styling each button individually. - all of the
Comicwidgets must be saved in./lib/widgets/theme/comic_<component_name>.dart - It must create re-usable widgets for common UI elements like buttons, cards, text fields, etc.
- For example, create a
Comic Design: Always follow these Comic design principles:
- Border:
- Standard elements:
2.0thickness - List-based widgets (ListTile, compact cards):
1.5thickness for lighter visual weight
- Standard elements:
- No shadow: Always no shadows
- No elevation: Always zero elevation
- Outline: Use outline color for borders
- Rounded corners: The border radius must be
12for large elements,8for other elements. - Colors: Use the Theme Primary Color for primary elements.
- Use the Theme Secondary Color for secondary elements.
- Use the Theme Surface Color for cards and containers.
- Use the Theme Background Color for the surface color.
- Use the Theme onSurface Color for text and icons on surface color.
- Fonts: Use the Theme Text Styles for all text elements.
- Use
bodyLargefor regular text. - Use
titleMediumfor titles. - Use
labelLargefor buttons.
- Use
- Border:
Spacing: Use multiples of 8 (8, 16, 24, 32, etc.)
Theme Design Guidelines
CRITICAL FLUTTER RULES:
- Use Theme ONLY: Always use
Theme.of(context)for colors, fonts, and styles. NEVER hardcode them.
❌ NEVER DO:
Text('Login') // Hardcoded text
color: Colors.blue // Hardcoded color
fontSize: 16 // Hardcoded size
border: Border.all() // Borders not allowed
elevation: 4 // Must be 0
ElevatedButton(child: Text('Click', style: TextStyle(...))) // Inline style overrides Theme
✅ ALWAYS DO:
Text(T.login) // i18n translation
color: Theme.of(context).colorScheme.primary // Theme color
style: Theme.of(context).textTheme.bodyLarge // Theme text style
elevation: 0 // Flat design
ElevatedButton(child: Text(T.click)) // Let Theme handle styling
ComicButton System
Concept & Intent
The ComicButton system is a unified, reusable button component library that implements the Comic design language across the entire Flutter application. Instead of styling buttons individually with inline styles, all buttons use the ComicButton base widget or its variants.
Key Benefits:
- Consistency: All buttons share the same Comic design DNA (2px border, no shadow, rounded corners)
- Flexibility: Three design options (
rounded,padding,textSize) allow creating any button style - Maintainability: Change the button style in one place, update everywhere
- Theme Integration: Automatically uses Theme colors and text styles
Core Design Principles
All ComicButton variants follow these Comic design rules:
- Border: 2.0px thickness with outline color
- Elevation: Always 0 (flat design, no shadows)
- Corners: Rounded with borderRadius 12 (normal) or fully rounded (full/pill)
- Colors: Theme-based (surface, primary, secondary)
- Typography: Theme text styles (bodyLarge, titleMedium, bodyMedium)
- Spacing: Padding in multiples of 8
Design Options
Every ComicXxxButton variant supports three design option enums:
ComicButtonRounded
Controls the corner shape of the button.
| Value | Description | Use Case |
|---|---|---|
full |
Pill/stadium shape (StadiumBorder) | CTA buttons, login buttons |
normal |
Rounded corners (borderRadius: 12) | Standard buttons |
ComicButtonPadding
Controls the internal padding of the button.
| Value | Padding | Use Case |
|---|---|---|
large |
32×20 | Important action buttons (login, submit) |
medium |
24×16 | Standard buttons (default) |
small |
16×12 | Compact buttons, inline actions |
ComicButtonTextSize
Controls the text size inside the button.
| Value | Text Style | Use Case |
|---|---|---|
large |
titleMedium | Important action buttons |
medium |
bodyLarge | Standard buttons (default) |
small |
bodyMedium | Compact buttons |
Button Variants
ComicButton (Base)
The foundation widget with neutral colors (surface background, onSurface text).
// Standard button
ComicButton(
onPressed: () => doSomething(),
child: Text(Lo.of(context)!.action),
)
// Large login button (pill shape)
ComicButton(
onPressed: () => login(),
rounded: ComicButtonRounded.full,
padding: ComicButtonPadding.large,
textSize: ComicButtonTextSize.large,
child: Text(Lo.of(context)!.login),
)
ComicPrimaryButton
Primary action button using Theme primary color.
ComicPrimaryButton(
onPressed: () => submit(),
rounded: ComicButtonRounded.full,
padding: ComicButtonPadding.large,
textSize: ComicButtonTextSize.large,
child: Text(Lo.of(context)!.submit),
)
ComicSecondaryButton
Secondary action button using Theme secondary color.
ComicSecondaryButton(
onPressed: () => cancel(),
padding: ComicButtonPadding.small,
textSize: ComicButtonTextSize.small,
child: Text(Lo.of(context)!.cancel),
)
Quick Reference
| Button Style | rounded | padding | textSize |
|---|---|---|---|
| Large CTA | full |
large |
large |
| Standard | normal |
medium |
medium |
| Compact | normal |
small |
small |
File Location
All ComicButton widgets are defined in:
./lib/widgets/theme/comic_button.dart
ComicTextFormField
Concept & Intent
The ComicTextFormField is a reusable text input component that implements the Comic design language. It provides a consistent way to create text input fields across the entire Flutter application with Comic-style borders, proper spacing, and theme integration.
Key Benefits:
- Consistency: All text fields share the same Comic design DNA (2px border, no shadow, rounded corners)
- Flexibility: Extensive customization options while maintaining Comic design principles
- Maintainability: Change the text field style in one place, update everywhere
- Theme Integration: Automatically uses Theme colors and text styles
Design Principles
All ComicTextFormField instances follow these Comic design rules:
- Border: 1.0px thickness with outline color (customizable) - lighter weight for compact input fields
- Elevation: Always 0 (flat design, no shadows)
- Corners: Rounded with borderRadius 12 (customizable)
- Colors: Theme-based (surface background, outline border, primary focus, error)
- Typography: Theme text styles (bodyLarge for content, bodyMedium for label/hint)
- Spacing: Content padding in multiples of 8
Key Features
- Full TextFormField API: Supports all standard TextFormField parameters
- Customizable Borders: Configure border color, focus color, error color, width, and radius
- Theme Integration: Uses Theme colors for all visual elements
- Validation Support: Built-in validator and error text display
- Input Control: Supports read-only, enabled/disabled, obscure text, keyboard type
- Icons: Prefix and suffix icon support
- Multi-line: Configurable min/max lines for single or multi-line input
- Length Control: Maximum length with counter
Usage Examples
Basic Text Field
ComicTextFormField(
controller: _nameController,
labelText: T.fullName,
hintText: T.fullNameHint,
)
Password Field
ComicTextFormField(
controller: _passwordController,
labelText: T.password,
hintText: T.enterPassword,
obscureText: true,
prefixIcon: Icon(Icons.lock),
)
With Validation
ComicTextFormField(
controller: _emailController,
labelText: T.email,
hintText: T.enterEmail,
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) {
return T.emailRequired;
}
return null;
},
)
Multi-line Text Area
ComicTextFormField(
controller: _bioController,
labelText: T.bio,
hintText: T.enterBio,
maxLines: 5,
minLines: 3,
maxLength: 500,
)
Disabled Field
ComicTextFormField(
controller: _nicknameController,
labelText: T.nickname,
hintText: T.nicknameHint,
enabled: false, // or readOnly: true
)
Customization Options
Border Styling
ComicTextFormField(
controller: _controller,
labelText: T.label,
// Custom border styling
borderWidth: 1.0, // Default: 1.0
borderRadius: 12, // Default: 12
borderColor: Colors.grey,
focusedBorderColor: Colors.blue,
errorBorderColor: Colors.red,
)
Content Padding
ComicTextFormField(
controller: _controller,
labelText: T.label,
// Custom padding (default: symmetric horizontal: 16, vertical: 16)
contentPadding: EdgeInsets.all(20),
)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| controller | TextEditingController? | null | Text field controller |
| labelText | String? | null | Label text displayed above input |
| hintText | String? | null | Hint text shown when empty |
| errorText | String? | null | Error message to display |
| prefixIcon | Widget? | null | Icon at the start of the field |
| suffixIcon | Widget? | null | Icon at the end of the field |
| keyboardType | TextInputType? | null | Keyboard type for input |
| obscureText | bool | false | Hide text for passwords |
| onChanged | ValueChanged |
null | Callback when text changes |
| onFieldSubmitted | ValueChanged |
null | Callback on submit |
| validator | String? Function(String?)? | null | Validation function |
| readOnly | bool | false | Prevent editing |
| enabled | bool | true | Enable/disable field |
| maxLines | int? | 1 | Maximum lines |
| minLines | int? | null | Minimum lines |
| maxLength | int? | null | Maximum characters |
| autofocus | bool | false | Auto focus on load |
| borderColor | Color? | null | Border color (default: outline) |
| focusedBorderColor | Color? | null | Focus border (default: primary) |
| errorBorderColor | Color? | null | Error border (default: error) |
| borderWidth | double | 1.0 | Border thickness |
| borderRadius | double | 12 | Corner radius |
| contentPadding | EdgeInsetsGeometry? | null | Internal padding |
Border States
ComicTextFormField automatically handles five border states:
- Enabled Border: Default state (outline color, 1.0px)
- Focused Border: When field has focus (primary color, 1.0px)
- Error Border: When validation fails (error color, 1.0px)
- Focused Error Border: Error state with focus (error color, 1.0px)
- Disabled Border: When field is disabled (outline with 50% opacity, 1.0px)
File Location
ComicTextFormField is defined in:
./lib/widgets/theme/comic_text_form_field.dart
List-Based Widgets Design
For list-based content such as ListTile and Compact Cards:
Design Principles
- Border: Use
1.0thickness (thinner than standard 2.0) for lighter visual weight in compact lists - Border Radius:
12for rounded corners - Elevation:
0(no shadow) - Margin:
EdgeInsets.zero(parent controls spacing) - Colors: Use Theme colors (surface, outline, onSurface)
Key Features
Card(
elevation: 0, // Comic Design: no shadow
margin: EdgeInsets.zero, // No margin (parent controls spacing)
color: Theme.of(context).colorScheme.surface,
// Comic Design: thinner border with rounded corners for compact list
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(
color: Theme.of(context).colorScheme.outline,
width: 1.0, // Thinner border for list items
),
),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12), // Match card radius for ripple
child: // ... content
),
)
ComicModalBottomSheet
Concept & Intent
The ComicModalBottomSheet is a reusable modal bottom sheet component that implements the Comic design language. It provides a consistent way to display modal content from the bottom of the screen with Comic-style rounded top corners, proper spacing, and an integrated drag handle.
Design Principles
- Border Radius:
12.0for top-left and top-right corners (creates Comic-style rounded top) - Border Width:
2.0thickness on top, left, and right sides (no border on bottom) - Elevation:
0(flat design, no shadows) - Colors: Theme-based (surface background, outline border)
- Spacing: Consistent padding using multiples of 8
- Drag Handle: Custom drag handle integrated inside the container (32px × 4px)
Usage
// Show Comic Modal Bottom Sheet with integrated drag handle
showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent, // Comic Design: transparent for custom styling
elevation: 0, // Comic Design: no shadow
builder: (context) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12.0), // Comic Design: rounded top corners
topRight: Radius.circular(12.0),
),
border: Border(
top: BorderSide(
color: Theme.of(context).colorScheme.outline,
width: 2.0, // Comic Design: 2.0 border
),
left: BorderSide(
color: Theme.of(context).colorScheme.outline,
width: 2.0,
),
right: BorderSide(
color: Theme.of(context).colorScheme.outline,
width: 2.0,
),
// No bottom border
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Drag handle indicator
// Comic Design: 8px spacing from top
const SizedBox(height: 8),
Container(
width: 32,
height: 4,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onSurfaceVariant.withValues(alpha: 0.4),
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(height: 8),
// Your content here (wrapped in Flexible if scrollable)
Flexible(
child: ListView(
shrinkWrap: true,
children: [
// Your list items
],
),
),
],
),
);
},
);
Key Features
- Rounded Top Corners: 12.0 radius on top-left and top-right for Comic style
- Selective Borders: Border on top, left, and right only (no bottom border)
- Theme Integration: Uses Theme colors for surface and outline
- No Shadow: Elevation set to 0 for flat design
- Transparent Background: Parent backgroundColor set to transparent for custom styling
- Integrated Drag Handle: Custom drag handle (32×4px) with proper spacing inside the container
- Flexible Layout: Uses Column with MainAxisSize.min and Flexible for proper sizing
Drag Handle Specifications
- Size: 32px wide × 4px tall
- Color:
onSurfaceVariantwith 40% opacity - Border Radius: 2px for rounded corners
- Spacing: 8px top and bottom (multiples of 8)
- Position: Inside the container, above the content
Best Practices
- Always use
backgroundColor: Colors.transparentinshowModalBottomSheet - Apply
elevation: 0to maintain flat design - Use Theme colors for consistency
- Wrap the Container's child in a Column with
mainAxisSize: MainAxisSize.min - Add the drag handle as the first element in the Column
- Use
Flexiblewrapper for scrollable content (ListView, GridView, etc.) - Use
shrinkWrap: truefor ListView to allow proper sizing - Add appropriate spacing (multiples of 8) around content
Comic AppBar Design
Concept & Intent
The Comic AppBar implements the Comic design language for app bars and header sections. It provides consistent styling with a characteristic 1.0px bottom border using the outlineVariant color, creating a subtle visual separation between the header and content areas that matches the bottom navigation bar style.
Design Principles
- Border: 1.0px bottom border with outlineVariant color (matches bottom navigation bar)
- Typography: Use Theme titleLarge text style for titles
- Height: Standard AppBar height (56px) or custom heights for special cases
- Colors: Theme-based (surface background, outlineVariant border, onSurface text)
- No Elevation: Always 0 (flat design, no shadows)
Usage Patterns
Pattern 1: Standard Scaffold AppBar
Use this pattern when you have a Scaffold widget with an appBar property.
Scaffold(
appBar: AppBar(
title: Text(T.editProfile, style: theme.textTheme.titleLarge),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(1),
child: Container(height: 1, color: scheme.outlineVariant),
),
),
body: // Your content here
)
Key Features:
- Uses standard
AppBarwidget - Title uses
titleLargetext style from Theme - Bottom border achieved using
PreferredSizewith 1.0px height Container - Border color uses Theme
outlineVariantcolor (matches bottom navigation bar)
Pattern 2: Custom Header (Without Scaffold)
Use this pattern when you need an app bar-like header without a Scaffold (e.g., in special layouts, custom scrolling views, or nested navigation).
SafeArea(
child: Container(
height: 56, // Standard AppBar height
padding: const EdgeInsets.only(right: 4, left: 12),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
// Comic Design: 1.0px border with outlineVariant color (matches bottom nav)
color: Theme.of(context).colorScheme.outlineVariant,
width: 1.0,
),
),
),
child: Row(
children: [
// Title
Expanded(
child: Text(
T.pageTitle,
style: Theme.of(context)
.textTheme
.titleLarge
?.copyWith(fontWeight: FontWeight.w600),
overflow: TextOverflow.ellipsis,
),
),
// Action buttons (optional)
IconButton(
icon: const FaIcon(FontAwesomeIcons.someIcon),
onPressed: () {
// Action handler
},
),
],
),
),
)
Key Features:
- Uses
Containerwith bottom border decoration - Wrapped in
SafeAreafor proper top spacing - Height set to 56px (standard AppBar height)
- Padding adjusts for left/right spacing
- Border applied only to bottom (1.0px with outlineVariant color, matches bottom navigation bar)
- Title uses
titleLargetext style - Supports action buttons via
IconButtonor other widgets
Design Specifications
| Property | Value | Description |
|---|---|---|
| Height | 56px | Standard AppBar height |
| Border Position | Bottom only | Visual separation from content |
| Border Width | 1.0px | Subtle border matching bottom navigation bar |
| Border Color | colorScheme.outlineVariant |
Theme outlineVariant color (matches bottom nav) |
| Title Style | titleLarge |
Theme text style |
| Elevation | 0 | Flat design (no shadow) |
| Padding | Left: 12px, Right: 4px | Balanced spacing for text and icons |
When to Use Each Pattern
Use Pattern 1 (Scaffold AppBar) when:
- Building a standard screen with Scaffold
- Need standard AppBar features (back button, actions, etc.)
- Want Flutter's built-in AppBar behavior
Use Pattern 2 (Custom Header) when:
- Working without a Scaffold widget
- Building custom scrolling layouts
- Creating nested navigation structures
- Need more control over header layout and behavior
- Implementing complex header interactions (e.g., dropdown menus, category pickers)
Example: Header with Action Buttons
SafeArea(
child: Container(
height: 56,
padding: const EdgeInsets.only(right: 4, left: 12),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Theme.of(context).colorScheme.outlineVariant,
width: 1.0,
),
),
),
child: Row(
children: [
Expanded(
child: Text(
T.categories,
style: Theme.of(context)
.textTheme
.titleLarge
?.copyWith(fontWeight: FontWeight.w600),
overflow: TextOverflow.ellipsis,
),
),
IconButton(
icon: const FaIcon(FontAwesomeIcons.penToSquare),
onPressed: () {
// Create new item
},
),
IconButton(
icon: const FaIcon(FontAwesomeIcons.chevronDown),
onPressed: () {
// Show dropdown menu
},
),
],
),
),
)
Best Practices
- Always use Theme colors (outlineVariant for border, titleLarge for text)
- Always set border width to 1.0px for consistency with bottom navigation bar
- Always apply border only to the bottom (not top/left/right)
- Always use SafeArea when implementing custom headers
- Never add elevation or shadows
- Use
overflow: TextOverflow.ellipsisfor long titles - Use multiples of 8 for padding and spacing
- Maintain 56px height for consistency with standard AppBar
Comic Modal
Concept & Intent
The ComicModal is a reusable modal bottom sheet component that implements the Comic design language. It provides a consistent way to display modal content from the bottom of the screen with Comic-style rounded top corners, proper spacing, and an integrated drag handle.
Key Benefits:
- Consistency: All modals share the same Comic design DNA (2px border, no shadow, rounded top corners)
- Flexibility: Extensive customization options while maintaining Comic design principles
- Maintainability: Change the modal style in one place, update everywhere
- Theme Integration: Automatically uses Theme colors and text styles
- Easy to Use: Helper function
showComicModal()simplifies modal creation
Design Principles
All ComicModal instances follow these Comic design rules:
- Border: 2.0px thickness on top, left, and right sides (no bottom border) with outline color
- Elevation: Always 0 (flat design, no shadows)
- Corners: Rounded top corners with borderRadius 12
- Colors: Theme-based (surface background, outline border)
- Spacing: Consistent padding using multiples of 8
- Drag Handle: Custom drag handle (32px × 4px) with proper spacing inside the container
Usage Examples
Basic Modal
// Simple modal with basic content
showComicModal(
context: context,
builder: (context) => ComicModal(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.all(16),
child: Text(
'Modal Title',
style: Theme.of(context).textTheme.titleLarge,
),
),
Padding(
padding: EdgeInsets.fromLTRB(16, 0, 16, 16),
child: Text('Modal content goes here'),
),
],
),
),
);
Modal with List Items
// Modal with selectable list items
final result = await showComicModal<String>(
context: context,
builder: (context) => ComicModal(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.all(16),
child: Text(
'Select an option',
style: Theme.of(context).textTheme.titleLarge,
),
),
ListTile(
title: Text('Option 1'),
onTap: () => Navigator.pop(context, 'option1'),
),
ListTile(
title: Text('Option 2'),
onTap: () => Navigator.pop(context, 'option2'),
),
ListTile(
title: Text('Option 3'),
onTap: () => Navigator.pop(context, 'option3'),
),
],
),
),
);
if (result != null) {
print('Selected: $result');
}
Scrollable Modal with Long Content
// Modal with scrollable content
showComicModal(
context: context,
isScrollControlled: true,
builder: (context) => ComicModal(
maxHeightFactor: 0.9, // Use 90% of screen height
child: ListView.builder(
shrinkWrap: true,
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(
title: Text('Item $index'),
onTap: () => Navigator.pop(context),
);
},
),
),
);
Modal with Custom Padding
// Modal with custom content padding
showComicModal(
context: context,
builder: (context) => ComicModal(
contentPadding: EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Terms & Conditions',
style: Theme.of(context).textTheme.titleLarge,
),
SizedBox(height: 16),
Text('Your terms and conditions content here...'),
SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ComicButton(
onPressed: () => Navigator.pop(context, false),
child: Text('Decline'),
),
SizedBox(width: 8),
ComicPrimaryButton(
onPressed: () => Navigator.pop(context, true),
child: Text('Accept'),
),
],
),
],
),
),
);
Modal without Drag Handle
// Modal without drag handle (cleaner look for simple content)
showComicModal(
context: context,
builder: (context) => ComicModal(
showDragHandle: false,
contentPadding: EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Simple notification'),
SizedBox(height: 8),
ComicButton(
onPressed: () => Navigator.pop(context),
child: Text('OK'),
),
],
),
),
);
ComicModal Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| child | Widget | required | The content widget to display in the modal |
| showDragHandle | bool | true | Whether to show the drag handle indicator at the top |
| backgroundColor | Color? | null | Background color (default: Theme surface color) |
| borderColor | Color? | null | Border color (default: Theme outline color) |
| borderWidth | double | 2.0 | Border thickness (Comic standard) |
| borderRadius | double | 12 | Corner radius for top corners |
| contentPadding | EdgeInsetsGeometry? | null | Custom padding around content (optional) |
| maxHeightFactor | double? | null | Maximum height as fraction of screen (0.0 to 1.0) |
showComicModal() Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| context | BuildContext | required | BuildContext for showing the modal |
| builder | Function | required | Builder function that returns the modal content |
| isScrollControlled | bool | false | Whether modal should expand to fill available space |
| isDismissible | bool | true | Whether modal can be dismissed by tapping outside |
| enableDrag | bool | true | Whether modal can be dragged down to dismiss |
| useSafeArea | bool | true | Whether to wrap content in SafeArea |
Design Specifications
| Property | Value | Description |
|---|---|---|
| Border Position | Top, Left, Right | No border on bottom (seamless with screen edge) |
| Border Width | 2.0px | Comic standard thickness |
| Border Radius | 12px | Rounded top corners only |
| Border Color | colorScheme.outline |
Theme outline color |
| Background | colorScheme.surface |
Theme surface color |
| Elevation | 0 | Flat design (no shadow) |
| Drag Handle Width | 32px | Standard width for handle |
| Drag Handle Height | 4px | Standard height for handle |
| Drag Handle Spacing | 8px top & bottom | Multiples of 8 spacing |
| Drag Handle Color | onSurfaceVariant (40% opacity) |
Subtle indicator |
When to Use ComicModal
Use ComicModal when:
- Displaying action sheets or option menus
- Showing forms that slide up from bottom
- Presenting filters or settings panels
- Displaying contextual content that doesn't need full screen
- Creating slide-up confirmation dialogs
- Showing lists of selectable items
- Implementing bottom sheets for mobile-first design
Use ComicDialog instead when:
- Need center-screen alert/confirmation
- Content is critical and requires immediate attention
- Blocking user interaction is necessary
- Content is brief and doesn't benefit from bottom positioning
Best Practices
- Always use
showComicModal()helper function instead ofshowModalBottomSheet()directly - Always wrap modal content in
ComicModalwidget for consistent styling - Set
isScrollControlled: truewhen content might be taller than half screen - Use
maxHeightFactorto limit modal height for very long content - Set
shrinkWrap: truefor ListView/GridView inside modal - Wrap content in Column with
mainAxisSize: MainAxisSize.minfor auto-sizing - Use
contentPaddingfor consistent spacing or control padding in your content - Pass result via
Navigator.pop(context, value)to return data to caller - Use Theme colors and text styles for all content
- Test with SafeArea enabled (default) to ensure proper spacing on all devices
- Consider disabling drag handle (
showDragHandle: false) for simple, non-scrollable content
Comparison: Before vs After
❌ Before (Manual Styling)
// Old style: Manual border/styling in every modal
showModalBottomSheet(
context: context,
builder: (context) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
),
border: Border(
top: BorderSide(color: Theme.of(context).colorScheme.outline, width: 2),
left: BorderSide(color: Theme.of(context).colorScheme.outline, width: 2),
right: BorderSide(color: Theme.of(context).colorScheme.outline, width: 2),
),
),
child: // ... complex setup
);
},
);
✅ After (Comic Style)
// Comic style: Clean, consistent, reusable
showComicModal(
context: context,
builder: (context) => ComicModal(
child: // ... your content
),
);
File Location
ComicModal widget and helper function are defined in:
./lib/widgets/theme/comic_modal.dart
Import:
import 'package:philgo/widgets/theme/comic_modal.dart';
Comic SnackBar
Concept & Intent
The Comic SnackBar system provides helper functions to display notification messages that follow Comic design principles. Instead of using the standard ScaffoldMessenger.of(context).showSnackBar() with inline styling, use the pre-built Comic SnackBar functions for consistent, theme-based notifications.
Design Principles
All Comic SnackBars follow these design rules:
- Border: 2.0px thickness with semantic colors (green, red, blue, orange)
- Elevation: Always 0 (flat design, no shadows)
- Corners: Rounded with borderRadius 12
- Colors: Semantic colors with lower contrast for softer appearance:
- Success: Medium green border (#66BB6A) on very light green background (#F1F8F4)
- Error: Medium red border (#EF5350) on very light red background (#FFF5F5)
- Info: Medium blue border (#42A5F5) on very light blue background (#F3F8FC)
- Warning: Medium orange border (#FFA726) on very light orange background (#FFF8F0)
- Typography: Theme bodyMedium text style with medium-tone colored text for comfortable readability
- Spacing: Padding and margins in multiples of 8
- Behavior: Floating style with proper margins
Available Functions
showComicSuccessSnackBar
Displays a success message with green color scheme.
showComicSuccessSnackBar(context, T.profileUpdateSuccess);
Use Cases:
- Form submission success
- Profile update success
- Data saved successfully
- Action completed successfully
Visual Style:
- Background: Very light green (#F1F8F4)
- Text: Medium dark green (#2E7D32)
- Border: Medium green (#66BB6A, 2.0px)
showComicErrorSnackBar
Displays an error message with red color scheme.
showComicErrorSnackBar(context, T.nicknameRequired);
Use Cases:
- Validation errors
- Failed operations
- Required field warnings
- API errors
Visual Style:
- Background: Very light red (#FFF5F5)
- Text: Medium dark red (#C62828)
- Border: Medium red (#EF5350, 2.0px)
showComicInfoSnackBar
Displays an informational message with blue color scheme.
showComicInfoSnackBar(context, T.pleaseWait);
Use Cases:
- Informational messages
- Status updates
- General notifications
- Neutral messages
Visual Style:
- Background: Very light blue (#F3F8FC)
- Text: Medium dark blue (#1565C0)
- Border: Medium blue (#42A5F5, 2.0px)
showComicWarningSnackBar
Displays a warning message with orange color scheme.
showComicWarningSnackBar(context, T.pleaseCheckInput);
Use Cases:
- Warning messages
- Cautionary notices
- Input validation warnings
- Non-critical issues
Visual Style:
- Background: Very light orange (#FFF8F0)
- Text: Medium dark orange (#EF6C00)
- Border: Medium orange (#FFA726, 2.0px)
Usage Examples
Basic Success Message
// After successful form submission
final user = await philgoApiUserUpdate(data);
if (mounted) {
AppState.of(context).setUser(user);
showComicSuccessSnackBar(context, T.profileUpdateSuccess);
}
Validation Error
// Form validation
if (_nicknameController.text.trim().isEmpty) {
showComicErrorSnackBar(context, T.nicknameRequired);
return;
}
Error Handling
try {
await philgoApiFileDelete(photoUrl);
showComicSuccessSnackBar(context, T.photoDeleted);
} catch (e) {
showComicErrorSnackBar(context, 'Failed to delete: $e');
}
Info Message
// Processing indicator
showComicInfoSnackBar(context, T.processingRequest);
await performLongOperation();
Design Specifications
| Property | Value | Description |
|---|---|---|
| Border Width | 2.0px | Comic standard thickness |
| Border Radius | 12px | Rounded corners |
| Elevation | 0 | Flat design (no shadow) |
| Behavior | Floating | SnackBar floats above content |
| Margin | 16px all sides | Space from screen edges |
| Padding | Horizontal: 16px, Vertical: 12px | Content spacing |
| Text Style | bodyMedium | Theme text style |
Comparison: Before vs After
❌ Before (Non-Comic Style)
// Old style: Inline styling, no border, inconsistent
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(T.nicknameRequired),
backgroundColor: Theme.of(context).colorScheme.error,
),
);
✅ After (Comic Style)
// Comic style: Consistent design, proper border, theme-based
showComicErrorSnackBar(context, T.nicknameRequired);
Best Practices
- Always use Comic SnackBar functions instead of creating SnackBars manually
- Choose the appropriate function based on message type:
- Success:
showComicSuccessSnackBar - Error/Validation:
showComicErrorSnackBar - Info/Neutral:
showComicInfoSnackBar - Warning/Caution:
showComicWarningSnackBar
- Success:
- Pass localized text (T.xxx or Lo.xxx) instead of hardcoded strings
- Check
mountedbefore showing SnackBar in async operations - Never create SnackBars with inline styling or hardcoded colors
File Location
Comic SnackBar helper functions are defined in:
./lib/widgets/theme/comic_snackbar.dart
Import:
import 'package:philgo/widgets/theme/comic_snackbar.dart';
Scripts
- This skill does not provide scripts.