---
title: "CSS UI Feedback Animations: Toasts, Modals & Tooltips"
description: "Six free CSS UI feedback animations with previews: toast slide-in, modal pop-in, tooltip fade, tab underline slide, and progress pings. Pure CSS."
url: https://frontendhelpers.com/css-ui-feedback-animations
updated: 2026-10-12
---

# CSS UI feedback animations

Toasts, modals, tooltips, tab indicators, progress bars and notification pings — the motion that tells users what just happened.

## The code for all 6 ui feedback, explained

Every effect above, with the markup to use and the complete CSS. Each one is self-contained: paste the CSS once, add the class, done.

### Toast Slide-In

The standard confirmation pattern — slides in with a slight overshoot so it feels placed, not teleported. Remove the element (or add a fade-out class) after ~2.5s.

Markup

```html
<div class="fh-toast">✓ Changes saved</div>
```

CSS

```css
.fh-toast {
  display: inline-block;
  padding: 12px 20px;
  background: #101828;
  border: 1px solid rgba(16, 185, 129, 0.5);
  border-radius: 10px;
  color: #10b981;
  font-weight: 600;
  box-shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.5);
  animation: fh-toastIn 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
@keyframes fh-toastIn {
  from { opacity: 0; transform: translateY(16px) scale(0.95); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
```

### Modal Pop-In

Dialog scales up as the backdrop fades — the pair reads as one motion. In production use the native

element and trigger this on open.

Markup

```html
<div class="fh-modal-backdrop">
  <div class="fh-modal">…dialog content…</div>
</div>
```

CSS

```css
.fh-modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  display: grid;
  place-items: center;
  animation: fh-backdropIn 0.25s ease both;
}
.fh-modal {
  background: #101828;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 14px;
  padding: 22px 26px;
  color: inherit;
  font-weight: 600;
  box-shadow: 0 24px 60px -12px rgba(0, 0, 0, 0.6);
  animation: fh-modalIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
@keyframes fh-backdropIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes fh-modalIn {
  from { opacity: 0; transform: translateY(14px) scale(0.9); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
/* demo-only helpers */
.fh-modal-row { display: flex; gap: 8px; margin-top: 12px; }
.fh-modal-ok, .fh-modal-no {
  font-size: 0.75em;
  padding: 6px 12px;
  border-radius: 7px;
}
.fh-modal-ok { background: #06b6d4; color: #001018; }
.fh-modal-no { border: 1px solid #3a4358; }
```

### Tooltip Fade

The short delay (0.15s) is deliberate — tooltips that appear instantly flicker annoyingly as the cursor crosses the page. The arrow is a rotated square.

Markup

```html
<div class="fh-tip-wrap">
  <button>Delete</button>
  <span class="fh-tooltip">This cannot be undone</span>
</div>
```

CSS

```css
.fh-tip-wrap {
  position: relative;
  display: inline-block;
}
.fh-tooltip {
  position: absolute;
  bottom: calc(100% + 9px);
  left: 50%;
  translate: -50% 4px;
  background: #101828;
  border: 1px solid #3a4358;
  border-radius: 8px;
  padding: 7px 12px;
  font-size: 0.75em;
  white-space: nowrap;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.25s ease 0.15s, translate 0.25s ease 0.15s, visibility 0.25s 0.15s;
}
.fh-tooltip::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  margin-left: -5px;
  margin-top: -5px;
  width: 10px;
  height: 10px;
  background: #101828;
  border-right: 1px solid #3a4358;
  border-bottom: 1px solid #3a4358;
  transform: rotate(45deg);
}
.fh-tip-wrap:hover .fh-tooltip,
.fh-tip-wrap:focus-within .fh-tooltip {
  opacity: 1;
  visibility: visible;
  translate: -50% 0;
}
```

### Tab Underline Slide

Each tab carries its own scaling underline — hover any tab and the line grows from centre. Swap the .active class in JS for the selected state.

Markup

```html
<nav class="fh-tabs">
  <a class="fh-tab active" href="#">Overview</a>
  <a class="fh-tab" href="#">Pricing</a>
</nav>
```

CSS

```css
.fh-tabs {
  display: flex;
  gap: 4px;
}
.fh-tab {
  position: relative;
  padding: 10px 16px;
  font-weight: 600;
  color: #94a3b8;
  cursor: pointer;
  transition: color 0.2s ease;
}
.fh-tab::after {
  content: '';
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 4px;
  height: 2px;
  border-radius: 2px;
  background: #06b6d4;
  transform: scaleX(0);
  transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1);
}
.fh-tab:hover,
.fh-tab.active { color: inherit; }
.fh-tab:hover::after,
.fh-tab.active::after { transform: scaleX(1); }
```

