Seorang pengguna mengunggah lima berkas sekaligus, lalu koneksinya putus. Di kebanyakan aplikasi, pojok kanan atas langsung dipenuhi lima kartu notifikasi yang menumpuk ke bawah, saling menutupi isi halaman, dan baru hilang satu per satu setelah beberapa detik. Pengguna tidak sempat membaca satu pun.
Cara umum menyelesaikan ini adalah menumpuk dengan rapi, memberi batas maksimal tiga, lalu menyisipkan sisanya ke antrean. Ding memilih jalan lain: hanya ada satu toast di layar, dan pesan baru mengubah toast itu di tempat.
Apa itu Ding
Ding adalah library notifikasi yang dirilis awal November 2026. Repo-nya YusufCeng1z/ding, berlisensi MIT, ditulis dengan TypeScript ketat, dan tidak punya dependensi runtime. Klaim utamanya ada di satu kalimat: toast dengan fisika spring sungguhan, satu toast yang berubah bentuk di tempat, jalan di React, Svelte, Vue, maupun DOM biasa.
Empat paket tersedia di npm:
| Paket | Isi | Ukuran terpasang |
|---|---|---|
@yusufceng1z/ding | spring, timer, store, suara, renderer DOM | 97,3 kB |
@yusufceng1z/ding-react | renderer React 18+ | 14,9 kB |
@yusufceng1z/ding-svelte | renderer Svelte 5 | 7,5 kB |
@yusufceng1z/ding-vue | renderer Vue 3 dan Nuxt | 8,2 kB |
Semuanya masih versi 0.0.4. Angka itu penting, dan saya bahas di bagian akhir.
Arsitekturnya jelas: paket inti memegang seluruh perilaku, paket framework hanya merender. Jadi bug di timer atau fisika tidak perlu diperbaiki empat kali.
Ide intinya: morph, bukan stack
Di library populer seperti sonner atau react-hot-toast, setiap panggilan toast() membuat kartu baru, lalu kartu itu masuk tumpukan. Di Ding, panggilan baru tidak menambah kartu. Dia mengubah kartu yang sedang tampil.
Efeknya terasa di dua situasi yang biasanya paling berantakan: unggahan bertahap dan pesan berulang.
const id = toast.loading('Mengunggah foto…', { progress: 0 });
toast.update(id, { progress: 0.6 });
toast.update(id, { type: 'success', title: 'Foto terunggah', progress: null });
Satu kartu, tiga keadaan. Tingginya ikut berubah mengikuti isi, dan itu bukan animasi tinggi biasa.
Kenapa "spring sungguhan" layak disebut
Hampir semua animasi UI memakai kurva easing: nilai berjalan dari A ke B mengikuti fungsi waktu yang sudah ditentukan. Masalahnya muncul ketika animasi disela di tengah jalan. Dengan easing, elemen yang disela biasanya melompat, karena kurva baru dimulai dari nol dan kecepatannya tidak pernah dihitung.
Spring solver berbeda. Dia menyimpan posisi dan kecepatan, lalu menghitung gerak berdasarkan gaya pegas. Ketika pesan baru masuk saat kartu masih bergerak, kecepatan yang sedang berjalan tidak dibuang, tapi dibawa ke target baru. Hasilnya gerakan yang terasa seperti benda nyata, bukan transisi yang dipotong.
Praktisnya, kamu melihatnya saat mengetik cepat atau saat beberapa toast.update() datang berurutan. Kartu tidak berkedip atau melompat, dia meluncur ke bentuk baru.
API yang menahan diri dari kerumitan
Pemakaian di React cuma dua baris: render Toaster sekali, lalu panggil toast dari mana saja.
import { Toaster, toast } from '@yusufceng1z/ding-react';
export default function App() {
return (
<>
<Toaster position="top-center" theme="system" />
<button onClick={() => toast.success('Tersimpan')}>Simpan</button>
</>
);
}
Paket React-nya membawa "use client", jadi Toaster bisa langsung ditaruh di app/layout.tsx pada Next.js App Router. Untuk Nuxt, komponennya baru dipasang saat onMounted, jadi aman terhadap server rendering.
Ada tiga keputusan API yang saya suka karena menyelesaikan masalah nyata, bukan masalah yang dikarang.
Hanya pemanggil terbaru yang boleh memperbarui. Kalau kamu memakai satu id bersama untuk beberapa request, request lambat yang selesai belakangan tidak bisa menimpa pesan yang lebih baru. Ini masalah klasik pada alur unggah berkas, dan di sini ditangani di tingkat library.
Aksi bisa mengembalikan patch. Tombol "Undo" tidak menutup toast, dia mengubahnya menjadi pesan baru.
toast.success('Pesan dihapus', {
action: {
label: 'Undo',
onClick: () => {
restore();
return { type: 'info', title: 'Dikembalikan' };
},
},
});
Panggilan berulang menambah penghitung. Kalau pesan yang sama dikirim tiga kali, toast menampilkan ×3, bukan tiga kartu. Untuk kasus seperti "3 berkas gagal diunggah", ini jauh lebih terbaca.
Opsi yang tersedia
| Opsi | Fungsi |
|---|---|
description | baris kedua teks |
action | tombol utama { label, onClick } |
secondary | tombol kedua yang lebih tenang |
image | avatar atau thumbnail, menggantikan ikon status |
meta | teks kecil di ujung, misalnya now atau ⌘V |
progress | angka 0 sampai 1, menggambar garis tipis di bawah |
duration | durasi dalam milidetik, 0 berarti bertahan sampai ditutup |
id | pakai ulang id supaya panggilan berulang dianggap satu pemanggil |
Beberapa perilaku bawaan yang masuk akal: toast error, loading, dan toast bertombol tidak menutup sendiri. Hover dan fokus keyboard menghentikan hitungan waktu. Semua panggilan mengembalikan id, jadi bisa ditutup manual kapan saja.
Styling tanpa stylesheet
Ding menyuntikkan satu elemen <style> dari JavaScript, jadi tidak ada berkas CSS yang perlu diimpor. Warna dan radius diatur lewat variabel CSS:
.tst-viewport {
--tst-radius: 8px;
--tst-bg: #1e3a8a;
--tst-fg: #ffffff;
--tst-success: #16a34a;
}
Bawaannya memakai light-dark(), jadi satu nilai bisa mengikuti tema terang dan gelap sekaligus. Kalau kamu ingin keduanya berbeda, tulis sendiri pasangan nilainya.
Karena gaya disuntik sebagai satu elemen, ada satu syarat tambahan: kalau situsmu memakai Content Security Policy yang melarang gaya inline, kirim nonce ke Toaster. Nilai per toast diatur lewat DOM, jadi hanya stylesheet itu yang butuh pengecualian.
Aksesibilitas yang tidak ditempel belakangan
- Pesan masuk ke live region permanen: error bersifat
assertive, sisanyapolite. Escmenutup toast,Alt+Tmemindahkan fokus ke dalamnya.- Menghormati
prefers-reduced-motion. - Otomatis bercermin di halaman kanan ke kiri.
- Nama aksesibel untuk region dan tombol tutup bisa diganti, jadi bisa diterjemahkan tanpa memotong library.
Untuk proyek Indonesia, poin terakhir itu berguna: label bawaan berbahasa Inggris, dan kamu bisa menggantinya sendiri.
Suara, opsional dan sopan
Suara mati secara bawaan. Kalau dinyalakan, setiap status punya nada pendek sendiri: sukses naik, error turun, warning dua ketukan, info satu nada. Loading tetap diam, dan pembaruan progres tidak pernah berbunyi.
Yang paling penting: tidak ada suara sebelum pengguna berinteraksi, karena browser mengunci audio. Ini yang membedakan library rapi dari library yang bikin kaget.
Integrasi cepat
Untuk pengguna shadcn/ui, ada perintah satu baris:
npx shadcn@latest add @ding/ding
Perintah itu memasang paket React dan menambahkan pembungkus ding-toaster.tsx. Untuk proyek di luar React, tinggal pasang paket yang sesuai; semuanya bergantung pada paket inti yang sama.
Perbandingan dengan yang sudah ada
| Hal | Ding | sonner | react-hot-toast |
|---|---|---|---|
| Perilaku saat pesan baru | satu toast berubah di tempat | tumpukan | tumpukan |
| Animasi | spring solver, bisa disela | CSS transition | CSS transition |
| Dependensi runtime | nol | ada | ada |
| Framework | React, Svelte, Vue, DOM | React | React |
| Kematangan | versi 0.0.4, rilis November 2026 | sudah dipakai luas | sudah dipakai luas |
Perbandingan ini bukan untuk menyimpulkan siapa yang menang. Tiga library itu menyelesaikan masalah yang berbeda bentuknya.
Yang perlu kamu pertimbangkan sebelum memakai
Satu toast berarti satu pesan. Kalau aplikasimu memang perlu menampilkan tiga notifikasi berbeda bersamaan, misalnya tiga unggahan paralel dengan status masing-masing, Ding memaksa kamu memilih. Panggilan berikutnya akan menimpa yang sedang tampil. Untuk beberapa alur, ini memang yang diinginkan. Untuk alur lain, ini batasan yang harus kamu terima.
Versi 0.0.4 artinya API belum stabil. Nol dependensi dan TypeScript ketat mengurangi risiko, tapi nomor versi itu sudah mengatakan bahwa perubahan yang merusak masih mungkin terjadi. Untuk produk yang sudah jalan, baca changelog sebelum naik versi.
Proyeknya masih sangat muda. Dibuat 5 November 2026, sekitar 441 bintang dan 8 fork saat artikel ini ditulis. Artinya dokumentasinya belum banyak diuji orang lain, dan jawaban atas masalahmu mungkin belum ada di mesin pencari. Untuk komponen sekecil toast, itu risiko yang wajar. Untuk komponen inti transaksi, pikirkan dua kali.
Animasi spring punya biaya rasa. Gerakan yang terasa hidup bisa terasa berlebihan kalau terlalu banyak elemen bergerak. Di halaman dengan modal, popover, dan toast yang semuanya beranimasi, satu library spring lagi bisa membuat layar terasa gelisah. Uji dulu di perangkat lambat.
Cara paling adil menilai library seperti ini: pasang di satu halaman, bukan di seluruh aplikasi. Ganti satu alur notifikasi yang paling sering dipakai, misalnya unggah berkas atau simpan form, lalu bandingkan dengan yang sekarang. Kalau setelah seminggu tidak ada yang mengeluh, baru perluas.
Kapan Ding masuk akal
Cocok kalau:
- aplikasimu menampilkan satu notifikasi pada satu waktu, dan pesan baru biasanya menggantikan yang lama
- kamu memakai lebih dari satu framework dalam satu organisasi, dan ingin perilaku notifikasi yang identik di semuanya
- kamu ingin notifikasi yang terbaca, bukan yang menutupi halaman
- kamu tidak ingin menambah dependensi demi komponen sekecil toast
Kurang cocok kalau:
- kamu butuh banyak notifikasi berbeda tampil bersamaan
- kamu memerlukan library yang sudah bertahun-tahun dipakai dan punya jawaban untuk setiap kasus tepi
- desainmu tidak mengizinkan animasi spring karena alasan performa atau estetika
Penutup
Ding menarik bukan karena animasinya halus. Yang menarik adalah keputusan desainnya: menolak menumpuk, dan memilih satu kartu yang berubah. Batasan itu memaksa library memikirkan hal-hal yang biasanya diabaikan, seperti request lambat yang menimpa pesan baru dan penghitung untuk pesan berulang.
Kalau kamu ingin melihatnya sendiri, demo dan dokumentasinya ada di ding.yusufcengiz.dev, dan kodenya di github.com/YusufCeng1z/ding. Untuk eksperimen pribadi, satu paket dan satu halaman sudah cukup untuk menilai apakah rasa spring-nya cocok dengan produkmu.







