agentleFS
soilmasssoilmass/motion-design-agent/skills/notifications/SKILL.md

notifications

Skill7 starsChanged 10 months agoMITView on GitHub

Toast, snackbar, and notification animation patterns. Use when implementing in-app notifications, alerts, success/error messages, or any temporary overlay UI.

9 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use review and release.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • SentrySkill

    review

    Fixture review skill.

    Also in 3 projects that use this

  • MicrosoftSkill

    release

    Use ONLY when the user explicitly asks to cut/publish a Huabu desktop release or tag a version. Performs an outward action — bumps the version in apps/desktop/package.json, commits + annotated-tags vX.Y.Z, and pushes the branch + tag. That tag push auto-builds the macOS/Windows installers and publishes a GitHub Release. Provides `status`, `bump`, `tag-push`, and a one-command `run` (bump → tag-push).

    Also in 3 projects that use this

The file

Files
SKILL.md

Notification Animations

Patterns for toasts, snackbars, alerts, and temporary messages.

Notification Types

TypeDurationPositionDismissal
Toast3-5sTop/BottomAuto + swipe
Snackbar5-10sBottomAction + swipe
AlertUntil actionCenterButton only
BannerPersistentTopClose button

Entrance Patterns

Slide In (Recommended)

/* From bottom */
@keyframes toast-enter {
  from {
    transform: translateY(100%);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}

.toast-enter {
  animation: toast-enter 250ms ease-out forwards;
}
// GSAP
gsap.from('.toast', {
  y: 100,
  opacity: 0,
  duration: 0.25,
  ease: 'power2.out'
});

Scale + Fade

@keyframes toast-scale-enter {
  from {
    transform: scale(0.9);
    opacity: 0;
  }
  to {
    transform: scale(1);
    opacity: 1;
  }
}

.toast-scale-enter {
  animation: toast-scale-enter 200ms ease-out forwards;
}

Spring Entrance (Playful)

// Framer Motion
<motion.div
  initial={{ y: 50, opacity: 0 }}
  animate={{ y: 0, opacity: 1 }}
  transition={{
    type: 'spring',
    damping: 20,
    stiffness: 300
  }}
/>

Exit Patterns

Fade Out

@keyframes toast-exit {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

.toast-exit {
  animation: toast-exit 150ms ease-in forwards;
}

Slide Out

// Exit in same direction as entry
gsap.to('.toast', {
  y: -50,
  opacity: 0,
  duration: 0.15,
  ease: 'power2.in',
  onComplete: () => toast.remove()
});

Swipe Dismiss

// Gesture-based dismissal
const swipeThreshold = 100;
const velocityThreshold = 500;

function handleSwipe(gesture) {
  if (Math.abs(gesture.deltaX) > swipeThreshold ||
      Math.abs(gesture.velocityX) > velocityThreshold) {
    // Dismiss in swipe direction
    gsap.to('.toast', {
      x: gesture.deltaX > 0 ? 300 : -300,
      opacity: 0,
      duration: 0.2,
      ease: 'power2.in'
    });
  } else {
    // Snap back
    gsap.to('.toast', {
      x: 0,
      duration: 0.3,
      ease: 'elastic.out(1, 0.5)'
    });
  }
}

Stacking Behavior

Push Stack

// New toasts push existing ones up
function addToast(toast) {
  // Animate existing toasts up
  gsap.to('.toast:not(:last-child)', {
    y: '-=70',  // Height + gap
    duration: 0.2,
    stagger: 0.05
  });

  // Insert new toast
  container.appendChild(toast);
  animateIn(toast);
}

Replace Stack

// Fade out old, fade in new
async function replaceToast(newToast) {
  const existing = document.querySelector('.toast');

  if (existing) {
    await gsap.to(existing, {
      opacity: 0,
      y: -10,
      duration: 0.15
    });
    existing.remove();
  }

  container.appendChild(newToast);
  animateIn(newToast);
}

Status-Specific Animations

Success

const successToast = {
  enter: {
    opacity: [0, 1],
    y: [20, 0],
    scale: [0.95, 1]
  },
  icon: {
    // Checkmark draw animation
    strokeDashoffset: [24, 0],
    duration: 0.3,
    delay: 0.2
  },
  timing: {
    duration: 250,
    easing: 'ease-out'
  }
};

Error

const errorToast = {
  enter: {
    opacity: [0, 1],
    x: [-10, 5, -3, 0]  // Subtle shake
  },
  timing: {
    duration: 300,
    easing: 'ease-out'
  },
  borderPulse: {
    // Attention-grabbing pulse
    boxShadow: [
      '0 0 0 0 rgba(239, 68, 68, 0.5)',
      '0 0 0 8px rgba(239, 68, 68, 0)',
    ],
    duration: 500,
    iterations: 2
  }
};

Warning

const warningToast = {
  enter: {
    opacity: [0, 1],
    y: [20, 0]
  },
  icon: {
    // Pulse animation for warning icon
    scale: [1, 1.1, 1],
    duration: 400,
    iterations: 2
  }
};

Info

const infoToast = {
  enter: {
    opacity: [0, 1],
    y: [10, 0]
  },
  timing: {
    duration: 200,
    easing: 'ease-out'
  }
};

Progress Indicator

/* Auto-dismiss countdown */
.toast-progress {
  position: absolute;
  bottom: 0;
  left: 0;
  height: 3px;
  background: currentColor;
  animation: toast-countdown 5s linear forwards;
}

@keyframes toast-countdown {
  from { width: 100%; }
  to { width: 0%; }
}
// Pause on hover
toast.addEventListener('mouseenter', () => {
  progressBar.style.animationPlayState = 'paused';
});

toast.addEventListener('mouseleave', () => {
  progressBar.style.animationPlayState = 'running';
});

Accessibility

ARIA Announcements

<div
  role="alert"
  aria-live="polite"
  aria-atomic="true"
>
  {message}
</div>

Reduced Motion

@media (prefers-reduced-motion: reduce) {
  .toast-enter {
    animation: toast-fade-only 150ms ease-out;
  }

  @keyframes toast-fade-only {
    from { opacity: 0; }
    to { opacity: 1; }
  }
}

Focus Management

// Return focus after dismissal
const previousFocus = document.activeElement;

toast.addEventListener('dismiss', () => {
  previousFocus?.focus();
});

React Implementation

import { AnimatePresence, motion } from 'framer-motion';

function ToastContainer({ toasts }) {
  return (
    <div className="toast-container">
      <AnimatePresence>
        {toasts.map(toast => (
          <motion.div
            key={toast.id}
            initial={{ opacity: 0, y: 50 }}
            animate={{ opacity: 1, y: 0 }}
            exit={{ opacity: 0, y: -20 }}
            transition={{
              duration: 0.25,
              ease: [0.2, 0, 0, 1]  // M3 standard
            }}
            layout  // Smooth reordering
          >
            <Toast {...toast} />
          </motion.div>
        ))}
      </AnimatePresence>
    </div>
  );
}

Timing Guidelines

AspectDurationNotes
Entrance200-250msFast enough to not interrupt
Exit150-200msSlightly faster than entrance
Auto-dismiss3000-5000msBased on content length
Swipe dismiss200msMatch gesture velocity
Stagger (stack)50msBetween existing toasts

Related Skills

  • Dependencies: easing, duration, entrance-patterns, exit-patterns
  • See also: accessibility, gesture-motion
  • Components: Toast, Snackbar, Alert, Banner

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.