Master Design System & Component Reusable
Perjalanan dari Design System hingga Production-Grade App
Fondasi konsistensi visual & behavior aplikasi yang scalable
Psikologi warna & kemampuan readable untuk UX maksimal
Widget reusable yang mempercepat development & maintain
Light & Dark mode yang seamless untuk semua device
Loading, Splash, Empty states yang engaging & informatif
Aplikasi E-Commerce UI dengan semua konsep terintegrasi
Analogi Rumah: Apa Bedanya?
= Tampilan Rumah
✓ Warna cat dinding
✓ Furnitur & dekorasi
✓ Pintu & jendela
✓ Tata letak interior
= Kenyamanan Tinggal
✓ Mudah navigasi
✓ Ventilasi baik
✓ Privasi terjamin
✓ Efisien & praktis
💡 Rumah mewah (UI bagus) tapi sulit navigasi (UX buruk)? Orang tetap pilih rumah sederhana yang nyaman!
= Resep Restoran Bintang Lima
Palet warna terdefinisi untuk konsistensi visual
Font sizes & weights yang harmonis & readable
Margin & padding proporsional (8px base grid)
Widget reusable dengan consistent behavior
Guideline: minimalist, accessible, performant
Semua rules tercatat untuk team alignment
Setiap warna membangkitkan perasaan & psikologi berbeda
FF6B6B
🔥 Energy, Passion, Alert
5E72E4
💜 Trust, Creative, Premium
00D4FF
🌊 Fresh, Modern, Info
Font yang tepat membuat teks lebih readable & memorable
POPPINS
Display font — Bold, eye-catching, headlines (Poppins 900)
Inter Regular — Comfortable for body text and description. Highly readable and accessible at any size. Perfect for main content.
Body text, description (Inter 400)
const button = () => <Button />
Code block, technical info (Space Mono)
= LEGO Block: Bangun UI kompleks dari blok sederhana
Tidak perlu membuat widget dari nol berulang kali
Semua button, card, input terlihat & behave sama
Ubah 1 component → seluruh app terupdate
Code terstruktur, mudah dibaca & dimodifikasi
Unit test per component lebih efisien
Tambah fitur tanpa breaking existing code
class CustomButton extends StatelessWidget {
final String label;
final VoidCallback onPressed;
final Color bgColor;
const CustomButton({
required this.label,
required this.onPressed,
this.bgColor = Colors.blue,
});
@override
Widget build(BuildContext context) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: bgColor,
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 14),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
onPressed: onPressed,
child: Text(label, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
);
}
}
Satu toggle, seluruh app ganti warna — Dark/Light mode seamless
Cocok untuk siang hari, kontras tinggi, mata fresh
✓ Tinggi perceived brightness
✓ Kontras maksimal
✓ Ideal untuk outdoor
Cocok untuk malam hari, hemat baterai, estetik premium
✓ Mengurangi eye strain
✓ Hemat baterai OLED
✓ Lebih premium modern
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(brightness: Brightness.light, primaryColor: Colors.blue),
darkTheme: ThemeData(brightness: Brightness.dark, primaryColor: Colors.blueAccent),
themeMode: ThemeMode.system,
home: HomeScreen(),
);
}
}
Implementasi dark mode yang seamless untuk UX & aksesibilitas
Mata lebih nyaman baca di kondisi gelap
OLED screen hemat 15-20% battery
Dark mode terlihat premium & modern
⚠️ Pro Tip: Gunakan dark gray (#121212) bukan black (#000000) untuk less eye strain
Animasi membuat UI lebih engaging, natural, dan memorable
Elemen gradually muncul/hilang dengan smooth opacity
Elemen bergerak dari posisi ke posisi lain
Elemen membesar/mengecil dengan smooth transform
Elemen berputar smooth tanpa jerky motion
Elemen bounce dengan overshoot untuk kesan fun
Layers bergerak beda kecepatan untuk depth
⚡ Duration: 200-500ms micro-interactions, 500-1000ms major animations
Widget yang otomatis animate saat state berubah
Size, warna, position smooth saat state update
Transparansi berubah gradual tanpa controller
Posisi di Stack berubah smooth
Alignment dari child berubah smooth
Custom property terupdate smooth
Text style berubah smooth tanpa rebuild
class ImplicitAnimDemo extends StatefulWidget {
@override
State<ImplicitAnimDemo> createState() => _ImplicitAnimDemoState();
}
class _ImplicitAnimDemoState extends State<ImplicitAnimDemo> {
bool isExpanded = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => setState(() => isExpanded = !isExpanded),
child: AnimatedContainer(
duration: Duration(milliseconds: 500),
curve: Curves.easeInOut,
width: isExpanded ? 200 : 100,
height: isExpanded ? 200 : 100,
decoration: BoxDecoration(
color: isExpanded ? Colors.blue : Colors.red,
borderRadius: BorderRadius.circular(isExpanded ? 20 : 10),
),
child: Center(child: Text('TAP')),
),
);
}
}
Kontrol penuh + magic shared element transition
Kontrol penuh: forward, reverse, repeat, stop
✓ Duration custom
✓ Curve control
✓ Listener callback
✓ Start/stop kapan saja
Elemen "terbang" antar screen dengan smooth
✓ Shared element
✓ Smooth morphing
✓ Zero setup
✓ Works with Navigator
// Screen 1: Image dengan Hero wrapper
Hero(
tag: 'product-image',
child: Image.asset('assets/product.png', width: 100, height: 100),
)
// Screen 2: Sama tag, ukuran beda → otomatis terbang & morph!
Hero(
tag: 'product-image',
child: Image.asset('assets/product.png', width: 300, height: 300),
)
Kesan pertama penting! Splash screen yang baik meningkatkan perceived quality
✓ Brand showcase
✓ Loading prep
✓ Emotional hook
2-3 detik cukup untuk kesan tapi tidak mengganggu
✓ Minimal
✓ Logo besar
✓ Subtle animation
class SplashScreen extends StatefulWidget {
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(duration: Duration(seconds: 2), vsync: this)..forward();
Future.delayed(Duration(seconds: 3), () {
Navigator.pushReplacementNamed(context, '/home');
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue,
body: Center(
child: FadeTransition(
opacity: _controller,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('assets/logo.png', width: 120),
SizedBox(height: 20),
Text('MyApp', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white)),
],
),
),
),
);
}
}
Jangan biarkan user bingung — guide mereka dengan empati & action
Gambar/icon lucu & relatable untuk kesan friendly
Jelaskan kenapa kosong & apa yang bisa dilakukan
Tombol prominent untuk trigger action relevan
❌ Jangan: "No data" kosong
✅ Lakukan: "Belum ada order 📦 Mulai belanja sekarang!" + button
Beri user feedback bahwa app "bekerja" — jangan biarkan layar membeku
Lingkaran berputar — simple & efektif untuk loading umum
Garis yang fill untuk show progress pada long task
Placeholder layout sebelum data nyata — terasa lebih cepat!
Efek "kilau" moving di skeleton untuk sophisticated feel
💡 Skeleton loading terasa 40% lebih cepat dibanding spinner kosong!
Aplikasikan SEMUA konsep menjadi aplikasi nyata production-grade!
Animasi loading dengan logo, brand message, 2-3 detik
AppBar custom, banner promo, category chips, featured products
GridView dengan ProductCard reusable, filter, search
Galeri foto, rating, deskripsi lengkap, add to cart
List item, quantity control, total, checkout button
Avatar, info, order history, dark mode toggle
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('E-Commerce', style: TextStyle(fontWeight: FontWeight.bold)),
actions: [IconButton(icon: Icon(Icons.shopping_cart), onPressed: () {})],
),
body: SingleChildScrollView(
child: Column(children: [
Container(height: 180, color: Colors.blueAccent,
child: Center(child: Text('Special Promo 50%', style: TextStyle(color: Colors.white, fontSize: 24)))),
GridView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemCount: 10,
itemBuilder: (context, index) => ProductCard(id: index),
),
]),
),
);
}
}
class ProductCard extends StatelessWidget {
final int id;
const ProductCard({required this.id});
@override
Widget build(BuildContext context) {
return Card(
child: Column(children: [
Expanded(child: Container(color: Colors.grey[300], child: Icon(Icons.image, size: 50))),
Padding(padding: EdgeInsets.all(8),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text('Product $id', style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(height: 4),
Text('Rp 150.000', style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold)),
SizedBox(height: 4),
Row(children: [
Icon(Icons.star, size: 14, color: Colors.amber),
SizedBox(width: 4),
Text('4.5', style: TextStyle(fontSize: 12)),
]),
]),
),
]),
);
}
}
class ProductDetailScreen extends StatefulWidget {
@override
State<ProductDetailScreen> createState() => _ProductDetailScreenState();
}
class _ProductDetailScreenState extends State<ProductDetailScreen> {
int quantity = 1;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Product Detail')),
body: SingleChildScrollView(
child: Column(children: [
Container(height: 300, color: Colors.grey[300], child: Icon(Icons.image, size: 100)),
Padding(padding: EdgeInsets.all(16),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text('Premium T-Shirt', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
SizedBox(height: 8),
Text('Rp 150.000', style: TextStyle(fontSize: 18, color: Colors.blue, fontWeight: FontWeight.bold)),
SizedBox(height: 20),
Row(children: [
IconButton(icon: Icon(Icons.remove), onPressed: () => setState(() => quantity--)),
Text('$quantity'),
IconButton(icon: Icon(Icons.add), onPressed: () => setState(() => quantity++)),
]),
SizedBox(height: 20),
ElevatedButton(onPressed: () => print('Added'), child: Text('Add to Cart'),
style: ElevatedButton.styleFrom(minimumSize: Size(double.infinity, 50)),
),
]),
),
]),
),
);
}
}
class ThemeProvider extends ChangeNotifier {
bool isDarkMode = false;
void toggleTheme() {
isDarkMode = !isDarkMode;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => ThemeProvider(),
child: Consumer<ThemeProvider>(
builder: (context, themeProvider, _) {
return MaterialApp(
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: themeProvider.isDarkMode ? ThemeMode.dark : ThemeMode.light,
home: HomeScreen(),
);
},
),
);
}
}
// Toggle Button
Consumer<ThemeProvider>(
builder: (context, themeProvider, _) {
return IconButton(
icon: Icon(themeProvider.isDarkMode ? Icons.light_mode : Icons.dark_mode),
onPressed: () => themeProvider.toggleTheme(),
);
},
)
Selamat! Kamu sudah membangun E-Commerce UI production-grade dengan semua fitur!
Dengan animasi loading yang engaging
List, detail, cart dengan full functionality
Toggle global dengan state management
Optimal di semua ukuran device
Clean, maintainable, production-ready
Smooth, engaging, user-delightful
Kamu siap jadi Mobile UI/UX Designer profesional! 🚀✨
Buat design system unik dengan color, typography, dan 3 custom widgets
1. Color Palette — 5 warna + dokumentasi psychological meaning
2. Typography — Minimal 3 font dari Google Fonts dengan size scale
3. Widgets — CustomButton, CustomCard, CustomAppBar yang reusable
4. Theme — Light & Dark theme yang konsisten
Desain (40%): Kreatif, harmonis
Konsistensi (30%): Warna & font consistent
Code (20%): Clean & maintainable
Dokumentasi (10%): Clear comments
lib/
├── constants/colors.dart
├── widgets/custom_*.dart
└── themes/app_theme.dart
Implementasikan dark mode complete dengan persistence & smooth transitions
Setup ThemeProvider (ChangeNotifier)
Simpan ke SharedPreferences
Buat toggle UI yang seamless
📌 Checkpoint: Toggle 1x → seluruh app terupdate smooth 🎯
Bangun Profile Screen dengan loading skeleton, empty state, dark mode
• Header: Avatar, nama, email dengan skeleton loading
• Menu: My Orders, Wishlist, Settings, Logout
• Loading: Skeleton card untuk simulated fetch
• Empty: Friendly message jika user baru
• Dark Mode: Full support seamless
□ UI modern & attractive
□ Loading state ✓
□ Empty state ✓
□ Dark mode ✓
□ Smooth animations ✓
Akhir minggu
Submit: GitHub link / APK
Format: Main branch clean
Kamu sudah menguasai UI/UX Mobile Development A sampai Z! 🎉
Konsistensi visual dengan color, typography, components
Implicit, explicit, hero animation yang engaging
Loading, empty, error states dengan UX informatif
E-Commerce UI ready-to-scale dengan semua konsep
"Design bukan hanya tampilan,
tapi tentang menciptakan pengalaman yang memorable!" 💖
Keep creating, keep improving! 🚀✨