构建一个Toast组件
Building a Toast Component

原始链接: https://emilkowal.ski/ui/building-a-toast-component

## Sonner:一个流行的Toast库的故事 Sonner,一个Toast通知库,已经迅速流行起来——每周npm下载量超过700万次,并被X、Vercel等公司采用,以及作为shadcn/ui的默认库。尽管市场竞争激烈,但Sonner的成功源于其对优雅性和用户体验的关注。 创建者有意选择了独特的名称“Sonner”(法语中“敲响”的意思),而不是功能性的名称,优先考虑了易记性。至关重要的是,Sonner视觉上吸引人的堆叠动画虽然并非全新,却是第一个被广泛可用的开源实现。 除了美观之外,Sonner还优先考虑流畅的开发者体验。它避免了像Context这样的复杂React模式,而是利用了一个简单的观察者模式和一个直接的`toast()`函数调用。诸如滑动消失、展开Toast以及处理标签页非活动状态等功能,都体现了对细节的关注。动画使用CSS过渡处理,以实现可中断性,并且巧妙的CSS技术保持了hover状态和拖动功能的一致性。 最终,Sonner的成功归功于其美丽的设计和易用性——提醒我们,在软件开发中,优先考虑美学可以成为强大的差异化因素。

## 吐司消息框的争论:可访问性与用户体验问题 最近一篇Hacker News上的帖子引发了关于“吐司”组件(那些用于确认的小型临时通知)实用性的讨论。虽然原始帖子详细介绍了如何构建吐司组件,但对话很快变得批判,反映了GitHub最近决定从Primer设计系统中*移除*吐司消息框的决定。 核心问题是:**可访问性**。许多评论员指出,很难以一种对所有人都有用的方式实现吐司消息框,特别是对于使用屏幕放大镜的用户。人们担心,即使使用ARIA角色,屏幕放大处理等细微之处也常常被忽视。 除了可访问性之外,讨论还强调了更广泛的用户体验问题:吐司消息框很容易被忽略,提供的上下文信息很少,并且经常被滥用。有人建议使用横幅、警报或内联确认等替代方案。一些人认为,开发者更喜欢吐司消息框是因为它们易于实现和视觉吸引力(动画!),而不是深思熟虑的用户体验。另一些人指出它们对后台任务或密集用户界面很有用,但即使如此,状态栏等更好的解决方案也存在。 最终,共识倾向于谨慎——吐司消息框通常是一种“具有吸引力的麻烦”,应该谨慎使用,或者根本不使用。
相关文章

原文

Back in 2023, I decided to build a toast library called Sonner. It’s now downloaded over 7,000,000 times per week from npm and used by companies like Cursor, X, and Vercel. It’s also the default toast component in shadcn/ui.

When I was making it, the toast “market” was already crowded. So what made Sonner stand out? Why did people choose it over proven alternatives?

Let’s start with the name.

Naming

My thinking was that naming things based on their function feels cheap. react-toast, react-snackbar, react-notifications, they all feel boring and generic to me. I wanted something more unique and elegant.

The way I came up with Sonner is I looked up French words related to notifications. Sonner, which means “to ring” was one of them.

sonner
/sɔ.ne/ Verb [intransitive]
  1. 1. to sound
  2. 2. to ring
  3. Sonner la cloche (Ring the bell).Sonner a la porte (Give a ring).

While I’m sacrificing discoverability and clarity, it feels elegant to me. It’s also a name that feels different, which is important if I want it to stand out.

Animations

I believe Sonner took off immediately because of the stacking animation which was done by some companies before, but never open sourced.

This is what made people fall in love with this tiny component, it just felt right when you saw it animate. Here’s Theo’s reaction to it for example:

Theo’s reaction to Sonner’s animations.

I knew I had to highlight this motion when introducing the library so I experimented with a few different announcement videos where the focus was on that stacking animation. Here’s what I ended up with back in 2023:

You can view the tweet here.

When it comes to the code, I initially used CSS keyframes for the animations, but they aren’t interruptible. Try quickly adding a few toasts below. As you add more toasts, the older ones jump into their new position instead of smoothly transitioning.

Notice how toasts jump into place when you add them quickly.

That’s one downside of keyframes: you can’t smoothly change the end position while the animation is running. CSS transitions, on the other hand, can be interrupted and retargeted, even before the first transition has finished, so I used them instead:

To mimic the enter keyframes, I use a useEffect hook to set mounted to true after the initial render. This makes the toast start at translateY(100%) and transition to translateY(0). The styles are applied through data attributes.

React.useEffect(() => {
  setMounted(true);
}, []);
 
//...
 
<li data-mounted={mounted}>
.sonner-toast {
  transition: transform 400ms ease;
}
 
[data-mounted="true"] {
  transform: translateY(0);
}
 
[data-mounted="false"] {
  transform: translateY(100%);
}

This can now also be solved with the @starting-style CSS at-rule, which would make the implementation much simpler. I might update the code to use this soon.

Stacking toasts

To create the stacking effect, I multiply the gap between toasts by the toast’s index to get the y position. Each toast uses position: absolute to simplify the stacking. I also scale them down by 0.05 * index to create a sense of depth.

联系我们 contact @ memedata.com