UX Feedback Patterns Skill
User feedback mechanisms for communicating state changes, success, errors, and progress. This skill covers visual, auditory, and haptic feedback patterns.
Related Skills
-
material-symbols-v3 : Icon names for status indicators (check_circle , error , warning )
-
ux-iconography : Icon + text patterns for feedback
-
ux-animation-motion : Anime.js animations for feedback effects
Feedback Types
- Inline Feedback
Immediate feedback near the action:
<button>Save</button> <span class="inline-feedback" role="status" aria-live="polite"> Saved successfully </span>
.inline-feedback { font-size: var(--step--1); color: var(--color-success); opacity: 0; transition: opacity 0.2s ease; }
.inline-feedback.visible { opacity: 1; }
- Toast Notifications
Non-blocking temporary messages as a web component:
class ToastContainer extends HTMLElement { #container; // Direct reference - NO querySelector
constructor() { super(); this.attachShadow({ mode: 'open' });
// Build and store direct reference
this.#container = document.createElement('div');
this.#container.className = 'toast-container';
this.#container.setAttribute('part', 'container');
this.shadowRoot.appendChild(this.#container);
}
show(message, type = 'info', duration = 3000) {
const toast = document.createElement('div');
toast.className = toast toast-${type};
toast.setAttribute('role', 'alert');
toast.setAttribute('part', 'toast');
toast.textContent = message;
this.#container.appendChild(toast); // Direct reference
// Auto-dismiss
setTimeout(() => {
toast.classList.add('exiting');
toast.addEventListener('animationend', () => toast.remove());
}, duration);
} }
customElements.define('toast-container', ToastContainer);
.toast { padding: var(--space-s) var(--space-m); background: var(--theme-surface-variant); border-radius: var(--space-2xs); animation: slideIn 0.2s ease; }
.toast-success { border-left: 4px solid var(--color-success); }
.toast-error { border-left: 4px solid var(--color-error); }
.toast.exiting { animation: slideOut 0.2s ease forwards; }
- Confirmation Dialogs
For destructive or important actions:
<dialog class="confirm-dialog"> <h2>Confirm Action</h2> <p>Are you sure you want to proceed?</p> <div class="dialog-actions"> <button class="btn-secondary">Cancel</button> <button class="btn-danger">Delete</button> </div> </dialog>
- Progress Indicators
For long-running operations:
<!-- Determinate progress --> <progress value="60" max="100" aria-label="Upload progress">60%</progress>
<!-- Indeterminate/spinner --> <div class="spinner" role="status" aria-label="Loading"> <span class="sr-only">Loading...</span> </div>
Success Feedback
Visual Patterns
/* Success color */ .success { color: var(--color-success); }
/* Success icon - use Material Symbol / / <span class="icon" aria-hidden="true">check_circle</span> */ .icon-success { color: var(--color-success); }
/* Success border */ .input-success { border-color: var(--color-success); }
Animation
import { successBounce, glow } from '../../utils/animations.js';
// On successful action successBounce(element); glow(element, { color: 'rgba(74, 222, 128, 0.6)' });
Announcements
announce(message) { // For screen readers this.#announcer.textContent = ''; requestAnimationFrame(() => { this.#announcer.textContent = message; }); }
// Usage this.announce('Word completed! 3 points earned.');
Error Feedback
Visual Patterns
/* Error color */ .error { color: var(--color-error); }
/* Error state */ [aria-invalid="true"] { border-color: var(--color-error); }
/* Error message */ .error-message { color: var(--color-error); font-size: var(--step--1); }
Shake Animation
import { shake } from '../../utils/animations.js';
// On validation failure shake(inputContainer, { intensity: 6 });
Error Message Structure
<div class="field"> <input aria-invalid="true" aria-describedby="error-1"> <span id="error-1" class="error-message" role="alert"> Please enter a valid email address </span> </div>
Loading States
Button Loading
.button[aria-busy="true"] { position: relative; color: transparent; pointer-events: none; }
.button[aria-busy="true"]::after { content: ''; position: absolute; inset: 0; margin: auto; width: 1em; height: 1em; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: spin 0.6s linear infinite; }
Skeleton Loading
.skeleton { background: linear-gradient( 90deg, var(--theme-surface-variant) 25%, var(--theme-surface) 50%, var(--theme-surface-variant) 75% ); background-size: 200% 100%; animation: shimmer 1.5s infinite; border-radius: var(--space-2xs); }
@keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
Page Loading
<div class="loading-overlay" role="status"> <div class="spinner"></div> <span class="sr-only">Loading game...</span> </div>
Progress Indicators
Linear Progress
.progress-bar { height: 4px; background: var(--theme-outline-variant); border-radius: 2px; overflow: hidden; }
.progress-fill { height: 100%; background: var(--theme-primary); transition: width 0.3s ease; }
Step Progress
<ol class="steps" aria-label="Progress"> <li data-status="complete" aria-current="false">Step 1</li> <li data-status="current" aria-current="step">Step 2</li> <li data-status="pending" aria-current="false">Step 3</li> </ol>
.steps [data-status="complete"] { color: var(--color-success); }
.steps [data-status="current"] { color: var(--theme-primary); font-weight: 600; }
.steps [data-status="pending"] { color: var(--theme-on-surface-variant); }
Circular Progress
.progress-circle { --progress: 0; width: 60px; height: 60px; border-radius: 50%; background: conic-gradient( var(--theme-primary) calc(var(--progress) * 1%), var(--theme-outline-variant) 0 ); }
Live Regions
Status Updates
<div role="status" aria-live="polite" aria-atomic="true"> Score: 42 points </div>
Alerts
<div role="alert" aria-live="assertive"> Session expired. Please log in again. </div>
Implementation
class Announcer { #region;
constructor() { this.#region = document.createElement('div'); this.#region.setAttribute('role', 'status'); this.#region.setAttribute('aria-live', 'polite'); this.#region.setAttribute('aria-atomic', 'true'); this.#region.className = 'sr-only'; document.body.appendChild(this.#region); }
announce(message, priority = 'polite') { this.#region.setAttribute('aria-live', priority); this.#region.textContent = ''; requestAnimationFrame(() => { this.#region.textContent = message; }); } }
Timing Guidelines
Feedback Type Duration Use Case
Micro-animation 100-200ms Button press, toggle
State transition 200-300ms Page change, modal
Toast display 3-5 seconds Success message
Error display Until dismissed Validation error
Loading indicator Immediate Any async operation
Accessibility Checklist
-
Success/error announced to screen readers
-
Focus moved to relevant element after action
-
Loading states communicated with aria-busy
-
Progress communicated with proper ARIA
-
Animations respect prefers-reduced-motion
-
Color is not the only indicator of state
-
Error messages are associated with inputs