Skip to content
SalesforceSkills

Animations

Adds smooth transitions, fade-ins, and subtle animations that make your components feel alive.

Purposeful, not decorative`prefers-reduced-motion` is mandatoryFastOnly animate `transform` and `opacity`Ease-out for entrances, ease-in for exits

Skill Details

Install this skill

Versionv1.1.0AuthorJorge ArteagaLicenseMITSections12

Works with

Claude CodeCursorWindsurf

Add purposeful, accessible motion to Lightning Web Components. Every animation must serve a functional purpose and respect user preferences.

Core Principles

1
Purposeful, not decorative — animation communicates state changes, not showing off
2
prefers-reduced-motion is mandatory — always provide a no-animation fallback
3
Fast — 150-300ms for most transitions; never more than 500ms
4
Only animate transform and opacity — these are GPU-composited and performant
5
Ease-out for entrances, ease-in for exits — matches natural physical motion

Reduced Motion (Mandatory)

Every component with animation must include this. No exceptions.

CSS
@media (prefers-reduced-motion: reduce) {
    *,
    *: :before,
    *: :after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

Alternatively, per-element:

CSS
.animated-element {
    transition: opacity 200ms ease-out, transform 200ms ease-out;
}

@media (prefers-reduced-motion: reduce) {
    .animated-element {
        transition: none;
    }
}

Timing Reference

Easing Functions

Entry Animations

Fade In

CSS
.fade-enter {
    opacity: 0;
    animation: fadeIn 300ms ease-out forwards;
}

@keyframes fadeIn {
    to { opacity: 1; }
}

Slide Up + Fade

The most versatile entry animation — content rises into view.

CSS
.slide-up-enter {
    opacity: 0;
    transform: translateY(12px);
    animation: slideUp 300ms ease-out forwards;
}

@keyframes slideUp {
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

Scale In (Modals, Popovers)

CSS
.scale-enter {
    opacity: 0;
    transform: scale(0.95);
    animation: scaleIn 200ms ease-out forwards;
}

@keyframes scaleIn {
    to {
        opacity: 1;
        transform: scale(1);
    }
}

Exit Animations

Fade Out

CSS
.fade-exit {
    animation: fadeOut 200ms ease-in forwards;
}

@keyframes fadeOut {
    to { opacity: 0; }
}

Slide Down + Fade (Reverse of entry)

CSS
.slide-down-exit {
    animation: slideDown 200ms ease-in forwards;
}

@keyframes slideDown {
    to {
        opacity: 0;
        transform: translateY(12px);
    }
}

Staggered List Reveals

Show list items one by one with increasing delay for a cascading effect.

HTML
<class="code-tag">template>
    <class="code-tag">ul class="stagger-list">
        <class="code-tag">template for:each={items} for:item="item" for:index="index">
            <class="code-tag">li key={item.id} class="stagger-item" style={item.staggerStyle}>
                {item.name}
            </class="code-tag">li>
        </class="code-tag">template>
    </class="code-tag">ul>
</class="code-tag">template>
JavaScript
get items() {
    return this._rawItems.map((item, index) => ({
        ...item,
        staggerStyle: class="code-string">`animation-delay: ${index * 50}ms`
    }));
}
CSS
.stagger-item {
    opacity: 0;
    transform: translateY(8px);
    animation: slideUp 300ms ease-out forwards;
}

.stagger-list {
    display: flex;
    flex-direction: column;
    gap: var(--slds-g-spacing-2, 0.5rem);
}

State Transitions

Expand / Collapse

CSS
.expandable {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 200ms ease-out;
}

.expandable--open {
    grid-template-rows: 1fr;
}

.expandable__inner {
    overflow: hidden;
}

Tab Content Transition

CSS
.tab-panel {
    opacity: 0;
    transform: translateX(8px);
    transition: opacity 200ms ease-out, transform 200ms ease-out;
}

.tab-panel--active {
    opacity: 1;
    transform: translateX(0);
}

Toggle / Switch

CSS
.toggle-track {
    width: 40px;
    height: 22px;
    border-radius: var(--slds-g-radius-border-pill, 9999px);
    background: var(--slds-g-color-surface-container-3, #e5e5e5);
    transition: background-color 150ms ease;
    position: relative;
    cursor: pointer;
}

.toggle-track--active {
    background: var(--slds-g-color-accent-1, #0176d3);
}

.toggle-thumb {
    width: 18px;
    height: 18px;
    border-radius: var(--slds-g-radius-circle);
    background: var(--slds-g-color-surface-1, #ffffff);
    box-shadow: var(--slds-g-shadow-1);
    position: absolute;
    top: 2px;
    left: 2px;
    transition: transform 150ms ease-out;
}

.toggle-track--active .toggle-thumb {
    transform: translateX(18px);
}

Loading Sequences

Skeleton Pulse (from sf-lwc-ux)

CSS
@keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.4; }
}

.skeleton {
    background: var(--slds-g-color-surface-container-2, #f3f3f3);
    border-radius: var(--slds-g-radius-border-1, 0.125rem);
    animation: pulse 1.5s ease-in-out infinite;
}

Shimmer Effect

CSS
@keyframes shimmer {
    0% { background-position: -200% 0; }
    100% { background-position: 200% 0; }
}

.skeleton-shimmer {
    background: linear-gradient(
        90deg,
        var(--slds-g-color-surface-container-2, #f3f3f3) 25%,
        var(--slds-g-color-surface-container-1, #f8f8f8) 50%,
        var(--slds-g-color-surface-container-2, #f3f3f3) 75%
    );
    background-size: 200% 100%;
    animation: shimmer 1.5s ease-in-out infinite;
    border-radius: var(--slds-g-radius-border-1, 0.125rem);
}

Spinner with Context

HTML
<class="code-tag">template>
    <class="code-tag">div class="loading-container" if:true={isLoading}>
        <class="code-tag">lightning-spinner alternative-text="Loading" size="small"></class="code-tag">lightning-spinner>
        <class="code-tag">span class="loading-text">{loadingMessage}</class="code-tag">span>
    </class="code-tag">div>
</class="code-tag">template>
CSS
.loading-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--slds-g-spacing-3, 0.75rem);
    padding: var(--slds-g-spacing-8, 2rem);
}

.loading-text {
    font-size: var(--slds-g-font-size-2, 0.75rem);
    color: var(--slds-g-color-on-surface-2, #444444);
    animation: fadeIn 300ms ease-out 500ms both;
}

Demo Choreography Sequences

Patterns specifically designed to make demos and PoCs visually impressive.

Dashboard Card Cascade

Stagger dashboard metric cards for a "building the picture" effect:

JavaScript
async loadDashboard() {
    this.isLoading = true;
    const data = await getDashboardMetrics();

    class="code-comment">// Assign staggered delays for visual cascade
    this.metrics = data.map((metric, index) => ({
        ...metric,
        cardStyle: class="code-string">`animation-delay: ${index * 80}ms`
    }));

    this.isLoading = false;
}
CSS
.dashboard-card {
    opacity: 0;
    transform: translateY(16px) scale(0.98);
    animation: cardReveal 400ms ease-out forwards;
}

@keyframes cardReveal {
    to {
        opacity: 1;
        transform: translateY(0) scale(1);
    }
}

Number Counter Animation

Animate KPI values counting up from zero for dramatic effect:

JavaScript
animateValue(element, start, end, duration) {
    const range = end - start;
    const startTime = performance.now();

    const step = (currentTime) => {
        const elapsed = currentTime - startTime;
        const progress = Math.min(elapsed / duration, 1);

        class="code-comment">// Ease-out curve
        const eased = 1 - Math.pow(1 - progress, 3);
        const current = start + (range * eased);

        element.textContent = this.formatValue(Math.round(current));

        if (progress < 1) {
            requestAnimationFrame(step);
        }
    };

    requestAnimationFrame(step);
}

Data Refresh Pulse

When demo data updates (e.g., after saving a record), briefly pulse the updated values:

CSS
.value-updated {
    animation: valueFlash 600ms ease-out;
}

@keyframes valueFlash {
    0% { background-color: var(--slds-g-color-success-container-1, #e6f4ea); }
    100% { background-color: transparent; }
}
JavaScript
class="code-comment">// After data refresh, mark changed values
handleDataRefresh(newData) {
    this.metrics = newData.map((metric, i) => ({
        ...metric,
        isUpdated: metric.value !== this._previousMetrics?.[i]?.value
    }));

    class="code-comment">// Clear the update flag after animation completes
    setTimeout(() => {
        this.metrics = this.metrics.map(m => ({ ...m, isUpdated: false }));
    }, 700);
}

Coordinated Page Reveal

For demo landing pages, reveal sections in sequence:

CSS
.section-hero { animation: slideUp 400ms ease-out forwards; }
.section-metrics { animation: slideUp 400ms ease-out 150ms forwards; opacity: 0; }
.section-chart { animation: slideUp 400ms ease-out 300ms forwards; opacity: 0; }
.section-list { animation: slideUp 400ms ease-out 450ms forwards; opacity: 0; }

Anti-PatternsReference

Scoring Rubric (100 Points)Reference

Cross-Skill IntegrationReference

DurationUse CaseEasing
100msHover/focus state changesease
150msButton press, toggle, micro-feedbackease-out
200msPanel expand/collapse, tab switchease-out
300msModal open, card entranceease-out
400msPage-level transitions, staggered listease-out
500msMaximum — complex multi-element sequencesease-in-out
NameCSSMotion Feel
Ease-outcubic-bezier(0, 0, 0.2, 1)Fast start, gentle stop (entrances)
Ease-incubic-bezier(0.4, 0, 1, 1)Gentle start, fast stop (exits)
Ease-in-outcubic-bezier(0.4, 0, 0.2, 1)Symmetric (state changes)
Springcubic-bezier(0.34, 1.56, 0.64, 1)Slight overshoot (playful)
Do NOTDo Instead
Animate width, height, top, leftAnimate transform and opacity
Animation longer than 500msKeep 150-300ms for most transitions
Animate on page load with no triggerAnimate in response to user action or data arrival
Bouncing/pulsing attention-grabbersSubtle one-shot transitions
Missing prefers-reduced-motionAlways include the media query
animation-iteration-count: infinite (except loading)Use forwards fill mode for one-shot
CategoryPointsPass Criteria
Reduced Motion25prefers-reduced-motion media query present; all animations disabled
Purpose20Every animation communicates a state change, not decorative
Performance20Only transform/opacity animated; no layout thrashing
Timing15Durations within 100-500ms range; appropriate easing per use case
SLDS Compliance10All colors/sizes in animations use --slds-g-* hooks
Consistency10Same animation patterns used across the component suite
SkillRelationship
sf-lwc-uxSkeleton/loading states use motion patterns defined here
sf-lwc-mobileMobile animations must be faster; reduced motion critical
sf-lwc-stylingUtility classes can include transition properties
sf-lwc-designAll animated colors/sizes must use SLDS 2 hooks
sf-se-demo-scriptsDemo choreography sequences create impressive wow moments