### Progress Bar Fill

The fill animates to a CSS variable, so JavaScript only sets --target and the easing is pure CSS. Great for onboarding steps and upload progress.

Markup

```html
<div class="fh-progress">
  <div class="fh-progress-fill" style="--target: 72%"></div>
</div>
```

CSS

```css
.fh-progress {
  width: 170px;
  height: 10px;
  border-radius: 5px;
  background: rgba(255, 255, 255, 0.08);
  overflow: hidden;
}
.fh-progress-fill {
  --target: 72%;
  height: 100%;
  width: var(--target);
  border-radius: 5px;
  background: linear-gradient(90deg, #8b5cf6, #06b6d4);
  animation: fh-progressFill 1.1s cubic-bezier(0.22, 1, 0.36, 1) both;
}
@keyframes fh-progressFill {
  from { width: 0; }
}
```

### Notification Ping

The unread-notification dot with a radiating ping — impossible to miss without being loud. The ::after ring animates; the dot itself stays still.

Markup

```html
<button class="fh-bell">
  🔔
  <span class="fh-badge-ping"></span>
</button>
```

CSS

```css
.fh-bell {
  position: relative;
  display: inline-grid;
  place-content: center;
  width: 46px;
  height: 46px;
  border: 1px solid #3a4358;
  border-radius: 12px;
}
.fh-badge-ping {
  position: absolute;
  top: 9px;
  right: 9px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #ef4444;
}
.fh-badge-ping::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: #ef4444;
  animation: fh-badgePing 1.4s cubic-bezier(0, 0, 0.2, 1) infinite;
}
@keyframes fh-badgePing {
  from { transform: scale(1);   opacity: 0.8; }
  to   { transform: scale(2.6); opacity: 0; }
}
```

## Feedback animation has a job to do

Every animation in this category answers a question: _did that work?_ _where did this come from?_ _how much longer?_ That makes them the most functional animations on the site — and the ones where getting the timing right matters most.

Confirmation feedback should appear **immediately** after the action, within about 100ms, or the user stops associating the two. A toast that arrives half a second after clicking Save feels disconnected from the click.

## Toasts, and announcing them properly

A toast that only appears visually is invisible to screen reader users — who then have no confirmation their action succeeded. Put toast messages in a container with `aria-live="polite"` so assistive technology announces them without interrupting, exactly as this site does for its copy confirmations.

Keep them on screen long enough to read — roughly 4 to 6 seconds for a short message — and never put an essential action inside a toast that disappears. Anything the user must act on belongs in a persistent element.

## Modals: the hardest pattern to get right

The pop-in animation is the easy part. The accessibility requirements are what most implementations miss: focus must move into the dialog when it opens, be trapped inside while it's open, and return to the triggering element on close. Escape must dismiss it, and content behind it should be inert.

The native `<dialog>` element with `showModal()` handles focus trapping, Escape and inertness for you, and is now well supported. Use it and apply this animation on open rather than rebuilding all of that from scratch.

## Progress and tooltips

The progress bar animates to a CSS custom property, so JavaScript only sets `--target` and the easing stays in CSS. For accessibility, use a real `role="progressbar"` with `aria-valuenow` so the value is exposed, not just drawn.

Tooltips use a deliberate short delay before appearing — without it they flicker distractingly as the cursor crosses the page. Never put essential information in a tooltip alone: it's unreachable on touch devices and easy to miss.

## Frequently asked questions

### How do I make toast notifications accessible?

Put them in a container with aria-live="polite" so screen readers announce the message without interrupting. Keep the toast visible for 4 to 6 seconds, and never place an essential action inside one that disappears.

### Should I use the native dialog element for modals?

Yes where you can. dialog with showModal() handles focus trapping, Escape to close and making background content inert — the parts most custom modals get wrong. Apply your animation on open.

### Why do my tooltips flicker when moving the mouse?

They are appearing instantly. Add a short transition delay of around 150 milliseconds so tooltips only show when the cursor settles, rather than firing as it passes across the page.

## Related categories

- [Animate a Native Dialog in CSS with @starting-style](https://frontendhelpers.com/css-dialog-animation): Animate a native dialog in and out with @starting-style and allow-discrete, fade its backdrop, close it on a backdrop click and respect reduced motion.
 
- [Forms & Inputs](https://frontendhelpers.com/css-form-animations): Focus glows, toggles and validation shakes
 
- [Loaders](https://frontendhelpers.com/css-loading-animations): Spinners, skeletons and progress indicators
 
- [Menus & Dropdowns](https://frontendhelpers.com/css-menu-animations): Dropdown fades, grows and staggered items
