Primitives
Primitive components are the fundamental building blocks of the BookWish UI. These components are used throughout the application and form the foundation for more complex components.
Buttons
BookWish uses three primary button variants, each with distinct visual styles and use cases.
ElevatedButton (Primary)
The primary call-to-action button with Amber Star background.
Visual Style:
- Background: Amber Star (#FFC857)
- Text Color: Ink Blue (#233548)
- Height: 48px minimum
- Border Radius: 24px (fully rounded)
- Elevation: 0 (flat design)
- Full width by default
Usage:
- Primary actions (Sign Up, Save, Submit)
- One primary button per screen section
- Most important user action
Props:
ElevatedButton(
onPressed: () => // action,
child: Text('Button Label'),
)
Example:
ElevatedButton(
onPressed: () => handleSubmit(),
child: const Text('Add to Wishlist'),
)
OutlinedButton (Secondary)
Secondary actions with Ink Blue outline.
Visual Style:
- Background: Transparent
- Border: 1px Ink Blue (#233548)
- Text Color: Ink Blue (#233548)
- Height: 48px minimum
- Border Radius: 24px (fully rounded)
- Full width by default
Usage:
- Secondary actions (Cancel, Skip)
- Alternative actions paired with primary button
- Less critical actions
Props:
OutlinedButton(
onPressed: () => // action,
child: Text('Button Label'),
)
Example:
OutlinedButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
)
Special Variant - Follow Button:
The FollowButton component uses OutlinedButton with dynamic styling:
// Following state
backgroundColor: surfaceContainerHighest
foregroundColor: onSurface
text: "Following"
// Not following state
backgroundColor: primary (Ink Blue)
foregroundColor: onPrimary (White)
text: "Follow"
TextButton (Tertiary)
Minimal button for tertiary actions.
Visual Style:
- Background: Transparent
- Text Color: Ink Blue (#233548)
- No border
- Height: Auto-sized to content
- No background or border
Usage:
- Tertiary actions (Learn More, See All)
- Navigation within dialogs
- Less prominent actions
Props:
TextButton(
onPressed: () => // action,
child: Text('Button Label'),
)
Example:
TextButton(
onPressed: () => showHelp(),
child: const Text('Learn More'),
)
Button States
All buttons support standard Flutter states:
- Enabled: Default appearance
- Disabled:
onPressed: null- Reduced opacity - Pressed: Material ripple effect
- Loading: Show CircularProgressIndicator
Loading State Example:
ElevatedButton(
onPressed: isLoading ? null : handleSubmit,
child: isLoading
? const CircularProgressIndicator()
: const Text('Submit'),
)
Text Fields
Input fields for user data entry with consistent styling.
Visual Style:
- Background: White (#FFFFFF)
- Border: 1px Border color (#E0D7C8)
- Border Radius: 12px
- Padding: 16px horizontal and vertical
- Focus Border: 2px Ink Blue (#233548)
- Error Border: 1px Error Red (#C44545)
- Focus Error Border: 2px Error Red (#C44545)
Usage:
- Text input
- Email input
- Password input
- Search fields
- Any user text entry
Props:
TextField(
decoration: InputDecoration(
labelText: 'Label',
hintText: 'Hint text',
errorText: 'Error message',
),
controller: controller,
onChanged: (value) => // handle change,
)
Example:
TextField(
decoration: const InputDecoration(
labelText: 'Email',
hintText: 'Enter your email address',
),
keyboardType: TextInputType.emailAddress,
controller: emailController,
)
Text Field States
- Default: Border color border
- Focused: Ink Blue border, 2px width
- Error: Error Red border
- Disabled: Reduced opacity
Chips
Compact elements for tags, filters, and metadata display.
StoreChip
Display store information compactly.
Visual Style:
- Background:
surfaceContainerHighest(light gray) - Padding: 12px horizontal, 6px vertical
- Border Radius: 16px (pill shape)
- Icon Size: 20px
- Text: AppTypography.labelSmall with medium weight
Usage:
- Store identification
- Store badges
- Store references in lists
Props:
final Store store;
final VoidCallback? onTap;
Example:
StoreChip(
store: bookstore,
onTap: () => navigateToStore(bookstore.id),
)
Generic Chip Pattern
For creating other chip types:
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 20),
const SizedBox(width: 6),
Text(label, style: AppTypography.labelSmall),
],
),
)
Cards
Content containers with consistent styling throughout the app.
Visual Style:
- Background: White (#FFFFFF)
- Elevation: 2
- Shadow: Black at 8% opacity
- Border Radius: 12px
- Border: 1px Border color (#E0D7C8) on ListTile variant
- Horizontal Margin: 16px
- Vertical Margin: 6px between cards
Standard Card Pattern
Used in most card components (ReviewCard, ClubCard, etc.):
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
child: Material(
elevation: 2,
shadowColor: Colors.black.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(12),
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(16),
child: // Card content
),
),
)
ListTile Card Pattern
Used in tile-based components (InventoryItemTile, WishlistItemTile):
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
child: Material(
elevation: 2,
shadowColor: Colors.black.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(12),
color: Colors.white,
child: ListTile(
// ListTile configuration
),
),
)
Usage:
- Container for related content
- Grouping information
- Interactive content blocks
- List items
Card Content Styling
Content within cards uses card-specific typography:
// Heading
Text(heading, style: AppTypography.cardHeading)
// Body text
Text(content, style: AppTypography.cardBody)
// Labels
Text(label, style: AppTypography.cardLabel)
// Captions and metadata
Text(metadata, style: AppTypography.cardCaption)
Avatars
User profile images in circular containers.
Visual Style:
- Shape: Circle (CircleAvatar)
- Fallback: User initial on colored background
- Fallback Text: AppTypography.cardLabel
Sizes
Standard Size (32px radius)
CircleAvatar(
radius: 16,
backgroundImage: user.avatarUrl != null
? NetworkImage(user.avatarUrl!)
: null,
child: user.avatarUrl == null
? Text(
(user.displayName ?? 'U')[0].toUpperCase(),
style: AppTypography.cardLabel.copyWith(fontSize: 12),
)
: null,
)
Usage:
- User identification in UserChip
- Comment and review authors
- Follower/following lists
- User profile references
Avatar Fallback
When no image is available:
- Display first letter of username or display name
- Use card label typography
- Size appropriate to container
Toggles
Boolean controls for settings and preferences.
Visual Style:
- Uses Flutter's Switch widget
- Active Color: Teal Edge (#4BB4C8)
- Inactive Track: Light gray
- Standard Material Design appearance
Usage:
- Settings toggles
- Feature enable/disable
- Preference controls
- Binary state changes
Example:
Switch(
value: isEnabled,
onChanged: (value) => setState(() => isEnabled = value),
)
In List Context:
SwitchListTile(
title: const Text('Enable Notifications'),
value: notificationsEnabled,
onChanged: (value) => updateNotificationSetting(value),
)
Loading Indicators
Provide feedback during asynchronous operations.
CircularProgressIndicator
Standard loading spinner.
Visual Style:
- Primary Color: Ink Blue (#233548)
- Standard Material Design appearance
- Various sizes for different contexts
Usage:
- Page loading states
- Button loading states
- Data fetching
- Any asynchronous operation
Full Page Loading:
Center(
child: CircularProgressIndicator(),
)
Button Loading:
SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
)
Inline Loading:
CircularProgressIndicator()
Image Loading
For network images, use CachedNetworkImage with placeholder:
CachedNetworkImage(
imageUrl: imageUrl,
placeholder: (context, url) => const Center(
child: CircularProgressIndicator(),
),
errorWidget: (context, url, error) => Icon(fallbackIcon),
)
Rating
Star-based rating display and input.
Display-Only Rating
Used in ReviewCard to show ratings:
Visual Style:
- Icon: CupertinoIcons.star_fill (filled) / CupertinoIcons.star (empty)
- Color: Amber (#FFC107 - Flutter's amber)
- Size: 18px
- Layout: Horizontal row of 5 stars
Implementation:
class _RatingStars extends StatelessWidget {
final int rating;
const _RatingStars({required this.rating});
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(5, (index) {
return Icon(
index < rating ? CupertinoIcons.star_fill : CupertinoIcons.star,
size: 18,
color: Colors.amber,
);
}),
);
}
}
Usage:
- Review ratings
- Book ratings
- Rating displays
- Read-only rating visualization
Interactive Rating
For rating input, create tappable stars:
Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(5, (index) {
return IconButton(
icon: Icon(
index < currentRating
? CupertinoIcons.star_fill
: CupertinoIcons.star,
color: Colors.amber,
),
onPressed: () => setRating(index + 1),
);
}),
)
Icon Usage
BookWish primarily uses Cupertino (iOS-style) icons for a modern, clean aesthetic.
Common Icons
Books:
CupertinoIcons.book_fill- Book representationCupertinoIcons.book- Outlined book
Social:
CupertinoIcons.heart_fill- Liked stateCupertinoIcons.heart- Unliked stateCupertinoIcons.person_3_fill- Book clubs, groupsCupertinoIcons.person_2_fill- Users, members
Actions:
CupertinoIcons.chevron_right- Navigation forwardCupertinoIcons.ellipsis- More options menuCupertinoIcons.flag- Report/flag contentCupertinoIcons.trash_fill- Delete action
Status:
CupertinoIcons.star_fill- Featured, priority, ratingsCupertinoIcons.star- Unfilled starCupertinoIcons.check_mark_circled_solid- CompletedCupertinoIcons.exclamationmark- High priority
Store:
CupertinoIcons.building_2_fill- Store/business
Content:
CupertinoIcons.doc_text_fill- Notes (with content)CupertinoIcons.doc_text- Notes (empty)
Icon Sizing
- Small: 16-18px - In-line with text, metadata
- Medium: 20-24px - Standard icon buttons
- Large: 32-48px - Empty states, placeholders
Spacing System
BookWish uses a consistent spacing system based on 4px increments.
Standard Spacing Values
- 4px: Minimal spacing, tight groupings
- 6px: Vertical spacing between cards
- 8px: Small gaps, inline elements
- 12px: Medium gaps, related elements
- 16px: Standard padding, horizontal margins
- 24px: Large gaps, section separation
Common Patterns
Card Padding:
padding: const EdgeInsets.all(16)
Card Margins:
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6)
Element Spacing:
const SizedBox(height: 8)
const SizedBox(width: 12)
Border Radius System
Consistent corner rounding throughout the application.
Standard Values
- 4px: Small images, thumbnails
- 8px: Nested containers, mini chips
- 12px: Cards, containers, text fields
- 16px: Chips, tags
- 20px: Ink wells, interactive feedback
- 24px: Buttons (fully rounded)
Usage
Cards:
borderRadius: BorderRadius.circular(12)
Buttons:
borderRadius: BorderRadius.circular(24)
Chips:
borderRadius: BorderRadius.circular(16)
Elevation System
Two-level elevation system for depth and hierarchy.
Elevation Levels
Level 0: Flat elements on Parchment background
- Buttons (elevation: 0)
- Flat surfaces
Level 2: Cards and interactive elements
- Cards (elevation: 2)
- Tiles (elevation: 2)
- Floating action buttons
Shadow Styling
Standard shadow for elevated elements:
shadowColor: Colors.black.withValues(alpha: 0.08)
This creates a subtle shadow that adds depth without overwhelming the design.
Accessibility
All primitive components follow accessibility best practices:
Touch Targets
Minimum touch target size: 44x44 points
- Buttons: 48px minimum height
- IconButtons: 44x44 minimum
- Interactive elements: Adequate padding
Color Contrast
All text meets WCAG AA standards:
- Ink Blue on Parchment: Sufficient contrast
- Ink Blue on White: Excellent contrast
- White on Ink Blue: Excellent contrast
- Disabled states: Clearly distinguishable
Semantic Labels
Provide meaningful labels for interactive elements:
IconButton(
icon: Icon(CupertinoIcons.heart),
onPressed: onLike,
tooltip: 'Like this review',
)
Keyboard Navigation
Support keyboard navigation where applicable:
- Tab through interactive elements
- Enter/Space to activate buttons
- Escape to dismiss modals
Best Practices
Do's
- Use primitives consistently throughout the app
- Follow the established spacing system
- Provide loading and error states
- Include proper accessibility labels
- Use AppColors and AppTypography constants
- Test with different content lengths
Don'ts
- Don't create custom button styles
- Don't hardcode spacing values outside the system
- Don't skip accessibility considerations
- Don't assume text fits on one line
- Don't use colors outside the palette
- Don't skip loading states for async operations