Jenis-Jenis Button
ElevatedButton, TextButton, OutlinedButton, IconButton, dan styling-nya.
Analogi: Tombol di Mesin Kasir
Perhatikan mesin kasir di minimarket. Tombol "Bayar" dibuat besar dan berwarna mencolok karena itu aksi utama yang paling sering ditekan. Tombol "Batal" dibuat kecil tanpa warna mencolok karena itu aksi sekunder. Tombol angka dibuat netral dengan garis tepi. Setiap tombol "berbicara" lewat tampilannya: seberapa penting aku, seberapa sering aku ditekan, dan apa risikonya. Di Flutter, perbedaan peran inilah yang diwakili oleh jenis-jenis button yang berbeda.
Kenapa Ada Banyak Jenis Button?
Satu jenis tombol untuk semua aksi akan membuat pengguna bingung: mana yang utama, mana yang boleh diabaikan? Material Design menyelesaikan ini dengan hierarki visual. ElevatedButton yang terisi penuh dan punya bayangan berkata "ini aksi utama di layar ini". TextButton yang tanpa background berkata "ini aksi sekunder, aman diabaikan". OutlinedButton yang bergaris tepi berkata "ini pilihan netral di antara beberapa opsi". IconButton yang hanya berupa ikon berkata "ini aksi kecil yang berhubungan dengan item ini".
Dengan memakai jenis yang tepat, kamu tidak perlu menjelaskan dengan kata-kata: tampilannya sudah menjelaskan sendiri.
Contoh 1: Empat Button Utama
Column(
children: [
ElevatedButton(
onPressed: () {},
child: const Text('Simpan'),
),
TextButton(
onPressed: () {},
child: const Text('Batal'),
),
OutlinedButton(
onPressed: () {},
child: const Text('Lihat Detail'),
),
IconButton(
icon: const Icon(Icons.delete),
tooltip: 'Hapus',
onPressed: () {},
),
],
)- ElevatedButton: tombol utama yang terisi penuh, punya elevasi (bayangan). Satu layar idealnya hanya punya satu.
- TextButton: tombol teks tanpa background, untuk aksi sekunder seperti "Batal" atau "Lewati".
- OutlinedButton: tombol bergaris tepi, untuk aksi netral seperti "Lihat Detail".
- IconButton: tombol ikon saja, untuk aksi kecil per item seperti hapus atau favorit. Isi
tooltipsupaya muncul label saat ditekan lama (penting untuk aksesibilitas).
Contoh 2: Form Pembayaran yang Realistis
Contoh yang lebih realistis: tombol bayar yang hanya aktif kalau nominal valid, dengan styling kustom sesuai brand.
class FormBayar extends StatefulWidget {
const FormBayar({super.key});
@override
State<FormBayar> createState() => _FormBayarState();
}
class _FormBayarState extends State<FormBayar> {
final _nominalController = TextEditingController();
int _nominal = 0;
@override
void initState() {
super.initState();
_nominalController.addListener(() {
setState(() {
_nominal = int.tryParse(_nominalController.text) ?? 0;
});
});
}
@override
void dispose() {
_nominalController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final valid = _nominal >= 10000;
return Column(
children: [
TextField(
controller: _nominalController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(labelText: 'Nominal (min. Rp10.000)'),
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: valid ? () {} : null,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.teal,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(valid ? 'Bayar Rp$_nominal' : 'Masukkan nominal dulu'),
),
),
TextButton(
onPressed: () => _nominalController.clear(),
child: const Text('Reset'),
),
],
);
}
}Pola onPressed: valid ? () {} : null adalah cara idiomatis Flutter untuk tombol yang aktif-nonaktif mengikuti kondisi. Label tombolnya ikut berubah memberi feedback yang jelas ke pengguna.
Catatan teknis: Untuk gaya global, atur
elevatedButtonTheme,textButtonTheme, dan kawan-kawannya diThemeData. Dengan begitu semua tombol di aplikasi konsisten tanpa styling berulang, dan mengubah brand color cukup di satu tempat.
Tombol Nonaktif
Isi onPressed: null (bukan fungsi kosong) untuk menonaktifkan tombol. Flutter otomatis memakai gaya disabled: warna memudar dan tombol tidak merespons sentuhan. Ini penting untuk aksesibilitas juga, karena screen reader akan mengumumkan tombol sebagai nonaktif.
Kesalahan umum pemula
1. Memakai fungsi kosong () {} padahal maksudnya menonaktifkan.
// SALAH: tombol terlihat aktif dan bisa ditekan, tapi tidak ngapa-ngapain.
// Pengguna bingung: rusak atau gimana?
ElevatedButton(onPressed: () {}, child: const Text('Bayar'))
// BENAR: tombol terlihat nonaktif dan screen reader mengumumkannya
ElevatedButton(onPressed: null, child: const Text('Bayar'))Fungsi kosong membuat tombol terlihat bisa ditekan padahal tidak ada efeknya. null memberi sinyal visual yang jujur.
2. Membungkus button dengan GestureDetector atau InkWell.
// SALAH: kehilangan efek ripple, gaya disabled, dan dukungan aksesibilitas bawaan
GestureDetector(
onTap: () {},
child: Container(...child: const Text('Simpan')),
)
// BENAR: pakai button bawaan, semua perilaku Material sudah termasuk
ElevatedButton(onPressed: () {}, child: const Text('Simpan'))Button bawaan Flutter sudah punya efek sentuhan (ripple), status disabled, focus keyboard, dan semantik aksesibilitas. Membuat tombol dari Container + GestureDetector berarti membangun ulang semuanya dari nol, hampir selalu lebih buruk hasilnya.
3. Terlalu banyak ElevatedButton dalam satu layar. Kalau semua tombol terlihat "utama", tidak ada yang utama. Pilih satu aksi primer per layar untuk ElevatedButton, sisanya pakai TextButton atau OutlinedButton. Hierarki visual yang jelas membuat pengguna langsung tahu harus menekan apa.
4. Lupa memberi feedback saat aksi butuh waktu. Tombol "Bayar" yang ditekan lalu diam saja selama dua detik membuat pengguna menekannya berkali-kali. Untuk aksi async, ubah tombol menjadi loading indicator atau nonaktifkan sementara selama proses berjalan.
Tantangan
Tombol bayar dinamis
Buat ElevatedButton 'Bayar Rp50.000' yang nonaktif (onPressed null) saat saldo kurang dari 50000, dan aktif saat saldo cukup. Gunakan variabel boolean cukup.