All skills
freshtechbro avatar

/modern-web-design

@1da73fe

Modern web design trends, principles, and implementation patterns for 2024-2025. Use this skill when designing websites, creating interactive experiences, implementing design systems, ensuring accessibility, or building performance-first interfaces. Triggers on tasks involving modern design trends, micro-interactions, scrollytelling, bold minimalism, cursor UX, glassmorphism, accessibility compliance, performance optimization, or design system architecture. References animation skills (GSAP, Framer Motion, React Spring), 3D skills (Three.js, R3F, Babylon.js), and component libraries for implementation guidance.

Use this Skill: https://skilld.dev/gh/freshtechbro/claudedesignskills/modern-web-design

This session only. Nothing lands on disk.

referencesinteraction_patterns.md

≈5.4k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Micro-Interaction Patterns Catalog

Overview

Comprehensive catalog of micro-interactions for modern web interfaces. Each pattern includes implementation code, accessibility considerations, and performance notes.

Micro-interactions are small, purposeful animations that provide feedback, guide users, and enhance perceived performance.


Table of Contents

  1. Button Interactions
  2. Form & Input Interactions
  3. Loading States
  4. Navigation Interactions
  5. Hover Effects
  6. Toggle & Switch Interactions
  7. Card Interactions
  8. Scroll Interactions
  9. Modal & Dialog Interactions
  10. Toast & Notification Interactions

Button Interactions

1.1 Hover Lift

Effect: Button lifts up slightly on hover, creating depth.

Implementation (CSS):

