Merancang tampilan desktop yang profesional: menerapkan Material dan desktop design, membangun sistem theming gelap-terang yang konsisten, dan membuat UI responsif terhadap ukuran window yang berubah

Fungsi CatatanKu sudah terbentuk: data lokal tersimpan, pencarian bekerja, dan background task tidak membekukan UI. Sekarang pengguna melihat hasilnya — dan di sinilah desain menentukan kualitas persepsi. Aplikasi yang fungsional tapi tampilannya berantakan akan ditinggalkan; aplikasi yang konsisten, nyaman di mata, dan mengikuti tema sistem terasa "profesional".
Episode ini membangun lapisan tampilan CatatanKu: prinsip desktop design (bukan sekadar desain web), sistem theming yang mengikuti preferensi OS (gelap/terang), dan responsivitas terhadap resize window. Semua prinsip diterapkan konsisten di Tauri, Electron, dan Flutter — karena yang kita pelajari adalah pola desain, bukan API.
Desain desktop bukan versi desktop dari desain web. Perbedaannya fundamental:
| Aspek | Web | Desktop |
|---|---|---|
| Viewport | Bervariasi, sesi singkat | Window besar, sesi panjang |
| Interaksi | Mouse + touch | Mouse + keyboard + shortcut |
| Jarak pandang | 50-80 cm (monitor) | Sama, tapi sesi berjam-jam |
| Fokus | Satu tab dalam banyak | Multi-window, multi-task |
| Ukuran target | Touch-first (48px) | Klik presisi (24-32px) |
Konsekuensinya: desktop bisa menampilkan kepadatan informasi tinggi dengan kontrol kecil yang presisi, dan harus menahan kelelahan mata untuk pemakaian berjam-jam. Padding 16-24px yang nyaman di web terasa kekanak-kanakan di layar 27 inci.
Desktop app produktivitas (VS Code, Slack, Obsidian) memakai pola layout yang sama — dan CatatanKu mengikutinya:
┌──────────┬────────────────────┬──────────────┐
│ Sidebar │ Daftar catatan │ Editor │
│ navigasi │ + pencarian │ isi teks │
└──────────┴────────────────────┴──────────────┘Setiap panel punya tanggung jawab tunggal, dan semuanya bisa di-resize. Panel ini dibangun dengan flex/grid di web stack dan Row/Expanded di Flutter — konsep yang sama.
Aplikasi desktop modern wajib mendukung light mode dan dark mode, idealnya mengikuti preferensi sistem pengguna. Desain dengan design token — variabel warna, spacing, dan tipografi — adalah kuncinya. Jangan hardcode warna; definisikan token, lalu dua tema.
export type ThemeMode = "light" | "dark" | "system";
export const tokens = {
light: {
bg: "#fafafa",
surface: "#ffffff",
text: "#1f1f1f",
textMuted: "#6b6b6b",
border: "#e0e0e0",
accent: "#6750a4",
danger: "#ba1a1a",
},
dark: {
bg: "#1e1e1e",
surface: "#262626",
text: "#e6e6e6",
textMuted: "#a8a8a8",
border: "#3d3d3d",
accent: "#cfbcff",
danger: "#ffb4ab",
},
} as const;Perhatikan: token bukan hanya "warna dibalik" — kontras adalah pertimbangan desain. textMuted di dark mode harus tetap terbaca (rasio kontras ≥ 4.5:1). Aksesibilitas akan kita sempurnakan di episode 11.
Ketiga framework menyediakan akses ke preferensi sistem:
const [mode, setMode] = useState<ThemeMode>("system");
const isDark = useMediaQuery("(prefers-color-scheme: dark)");
const resolved = mode === "system" ? (isDark ? "dark" : "light") : mode;MaterialApp(
theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: accent)),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: accent, brightness: Brightness.dark),
),
themeMode: ThemeMode.system, // ikuti OS; ganti via pengaturan
)Prinsipnya: simpan pilihan pengguna (light/dark/system) di config store (episode 8), lalu resolve tema final. Perubahan OS saat aplikasi berjalan (misal memindahkan window dari laptop terang ke monitor gelap di malam hari) harus di-reflect — inilah yang membuat aplikasi terasa hidup.
prefers-color-scheme. Native dialogs mengikuti OS sendiri.ColorScheme + ThemeData — token sudah terstruktur oleh framework.Note
Untuk window frame itu sendiri (title bar), ada opsi "frameless + custom titlebar" yang membuat aplikasi terlihat sangat native — tetapi biayanya: kalian harus menangani drag region, tombol minimize/close, dan platform-specific behavior. Pertimbangkan di episode 25 (cross-platform mastery); untuk sekarang biarkan window frame standar OS.
"Responsif" di desktop artinya UI beradaptasi saat window di-resize: sidebar bisa menyempit, panel editor mendapat ruang, dan tidak ada elemen yang terpotong atau tumpang tindih. Di web, kalian memakai media queries; di desktop, kalian merespons ukuran window yang berubah-ubah.
function App() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const onResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, []);
const compact = width < 800;
return compact ? <CompactLayout /> : <FullLayout />;
}LayoutBuilder(builder: (context, constraints) {
final compact = constraints.maxWidth < 800;
return compact
? const CompactLayout()
: const FullLayout();
});Aturan desain yang berguna: sidebar bisa disembunyikan/diminimalkan, daftar catatan punya min-width dan max-width, dan editor selalu mengisi sisa ruang. Simpan status panel (mana yang terbuka) di config store agar pengguna tidak mengatur ulang setiap kali membuka aplikasi.
Rangkuman yang kita terapkan:
system default, toggle manual disimpan di config store.Inti yang harus dibawa pulang:
prefers-color-scheme/ThemeMode.system) dan simpan override manual.Di episode 11 selanjutnya kita akan membuat CatatanKu ramah untuk semua orang: accessibility & localization — keyboard navigation, dukungan screen reader, dan internasionalisasi bahasa Indonesia-Inggris. Sampai jumpa di episode 11!