Ding: Toast yang Berubah di Tempat, Bukan Menumpuk

Abd. Asis
Ding: Toast yang Berubah di Tempat, Bukan Menumpuk

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:

PaketIsiUkuran terpasang
@yusufceng1z/dingspring, timer, store, suara, renderer DOM97,3 kB
@yusufceng1z/ding-reactrenderer React 18+14,9 kB
@yusufceng1z/ding-svelterenderer Svelte 57,5 kB
@yusufceng1z/ding-vuerenderer Vue 3 dan Nuxt8,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.

TypeScriptTS
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.

React TSXTSX
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.

TypeScriptTS
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

OpsiFungsi
descriptionbaris kedua teks
actiontombol utama { label, onClick }
secondarytombol kedua yang lebih tenang
imageavatar atau thumbnail, menggantikan ikon status
metateks kecil di ujung, misalnya now atau ⌘V
progressangka 0 sampai 1, menggambar garis tipis di bawah
durationdurasi dalam milidetik, 0 berarti bertahan sampai ditutup
idpakai 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:

CSSCSS
.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, sisanya polite.
  • Esc menutup toast, Alt + T memindahkan 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:

BashBASH
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

HalDingsonnerreact-hot-toast
Perilaku saat pesan barusatu toast berubah di tempattumpukantumpukan
Animasispring solver, bisa diselaCSS transitionCSS transition
Dependensi runtimenoladaada
FrameworkReact, Svelte, Vue, DOMReactReact
Kematanganversi 0.0.4, rilis November 2026sudah dipakai luassudah 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.

Abd. Asis
Ditulis oleh
Abd. Asis

Software Developer dari Madura. Menulis tentang PHP, Laravel, dan pengembangan web modern dalam Bahasa Indonesia.