.button-lift {
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.button-lift:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.button-lift:active {
  transform: translateY(0);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

Framer Motion:

<motion.button
  whileHover={{ y: -2, boxShadow: "0 4px 12px rgba(0,0,0,0.15)" }}
  whileTap={{ y: 0, boxShadow: "0 2px 4px rgba(0,0,0,0.1)" }}
  transition={{ type: "spring", stiffness: 400, damping: 17 }}
>
  Click me
</motion.button>

Accessibility: Preserve for keyboard focus states.


1.2 Ripple Effect

Effect: Material Design-style ripple on click.

Implementation (JavaScript):

function createRipple(event) {
  const button = event.currentTarget;
  const ripple = document.createElement('span');
  const rect = button.getBoundingClientRect();
  const size = Math.max(rect.width, rect.height);
  const x = event.clientX - rect.left - size / 2;
  const y = event.clientY - rect.top - size / 2;

  ripple.style.width = ripple.style.height = `${size}px`;
  ripple.style.left = `${x}px`;
  ripple.style.top = `${y}px`;
  ripple.classList.add('ripple');

  button.appendChild(ripple);

  setTimeout(() => ripple.remove(), 600);
}

document.querySelectorAll('.button-ripple').forEach(button => {
  button.addEventListener('click', createRipple);
});

CSS:

.button-ripple {
  position: relative;
  overflow: hidden;
}

.ripple {
  position: absolute;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.6);
  transform: scale(0);
  animation: ripple-animation 0.6s ease-out;
}

@keyframes ripple-animation {
  to {
    transform: scale(4);
    opacity: 0;
  }
}

Performance: Use will-change: transform sparingly.


1.3 Magnetic Button

Effect: Button magnetically attracts cursor on proximity.

Implementation (GSAP):

const button = document.querySelector('.magnetic-button');
const magneticStrength = 0.3;

button.addEventListener('mousemove', (e) => {
  const rect = button.getBoundingClientRect();
  const centerX = rect.left + rect.width / 2;
  const centerY = rect.top + rect.height / 2;
  const distanceX = (e.clientX - centerX) * magneticStrength;
  const distanceY = (e.clientY - centerY) * magneticStrength;

  gsap.to(button, {
    x: distanceX,
    y: distanceY,
    duration: 0.3,
    ease: "power2.out"
  });
});

button.addEventListener('mouseleave', () => {
  gsap.to(button, {
    x: 0,
    y: 0,
    duration: 0.5,
    ease: "elastic.out(1, 0.3)"
  });
});

Accessibility: Disable on mobile/touch devices.


1.4 Loading Button State

Effect: Button morphs into loading spinner.

Framer Motion:

function LoadingButton() {
  const [isLoading, setIsLoading] = useState(false);

  return (
    <motion.button
      onClick={() => setIsLoading(true)}
      animate={isLoading ? "loading" : "idle"}
      disabled={isLoading}
      style={{ position: 'relative', overflow: 'hidden' }}
    >
      <motion.span
        variants={{
          idle: { opacity: 1 },
          loading: { opacity: 0 }
        }}
      >
        Submit
      </motion.span>

      <motion.div
        className="spinner"
        variants={{
          idle: { opacity: 0, scale: 0 },
          loading: { opacity: 1, scale: 1 }
        }}
        style={{ position: 'absolute', inset: 0 }}
      >
        <Spinner />
      </motion.div>
    </motion.button>
  );
}

Accessibility: Announce loading state to screen readers.


Form & Input Interactions

2.1 Floating Label

Effect: Label floats up when input is focused or filled.

Implementation:

.input-wrapper {
  position: relative;
  margin: 1rem 0;
}

.input-field {
  width: 100%;
  padding: 1rem;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 1rem;
}

.input-field:focus {
  outline: none;
  border-color: #3b82f6;
}

.input-label {
  position: absolute;
  left: 1rem;
  top: 1rem;
  pointer-events: none;
  transition: all 0.2s ease;
  color: #666;
}

.input-field:focus + .input-label,
.input-field:not(:placeholder-shown) + .input-label {
  top: -0.5rem;
  left: 0.75rem;
  font-size: 0.75rem;
  background: white;
  padding: 0 0.25rem;
  color: #3b82f6;
}

HTML:

<div class="input-wrapper">
  <input
    class="input-field"
    type="text"
    placeholder=" "
    required
  />
  <label class="input-label">Email address</label>
</div>

2.2 Input Validation Feedback

Effect: Instant feedback on validation state.

React + Framer Motion:

function ValidatedInput({ label, validator }) {
  const [value, setValue] = useState('');
  const [isValid, setIsValid] = useState(null);

  const handleChange = (e) => {
    const newValue = e.target.value;
    setValue(newValue);
    setIsValid(validator(newValue));
  };

  return (
    <div className="input-wrapper">
      <motion.input
        value={value}
        onChange={handleChange}
        animate={{
          borderColor: isValid === null ? '#ccc' :
                      isValid ? '#10b981' : '#ef4444'
        }}
        transition={{ duration: 0.2 }}
      />

      <AnimatePresence>
        {isValid === true && (
          <motion.div
            initial={{ opacity: 0, x: -10 }}
            animate={{ opacity: 1, x: 0 }}
            exit={{ opacity: 0, x: -10 }}
            className="success-icon"
          >
            ✓
          </motion.div>
        )}

        {isValid === false && (
          <motion.div
            initial={{ opacity: 0, x: -10 }}
            animate={{ opacity: 1, x: 0 }}
            exit={{ opacity: 0, x: -10 }}
            className="error-icon"
          >
            ✗
          </motion.div>
        )}
      </AnimatePresence>
    </div>
  );
}

Accessibility: Provide error messages, not just icons.


2.3 Password Strength Indicator

Effect: Visual indicator of password strength.

Implementation:

function PasswordStrengthIndicator({ password }) {
  const strength = calculateStrength(password);

  const variants = {
    weak: { width: '33%', backgroundColor: '#ef4444' },
    medium: { width: '66%', backgroundColor: '#f59e0b' },
    strong: { width: '100%', backgroundColor: '#10b981' }
  };

  return (
    <div className="strength-bar-container">
      <motion.div
        className="strength-bar"
        variants={variants}
        animate={strength}
        transition={{ duration: 0.3 }}
      />
      <p className="strength-label">
        Password strength: <strong>{strength}</strong>
      </p>
    </div>
  );
}

function calculateStrength(password) {
  if (password.length < 6) return 'weak';
  if (password.length < 10) return 'medium';
  return 'strong';
}

2.4 Character Count with Progress

Effect: Shows remaining characters with visual indicator.

Implementation:

function TextareaWithCount({ maxLength = 280 }) {
  const [text, setText] = useState('');
  const remaining = maxLength - text.length;
  const percentage = (text.length / maxLength) * 100;

  return (
    <div>
      <textarea
        value={text}
        onChange={(e) => setText(e.target.value)}
        maxLength={maxLength}
      />

      <div className="count-indicator">
        <motion.svg
          width="40"
          height="40"
          style={{ transform: 'rotate(-90deg)' }}
        >
          <circle
            cx="20"
            cy="20"
            r="16"
            stroke="#e5e7eb"
            strokeWidth="3"
            fill="none"
          />
          <motion.circle
            cx="20"
            cy="20"
            r="16"
            stroke={remaining < 20 ? '#ef4444' : '#3b82f6'}
            strokeWidth="3"
            fill="none"
            strokeDasharray={`${2 * Math.PI * 16}`}
            animate={{
              strokeDashoffset: (1 - percentage / 100) * (2 * Math.PI * 16)
            }}
          />
        </motion.svg>
        <span className="count-text">{remaining}</span>
      </div>
    </div>
  );
}

Loading States

3.1 Skeleton Screen

Effect: Content placeholder that mimics layout.

Implementation:

.skeleton {
  background: linear-gradient(
    90deg,
    #f0f0f0 25%,
    #e0e0e0 50%,
    #f0f0f0 75%
  );
  background-size: 200% 100%;
  animation: skeleton-loading 1.5s ease-in-out infinite;
  border-radius: 4px;
}

@keyframes skeleton-loading {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

.skeleton-text {
  height: 1rem;
  margin-bottom: 0.5rem;
}

.skeleton-title {
  height: 2rem;
  width: 60%;
  margin-bottom: 1rem;
}

.skeleton-image {
  width: 100%;
  aspect-ratio: 16 / 9;
}

React Component:

function SkeletonCard() {
  return (
    <div className="card">
      <div className="skeleton skeleton-image" />
      <div className="skeleton skeleton-title" />
      <div className="skeleton skeleton-text" />
      <div className="skeleton skeleton-text" style={{ width: '80%' }} />
    </div>
  );
}

3.2 Progress Bar

Effect: Linear progress indicator.

Framer Motion:

function ProgressBar({ progress }) {
  return (
    <div className="progress-container">
      <motion.div
        className="progress-bar"
        initial={{ width: 0 }}
        animate={{ width: `${progress}%` }}
        transition={{ duration: 0.5, ease: "easeOut" }}
      />
    </div>
  );
}

CSS:

.progress-container {
  width: 100%;
  height: 4px;
  background: #e5e7eb;
  border-radius: 9999px;
  overflow: hidden;
}

.progress-bar {
  height: 100%;
  background: linear-gradient(90deg, #3b82f6, #8b5cf6);
  border-radius: 9999px;
}

3.3 Spinner

Effect: Rotating loading indicator.

CSS:

.spinner {
  width: 40px;
  height: 40px;
  border: 4px solid rgba(59, 130, 246, 0.1);
  border-top-color: #3b82f6;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

Framer Motion (Advanced):

function Spinner() {
  return (
    <motion.div
      className="spinner-container"
      initial={{ opacity: 0, scale: 0.8 }}
      animate={{ opacity: 1, scale: 1 }}
      exit={{ opacity: 0, scale: 0.8 }}
    >
      <motion.div
        className="spinner"
        animate={{ rotate: 360 }}
        transition={{
          duration: 1,
          repeat: Infinity,
          ease: "linear"
        }}
      />
    </motion.div>
  );
}

Navigation Interactions

4.1 Hamburger Menu Animation

Effect: Hamburger transforms into X on click.

CSS:

.hamburger {
  width: 30px;
  height: 20px;
  position: relative;
  cursor: pointer;
}

.hamburger span {
  display: block;
  position: absolute;
  height: 3px;
  width: 100%;
  background: #333;
  border-radius: 3px;
  transition: all 0.3s ease;
}

.hamburger span:nth-child(1) { top: 0; }
.hamburger span:nth-child(2) { top: 50%; transform: translateY(-50%); }
.hamburger span:nth-child(3) { bottom: 0; }

.hamburger.active span:nth-child(1) {
  top: 50%;
  transform: translateY(-50%) rotate(45deg);
}

.hamburger.active span:nth-child(2) {
  opacity: 0;
}

.hamburger.active span:nth-child(3) {
  bottom: 50%;
  transform: translateY(50%) rotate(-45deg);
}

4.2 Dropdown Menu Reveal

Effect: Dropdown slides down with stagger.

Framer Motion:

const menuVariants = {
  closed: {
    opacity: 0,
    y: -20,
    transition: { staggerChildren: 0.05, staggerDirection: -1 }
  },
  open: {
    opacity: 1,
    y: 0,
    transition: { staggerChildren: 0.07, delayChildren: 0.1 }
  }
};

const itemVariants = {
  closed: { opacity: 0, x: -20 },
  open: { opacity: 1, x: 0 }
};

function DropdownMenu({ isOpen }) {
  return (
    <AnimatePresence>
      {isOpen && (
        <motion.ul
          className="dropdown"
          variants={menuVariants}
          initial="closed"
          animate="open"
          exit="closed"
        >
          {items.map((item) => (
            <motion.li key={item} variants={itemVariants}>
              {item}
            </motion.li>
          ))}
        </motion.ul>
      )}
    </AnimatePresence>
  );
}

4.3 Active Link Indicator

Effect: Animated underline follows active link.

Framer Motion:

function TabNavigation() {
  const [activeTab, setActiveTab] = useState(0);

  return (
    <nav className="tabs">
      {tabs.map((tab, index) => (
        <button
          key={tab}
          onClick={() => setActiveTab(index)}
          className={activeTab === index ? 'active' : ''}
        >
          {tab}
          {activeTab === index && (
            <motion.div
              className="active-indicator"
              layoutId="activeTab"
              transition={{ type: "spring", stiffness: 300, damping: 30 }}
            />
          )}
        </button>
      ))}
    </nav>
  );
}

CSS:

.tabs {
  display: flex;
  gap: 2rem;
  border-bottom: 2px solid #e5e7eb;
}

.tabs button {
  position: relative;
  padding: 1rem 0;
  background: none;
  border: none;
  cursor: pointer;
}

.active-indicator {
  position: absolute;
  bottom: -2px;
  left: 0;
  right: 0;
  height: 2px;
  background: #3b82f6;
}

Hover Effects

5.1 Image Zoom on Hover

Effect: Image scales up slightly on hover.

CSS:

.image-container {
  overflow: hidden;
  border-radius: 8px;
}

.image-container img {
  transition: transform 0.3s ease;
  display: block;
  width: 100%;
}

.image-container:hover img {
  transform: scale(1.05);
}

5.2 Tilt Effect

Effect: Card tilts based on mouse position.

Vanilla-Tilt:

VanillaTilt.init(document.querySelectorAll(".tilt-card"), {
  max: 15,
  speed: 400,
  glare: true,
  "max-glare": 0.3
});

Manual Implementation:

const card = document.querySelector('.tilt-card');

card.addEventListener('mousemove', (e) => {
  const rect = card.getBoundingClientRect();
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;

  const centerX = rect.width / 2;
  const centerY = rect.height / 2;

  const rotateX = (y - centerY) / 10;
  const rotateY = (centerX - x) / 10;

  card.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
});

card.addEventListener('mouseleave', () => {
  card.style.transform = 'perspective(1000px) rotateX(0) rotateY(0)';
});

5.3 Reveal on Hover

Effect: Content reveals on hover with slide/fade.

CSS:

.card {
  position: relative;
  overflow: hidden;
}

.card-overlay {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.8);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transform: translateY(100%);
  transition: all 0.3s ease;
}

.card:hover .card-overlay {
  opacity: 1;
  transform: translateY(0);
}

Toggle & Switch Interactions

6.1 Toggle Switch

Effect: Smooth toggle with spring animation.

Framer Motion:

function ToggleSwitch({ enabled, setEnabled }) {
  return (
    <motion.div
      className="switch"
      data-enabled={enabled}
      onClick={() => setEnabled(!enabled)}
      animate={{ backgroundColor: enabled ? '#3b82f6' : '#d1d5db' }}
    >
      <motion.div
        className="switch-handle"
        layout
        transition={{ type: "spring", stiffness: 500, damping: 30 }}
        style={{ justifyContent: enabled ? 'flex-end' : 'flex-start' }}
      />
    </motion.div>
  );
}

CSS:

.switch {
  width: 50px;
  height: 28px;
  border-radius: 14px;
  padding: 4px;
  cursor: pointer;
  display: flex;
  align-items: center;
}

.switch-handle {
  width: 20px;
  height: 20px;
  background: white;
  border-radius: 50%;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}

Card Interactions

7.1 Card Expand

Effect: Card expands to full screen on click.

Framer Motion with layout:

function ExpandableCard() {
  const [isExpanded, setIsExpanded] = useState(false);

  return (
    <motion.div
      layout
      className={isExpanded ? 'card-expanded' : 'card'}
      onClick={() => setIsExpanded(!isExpanded)}
      transition={{ type: "spring", stiffness: 300, damping: 30 }}
    >
      <motion.h2 layout="position">Title</motion.h2>
      <motion.p layout="position">Content</motion.p>

      {isExpanded && (
        <motion.div
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          exit={{ opacity: 0 }}
        >
          Additional content...
        </motion.div>
      )}
    </motion.div>
  );
}

Scroll Interactions

8.1 Fade In on Scroll

Effect: Elements fade in as they enter viewport.

Framer Motion + useInView:

function FadeInSection({ children }) {
  const ref = useRef(null);
  const isInView = useInView(ref, { once: true, amount: 0.3 });

  return (
    <motion.div
      ref={ref}
      initial={{ opacity: 0, y: 50 }}
      animate={isInView ? { opacity: 1, y: 0 } : {}}
      transition={{ duration: 0.6 }}
    >
      {children}
    </motion.div>
  );
}

8.2 Scroll Progress Indicator

Effect: Shows reading progress.

Implementation:

function ScrollProgress() {
  const [scrollProgress, setScrollProgress] = useState(0);

  useEffect(() => {
    const updateProgress = () => {
      const scrollHeight = document.documentElement.scrollHeight - window.innerHeight;
      const progress = (window.scrollY / scrollHeight) * 100;
      setScrollProgress(progress);
    };

    window.addEventListener('scroll', updateProgress);
    return () => window.removeEventListener('scroll', updateProgress);
  }, []);

  return (
    <motion.div
      className="progress-bar"
      style={{ scaleX: scrollProgress / 100, transformOrigin: 'left' }}
    />
  );
}

Modal & Dialog Interactions

9.1 Modal Fade & Scale

Effect: Modal fades in and scales up.

Framer Motion:

function Modal({ isOpen, onClose, children }) {
  return (
    <AnimatePresence>
      {isOpen && (
        <>
          <motion.div
            className="modal-backdrop"
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            onClick={onClose}
          />

          <motion.div
            className="modal"
            initial={{ opacity: 0, scale: 0.8, y: 50 }}
            animate={{ opacity: 1, scale: 1, y: 0 }}
            exit={{ opacity: 0, scale: 0.8, y: 50 }}
            transition={{ type: "spring", stiffness: 300, damping: 25 }}
          >
            {children}
          </motion.div>
        </>
      )}
    </AnimatePresence>
  );
}

Accessibility: Trap focus, close on Escape key, focus first element.


Toast & Notification Interactions

10.1 Toast Notification

Effect: Toast slides in from top/bottom with auto-dismiss.

Framer Motion:

function Toast({ message, type, onDismiss }) {
  useEffect(() => {
    const timer = setTimeout(onDismiss, 3000);
    return () => clearTimeout(timer);
  }, [onDismiss]);

  return (
    <motion.div
      className={`toast toast-${type}`}
      initial={{ opacity: 0, y: -50, scale: 0.8 }}
      animate={{ opacity: 1, y: 0, scale: 1 }}
      exit={{ opacity: 0, y: -50, scale: 0.8 }}
      transition={{ type: "spring", stiffness: 400, damping: 25 }}
    >
      {message}
      <button onClick={onDismiss}>×</button>
    </motion.div>
  );
}

Accessibility Checklist

For all micro-interactions:

  • Respect prefers-reduced-motion: Disable or simplify animations
  • Keyboard accessible: All interactions work without mouse
  • Focus indicators: Visible focus states
  • Screen reader friendly: Announce state changes with aria-live
  • Touch targets: Minimum 44x44px for mobile
  • Escape key: Close modals/overlays
  • Loading states: Indicate when actions are processing
  • Error states: Clear error messages, not just visual indicators

Performance Checklist

  • GPU-accelerated properties: Use transform and opacity only
  • Avoid layout thrash: Batch DOM reads/writes
  • RequestAnimationFrame: For JavaScript animations
  • Will-change sparingly: Only during active animations
  • Debounce scroll/resize: Avoid excessive repaints
  • Lazy load: Defer non-critical animations
  • Test on low-end devices: Ensure 60 FPS

This catalog is a living document. Patterns are updated based on modern web standards and user research.

Source: SKILL.md on GitHub

1 warning16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill provides a comprehensive toolkit for modern web design, including accessibility guides, performance checklists, and utility scripts. The included Python tools for auditing and template generation operate locally without evidence of malicious behavior or data exfiltration.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    2/8 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 1da73fe. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Dormantupdated 11 months ago

README badge

README badge for freshtechbro/claudedesignskills/modern-web-design