39 KiB
39 KiB
UI Design Guidelines - AI Study Assistant
🎨 DESIGN SYSTEM OVERVIEW
Core Design Philosophy
- Clean & Modern: Minimalist interface with purposeful use of color and space
- Educational Focus: UI elements designed to support learning, not distract
- Accessibility First: High contrast ratios, readable fonts, and clear visual hierarchy
- Responsive Design: Seamless experience across mobile, tablet, and web
🎨 COLOR PALETTE
Primary Colors (Extracted from EPVChat Design)
class AppColors {
// Primary Brand Colors
static const Color primaryBlue = Color(0xFF4A90E2); // Light blue from gradient
static const Color primaryTeal = Color(0xFF5AC8FA); // Teal/light blue from UI
static const Color primaryOrange = Color(0xFFFF9500); // Orange from logo
// Gradient Colors
static const Color gradientStart = Color(0xFF4A90E2); // Light blue
static const Color gradientEnd = Color(0xFF5AC8FA); // Teal
// Secondary Colors
static const Color secondaryBlue = Color(0xFF2E7CD6); // Darker blue
static const Color accentTeal = Color(0xFF30B0C7); // Deeper teal
// Neutral Colors
static const Color background = Color(0xFFF8F9FA); // Light gray background
static const Color surface = Color(0xFFFFFFFF); // White surfaces
static const Color cardBackground = Color(0xFFFFFFFF); // White cards
// Text Colors
static const Color textPrimary = Color(0xFF1A1A1A); // Primary text
static const Color textSecondary = Color(0xFF6B7280); // Secondary text
static const Color textHint = Color(0xFF9CA3AF); // Hint text
// Status Colors
static const Color success = Color(0xFF10B981); // Green for success
static const Color warning = Color(0xFFF59E0B); // Amber for warnings
static const Color error = Color(0xFFEF4444); // Red for errors
static const Color info = Color(0xFF3B82F6); // Blue for info
// Interactive Colors
static const Color buttonPrimary = Color(0xFF4A90E2); // Primary button
static const Color buttonSecondary = Color(0xFFE5E7EB); // Secondary button
static const Color iconActive = Color(0xFF4A90E2); // Active icons
static const Color iconInactive = Color(0xFF9CA3AF); // Inactive icons
// Chat Specific Colors
static const Color chatBubbleStudent = Color(0xFF4A90E2); // Student messages
static const Color chatBubbleAI = Color(0xFFF3F4F6); // AI messages
static const Color chatInputBackground = Color(0xFFF8F9FA); // Input background
static const Color chatSendButton = Color(0xFF5AC8FA); // Send button
}
Color Usage Guidelines
Primary Colors (70% usage)
- Primary Blue: Main actions, navigation, important CTAs
- Primary Teal: Secondary actions, accents, hover states
- Primary Orange: Highlights, achievements, important notifications
Neutral Colors (25% usage)
- Background: Page backgrounds, card backgrounds
- Surface: Cards, modals, dropdowns
- Text Colors: Hierarchical text organization
Status Colors (5% usage)
- Success: Quiz completion, correct answers
- Warning: Low mastery, upcoming deadlines
- Error: Network errors, validation failures
- Info: System notifications, help text
📝 TYPOGRAPHY
Font Family
class AppTypography {
// Primary Font Family (System fonts for consistency)
static const String primaryFont = 'SF Pro Display'; // iOS
static const String secondaryFont = 'Roboto'; // Android
static const String webFont = 'Inter'; // Web
// Font Weights
static const FontWeight thin = FontWeight.w100;
static const FontWeight light = FontWeight.w300;
static const FontWeight regular = FontWeight.w400;
static const FontWeight medium = FontWeight.w500;
static const FontWeight semiBold = FontWeight.w600;
static const FontWeight bold = FontWeight.w700;
static const FontWeight extraBold = FontWeight.w800;
}
Text Styles
class AppTextStyles {
// Headings
static const TextStyle h1 = TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
height: 1.2,
);
static const TextStyle h2 = TextStyle(
fontSize: 24,
fontWeight: FontWeight.semiBold,
color: AppColors.textPrimary,
height: 1.3,
);
static const TextStyle h3 = TextStyle(
fontSize: 20,
fontWeight: FontWeight.semiBold,
color: AppColors.textPrimary,
height: 1.4,
);
// Body Text
static const TextStyle bodyLarge = TextStyle(
fontSize: 16,
fontWeight: FontWeight.regular,
color: AppColors.textPrimary,
height: 1.5,
);
static const TextStyle bodyMedium = TextStyle(
fontSize: 14,
fontWeight: FontWeight.regular,
color: AppColors.textPrimary,
height: 1.5,
);
static const TextStyle bodySmall = TextStyle(
fontSize: 12,
fontWeight: FontWeight.regular,
color: AppColors.textSecondary,
height: 1.4,
);
// Button Text
static const TextStyle buttonLarge = TextStyle(
fontSize: 16,
fontWeight: FontWeight.semiBold,
color: Colors.white,
height: 1.2,
);
static const TextStyle buttonMedium = TextStyle(
fontSize: 14,
fontWeight: FontWeight.medium,
color: Colors.white,
height: 1.2,
);
// Caption and Label
static const TextStyle caption = TextStyle(
fontSize: 11,
fontWeight: FontWeight.regular,
color: AppColors.textHint,
height: 1.3,
);
static const TextStyle label = TextStyle(
fontSize: 12,
fontWeight: FontWeight.medium,
color: AppColors.textSecondary,
height: 1.3,
);
}
🎯 ICONOGRAPHY
Icon Sets
- Primary: Cupertino Icons (iOS-style consistency)
- Secondary: Material Icons (for Android compatibility)
- Custom: Educational icons for specific features
Icon Guidelines
- Size: 24px for standard icons, 16px for compact, 32px for large
- Weight: Medium (consistent across the app)
- Color: Use
iconActivefor active states,iconInactivefor inactive - Meaning: Clear, universally understood symbols
Key Icons
class AppIcons {
// Navigation
static const IconData home = CupertinoIcons.home;
static const IconData search = CupertinoIcons.search;
static const IconData profile = CupertinoIcons.person_circle;
static const IconData settings = CupertinoIcons.settings;
static const IconData menu = CupertinoIcons.bars;
// Actions
static const IconData send = CupertinoIcons.paperplane_fill;
static const IconData add = CupertinoIcons.add;
static const IconData edit = CupertinoIcons.pencil;
static const IconData delete = CupertinoIcons.trash;
static const IconData check = CupertinoIcons.check_mark;
// Educational
static const IconData book = CupertinoIcons.book;
static const IconData quiz = CupertinoIcons.question_circle;
static const IconData progress = CupertinoIcons.chart_bar;
static const IconData lightbulb = CupertinoIcons.lightbulb;
static const IconData trophy = CupertinoIcons.trophy;
// Status
static const IconData success = CupertinoIcons.check_mark_circled;
static const IconData warning = CupertinoIcons.exclamationmark_triangle;
static const IconData error = CupertinoIcons.xmark_circle;
static const IconData info = CupertinoIcons.info_circle;
}
📱 COMPONENTS
Buttons
Primary Button
class PrimaryButton extends StatelessWidget {
final String text;
final VoidCallback onPressed;
final bool isLoading;
final bool isDisabled;
const PrimaryButton({
Key? key,
required this.text,
required this.onPressed,
this.isLoading = false,
this.isDisabled = false,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
height: 48,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: isDisabled
? [AppColors.buttonSecondary, AppColors.buttonSecondary]
: [AppColors.gradientStart, AppColors.gradientEnd],
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: AppColors.primaryBlue.withOpacity(0.2),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(12),
onTap: isDisabled || isLoading ? null : onPressed,
child: Center(
child: isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation(Colors.white),
),
)
: Text(
text,
style: AppTextStyles.buttonLarge,
),
),
),
),
);
}
}
Secondary Button
class SecondaryButton extends StatelessWidget {
final String text;
final VoidCallback onPressed;
final bool isDisabled;
const SecondaryButton({
Key? key,
required this.text,
required this.onPressed,
this.isDisabled = false,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 48,
decoration: BoxDecoration(
color: AppColors.buttonSecondary,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: AppColors.primaryBlue.withOpacity(0.3),
width: 1,
),
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(12),
onTap: isDisabled ? null : onPressed,
child: Center(
child: Text(
text,
style: AppTextStyles.buttonLarge.copyWith(
color: isDisabled
? AppColors.textHint
: AppColors.primaryBlue,
),
),
),
),
),
);
}
}
Cards
Standard Card
class StandardCard extends StatelessWidget {
final Widget child;
final EdgeInsetsGeometry? padding;
final EdgeInsetsGeometry? margin;
final VoidCallback? onTap;
final bool hasShadow;
const StandardCard({
required this.child,
this.padding,
this.onTap,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 8,
offset: Offset(0, 2),
),
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 1,
offset: Offset(0, 1),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: padding ?? EdgeInsets.all(16),
child: child,
),
),
),
);
}
}
Interactive Card
class InteractiveCard extends StatefulWidget {
final Widget child;
final EdgeInsetsGeometry? padding;
final VoidCallback? onTap;
final bool isSelected;
const InteractiveCard({
required this.child,
this.padding,
this.onTap,
this.isSelected = false,
});
@override
State<InteractiveCard> createState() => _InteractiveCardState();
}
class _InteractiveCardState extends State<InteractiveCard> {
bool isHovered = false;
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) => setState(() => isHovered = true),
onExit: (_) => setState(() => isHovered = false),
child: AnimatedContainer(
duration: Duration(milliseconds: 200),
decoration: BoxDecoration(
color: widget.isSelected
? AppColors.primaryBlue.withOpacity(0.1)
: AppColors.surface,
borderRadius: BorderRadius.circular(16),
border: widget.isSelected
? Border.all(color: AppColors.primaryBlue, width: 2)
: null,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(isHovered ? 0.12 : 0.08),
blurRadius: isHovered ? 12 : 8,
offset: Offset(0, isHovered ? 4 : 2),
),
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 1,
offset: Offset(0, 1),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: widget.onTap,
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: widget.padding ?? EdgeInsets.all(16),
child: widget.child,
),
),
),
),
);
}
}
Input Fields
Text Input
class AppTextInput extends StatelessWidget {
final String? label;
final String? hint;
final String? errorText;
final TextEditingController controller;
final bool obscureText;
final TextInputType keyboardType;
final VoidCallback? onTap;
final ValueChanged<String>? onChanged;
final bool isDisabled;
const AppTextInput({
Key? key,
this.label,
this.hint,
this.errorText,
required this.controller,
this.obscureText = false,
this.keyboardType = TextInputType.text,
this.onTap,
this.onChanged,
this.isDisabled = false,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (label != null) ...[
Text(
label!,
style: AppTextStyles.label,
),
const SizedBox(height: 8),
],
Container(
decoration: BoxDecoration(
color: isDisabled ? AppColors.buttonSecondary : AppColors.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: errorText != null
? AppColors.error
: AppColors.primaryBlue.withOpacity(0.3),
width: 1,
),
),
child: TextField(
controller: controller,
obscureText: obscureText,
keyboardType: keyboardType,
onTap: onTap,
onChanged: onChanged,
enabled: !isDisabled,
decoration: InputDecoration(
hintText: hint,
hintStyle: AppTextStyles.bodyMedium.copyWith(
color: AppColors.textHint,
),
border: InputBorder.none,
contentPadding: const EdgeInsets.all(16),
),
style: AppTextStyles.bodyMedium,
),
),
if (errorText != null) ...[
const SizedBox(height: 4),
Text(
errorText!,
style: AppTextStyles.caption.copyWith(
color: AppColors.error,
),
),
],
],
);
}
}
📱 MODERN LAYOUT PATTERNS
Dashboard Layout
Based on the reference design, implement a modern card-based dashboard with:
Grid Layout System
class DashboardGrid extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(16),
child: GridView.count(
crossAxisCount: 2,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 1.2,
children: [
_buildQuickActionCard(
icon: Icons.chat,
title: "Ask Tutor",
subtitle: "Get help with your questions",
color: AppColors.primaryBlue,
onTap: () => _navigateToChat(),
),
_buildQuickActionCard(
icon: Icons.quiz,
title: "Take Quiz",
subtitle: "Test your knowledge",
color: AppColors.primaryTeal,
onTap: () => _navigateToQuiz(),
),
_buildQuickActionCard(
icon: Icons.trending_up,
title: "Progress",
subtitle: "Track your learning",
color: AppColors.primaryOrange,
onTap: () => _navigateToProgress(),
),
_buildQuickActionCard(
icon: Icons.book,
title: "Study Materials",
subtitle: "Access learning resources",
color: AppColors.secondaryBlue,
onTap: () => _navigateToMaterials(),
),
],
),
);
}
Widget _buildQuickActionCard({
required IconData icon,
required String title,
required String subtitle,
required Color color,
required VoidCallback onTap,
}) {
return InteractiveCard(
onTap: onTap,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
icon,
color: color,
size: 24,
),
),
SizedBox(height: 12),
Text(
title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
),
),
SizedBox(height: 4),
Text(
subtitle,
style: TextStyle(
fontSize: 12,
color: AppColors.textSecondary,
),
),
],
),
);
}
}
Content Cards
class ContentCard extends StatelessWidget {
final String title;
final String description;
final String progress;
final Color accentColor;
final VoidCallback onTap;
const ContentCard({
required this.title,
required this.description,
required this.progress,
required this.accentColor,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return InteractiveCard(
onTap: onTap,
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
),
),
SizedBox(height: 8),
Text(
description,
style: TextStyle(
fontSize: 14,
color: AppColors.textSecondary,
),
),
],
),
),
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: accentColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Icon(
Icons.arrow_forward,
color: accentColor,
size: 20,
),
),
],
),
SizedBox(height: 16),
Container(
height: 4,
decoration: BoxDecoration(
color: AppColors.buttonSecondary,
borderRadius: BorderRadius.circular(2),
),
child: FractionallySizedBox(
alignment: Alignment.centerLeft,
widthFactor: double.parse(progress),
child: Container(
decoration: BoxDecoration(
color: accentColor,
borderRadius: BorderRadius.circular(2),
),
),
),
),
SizedBox(height: 8),
Text(
'${(double.parse(progress) * 100).toInt()}% Complete',
style: TextStyle(
fontSize: 12,
color: AppColors.textHint,
),
),
],
),
);
}
}
Navigation Bar
class ModernBottomNavigation extends StatelessWidget {
final int currentIndex;
final ValueChanged<int> onTap;
const ModernBottomNavigation({
required this.currentIndex,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: AppColors.surface,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: Offset(0, -2),
),
],
),
child: SafeArea(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildNavItem(
icon: Icons.home,
label: "Home",
index: 0,
),
_buildNavItem(
icon: Icons.chat,
label: "Chat",
index: 1,
),
_buildNavItem(
icon: Icons.quiz,
label: "Quiz",
index: 2,
),
_buildNavItem(
icon: Icons.person,
label: "Profile",
index: 3,
),
],
),
),
),
);
}
Widget _buildNavItem({
required IconData icon,
required String label,
required int index,
}) {
final isActive = currentIndex == index;
final color = isActive ? AppColors.primaryBlue : AppColors.iconInactive;
return GestureDetector(
onTap: () => onTap(index),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: isActive ? AppColors.primaryBlue.withOpacity(0.1) : Colors.transparent,
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
color: color,
size: 24,
),
SizedBox(height: 4),
Text(
label,
style: TextStyle(
color: color,
fontSize: 12,
fontWeight: isActive ? FontWeight.w600 : FontWeight.normal,
),
),
],
),
),
);
}
}
Chat Interface
class ModernChatInterface extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
// Chat Header
Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.surface,
border: Border(
bottom: BorderSide(
color: AppColors.buttonSecondary,
width: 1,
),
),
),
child: Row(
children: [
CircleAvatar(
backgroundColor: AppColors.primaryBlue.withOpacity(0.1),
child: Icon(
Icons.smart_toy,
color: AppColors.primaryBlue,
),
),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"AI Tutor",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
),
),
Text(
"Always here to help",
style: TextStyle(
fontSize: 12,
color: AppColors.textSecondary,
),
),
],
),
),
Icon(
Icons.more_vert,
color: AppColors.iconInactive,
),
],
),
),
// Chat Messages
Expanded(
child: Container(
color: AppColors.background,
child: ListView.builder(
padding: EdgeInsets.all(16),
itemCount: messages.length,
itemBuilder: (context, index) {
final message = messages[index];
return _buildMessageBubble(message);
},
),
),
),
// Chat Input
Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.surface,
border: Border(
top: BorderSide(
color: AppColors.buttonSecondary,
width: 1,
),
),
),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
color: AppColors.chatInputBackground,
borderRadius: BorderRadius.circular(24),
border: Border.all(
color: AppColors.primaryBlue.withOpacity(0.3),
width: 1,
),
),
child: TextField(
decoration: InputDecoration(
hintText: "Ask your question...",
hintStyle: TextStyle(
color: AppColors.textHint,
),
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
),
),
),
),
SizedBox(width: 12),
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [AppColors.primaryBlue, AppColors.primaryTeal],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
borderRadius: BorderRadius.circular(24),
),
child: Icon(
Icons.send,
color: Colors.white,
size: 20,
),
),
],
),
),
],
);
}
Widget _buildMessageBubble(Message message) {
final isUser = message.isUser;
return Container(
margin: EdgeInsets.only(bottom: 16),
child: Row(
mainAxisAlignment: isUser ? MainAxisAlignment.end : MainAxisAlignment.start,
children: [
if (!isUser) ...[
CircleAvatar(
radius: 16,
backgroundColor: AppColors.primaryBlue.withOpacity(0.1),
child: Icon(
Icons.smart_toy,
color: AppColors.primaryBlue,
size: 16,
),
),
SizedBox(width: 8),
],
Flexible(
child: Container(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.7,
),
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: isUser ? AppColors.primaryBlue : AppColors.chatBubbleAI,
borderRadius: BorderRadius.circular(16),
),
child: Text(
message.text,
style: TextStyle(
color: isUser ? Colors.white : AppColors.textPrimary,
fontSize: 14,
),
),
),
),
if (isUser) ...[
SizedBox(width: 8),
CircleAvatar(
radius: 16,
backgroundColor: AppColors.primaryOrange.withOpacity(0.1),
child: Icon(
Icons.person,
color: AppColors.primaryOrange,
size: 16,
),
),
],
],
),
);
}
}
🎭 ANIMATIONS
Animation Principles
- Purposeful: Every animation should enhance user understanding
- Smooth: Use easing functions that feel natural
- Fast: Respect user time, keep animations under 300ms
- Consistent: Use similar timing across the app
Standard Animation Durations
class AppAnimations {
static const Duration fast = Duration(milliseconds: 150);
static const Duration medium = Duration(milliseconds: 250);
static const Duration slow = Duration(milliseconds: 350);
static const Duration extraSlow = Duration(milliseconds: 500);
}
Curves
class AppCurves {
static const Curve easeIn = Curves.easeIn;
static const Curve easeOut = Curves.easeOut;
static const Curve easeInOut = Curves.easeInOut;
static const Curve bounceIn = Curves.bounceIn;
static const Curve elasticOut = Curves.elasticOut;
}
Common Animations
Fade Transition
class FadeIn extends StatefulWidget {
final Widget child;
final Duration duration;
const FadeIn({
Key? key,
required this.child,
this.duration = AppAnimations.medium,
}) : super(key: key);
@override
State<FadeIn> createState() => _FadeInState();
}
class _FadeInState extends State<FadeIn>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.duration,
vsync: this,
);
_animation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: AppCurves.easeIn,
));
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _animation,
child: widget.child,
);
}
}
Slide Transition
class SlideIn extends StatefulWidget {
final Widget child;
final Duration duration;
final Offset begin;
final Offset end;
const SlideIn({
Key? key,
required this.child,
this.duration = AppAnimations.medium,
this.begin = const Offset(0, 0.3),
this.end = Offset.zero,
}) : super(key: key);
@override
State<SlideIn> createState() => _SlideInState();
}
class _SlideInState extends State<SlideIn>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Offset> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.duration,
vsync: this,
);
_animation = Tween<Offset>(
begin: widget.begin,
end: widget.end,
).animate(CurvedAnimation(
parent: _controller,
curve: AppCurves.easeOut,
));
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SlideTransition(
position: _animation,
child: widget.child,
);
}
}
Scale Animation
class ScaleIn extends StatefulWidget {
final Widget child;
final Duration duration;
const ScaleIn({
Key? key,
required this.child,
this.duration = AppAnimations.medium,
}) : super(key: key);
@override
State<ScaleIn> createState() => _ScaleInState();
}
class _ScaleInState extends State<ScaleIn>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.duration,
vsync: this,
);
_animation = Tween<double>(
begin: 0.8,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: AppCurves.elasticOut,
));
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ScaleTransition(
scale: _animation,
child: widget.child,
);
}
}
📐 LAYOUT GUIDELINES
Spacing System
class AppSpacing {
// Base spacing unit (8px)
static const double xs = 4; // 0.5x
static const double sm = 8; // 1x
static const double md = 16; // 2x
static const double lg = 24; // 3x
static const double xl = 32; // 4x
static const double xxl = 48; // 6x
static const double xxxl = 64; // 8x
}
Border Radius
class AppBorderRadius {
static const double sm = 8;
static const double md = 12;
static const double lg = 16;
static const double xl = 20;
static const double xxl = 24;
static const double round = 1000; // For circles
}
Screen Breakpoints
class AppBreakpoints {
static const double mobile = 480;
static const double tablet = 768;
static const double desktop = 1024;
static const double largeDesktop = 1440;
}
🎨 SCREEN-SPECIFIC DESIGNS
Login Screen
- Background: Gradient from
gradientStarttogradientEnd - Logo: Centered with fade-in animation
- Form: White card with subtle shadow
- Buttons: Primary gradient button for login, secondary for signup
Student Dashboard
- Header: App bar with gradient background
- Cards: Grid layout with mastery progress bars
- Navigation: Bottom navigation with active state indicators
- Animations: Cards slide in from bottom on load
Chat Interface
- Background: Light gray (
chatInputBackground) - Messages: Different colors for student vs AI
- Input: Rounded input field with send button
- Animations: Messages slide in from sides
Quiz Screen
- Timer: Circular progress indicator
- Questions: Card-based layout
- Options: Radio buttons with hover states
- Progress: Linear progress bar at top
🌙 DARK MODE
Dark Color Palette
class AppDarkColors {
// Background Colors
static const Color background = Color(0xFF121212);
static const Color surface = Color(0xFF1E1E1E);
static const Color cardBackground = Color(0xFF2D2D2D);
// Text Colors
static const Color textPrimary = Color(0xFFFFFFFF);
static const Color textSecondary = Color(0xFFB3B3B3);
static const Color textHint = Color(0xFF808080);
// Keep primary colors the same
static const Color primaryBlue = AppColors.primaryBlue;
static const Color primaryTeal = AppColors.primaryTeal;
static const Color primaryOrange = AppColors.primaryOrange;
}
Implementation Guidelines
- Automatic: Follow system dark mode preference
- Manual: Toggle in app settings
- Consistency: All screens must support both themes
- Animation: Smooth transition between themes
📱 RESPONSIVE DESIGN
Mobile (< 768px)
- Layout: Single column, scrollable
- Navigation: Bottom navigation bar
- Cards: Full width with horizontal padding
- Typography: Base sizes as defined
Tablet (768px - 1024px)
- Layout: Two-column where appropriate
- Navigation: Side drawer or top navigation
- Cards: Grid layout (2-3 columns)
- Typography: Slightly larger base sizes
Desktop (> 1024px)
- Layout: Multi-column, fixed width centered
- Navigation: Top navigation bar
- Cards: Grid layout (3-4 columns)
- Typography: Larger base sizes, better readability
🎯 ACCESSIBILITY
Color Contrast
- Normal Text: Minimum 4.5:1 contrast ratio
- Large Text: Minimum 3:1 contrast ratio
- Interactive Elements: Minimum 3:1 contrast ratio
Typography
- Minimum Size: 14px for body text
- Line Height: 1.5 for better readability
- Font Weight: Regular or medium for body text
Focus States
- Visible: All interactive elements have visible focus states
- Consistent: Same focus style across the app
- Keyboard: Full keyboard navigation support
Screen Reader Support
- Labels: All images and icons have alt text
- Semantic: Use semantic HTML elements
- Announcements: Important changes are announced
🚀 PERFORMANCE
Animation Performance
- 60 FPS: All animations should maintain 60 FPS
- GPU: Use GPU-accelerated animations where possible
- Simplify: Avoid complex animations on low-end devices
Asset Optimization
- Images: WebP format with appropriate sizes
- Icons: SVG icons where possible
- Fonts: Subset fonts to reduce size
Bundle Size
- Tree Shaking: Remove unused code
- Lazy Loading: Load features on demand
- Compression: Compress all assets
📋 DESIGN CHECKLIST
Before Implementation
- Color palette defined and consistent
- Typography scale established
- Component library created
- Animation system implemented
- Accessibility guidelines reviewed
During Implementation
- Components follow design system
- Animations are smooth and purposeful
- Responsive design works on all breakpoints
- Dark mode is supported
- Accessibility features are implemented
Before Release
- All screens reviewed for consistency
- Performance tested on target devices
- Accessibility tested with screen readers
- User testing completed
- Design documentation updated
🔄 DESIGN SYSTEM MAINTENANCE
Regular Updates
- Monthly: Review usage analytics and user feedback
- Quarterly: Update components and add new ones
- Annually: Major design system updates
Version Control
- Semantic Versioning: Use semantic versioning for releases
- Changelog: Maintain detailed changelog
- Migration: Provide migration guides for breaking changes
Documentation
- Living Document: Keep documentation up to date
- Examples: Provide code examples for all components
- Guidelines: Maintain usage guidelines and best practices
Last Updated: 2026-05-06 Version: 1.0.0 Design System Owner: UI/UX Team