Week 12: Advanced CSS & Design Systems

Animations, Transitions, and Professional Patterns

INFO 153A/253A - Front-End Web Architecture

UC Berkeley School of Information

November 10, 2025

Virtual Lecture via Zoom

Part 1: Prep Work Recap

CSS Animations & Transitions Fundamentals

From Chapter 16: Key Concepts Review

  • Transitions: Smooth property changes over time
  • Transform: Rotate, scale, translate elements
  • Keyframes: Complex multi-step animations
  • CSS Variables: Dynamic, maintainable values

Transitions vs Animations: Core Differences

Transitions

/* State change required */
button {
    background: blue;
    transition: background 0.3s;
}
button:hover {
    background: red;
}

Animations

/* Automatic or triggered */
@keyframes pulse {
    50% { transform: scale(1.1); }
}
.pulse {
    animation: pulse 2s infinite;
}
  • Transitions need a trigger like hover, focus, or class change - they're reactive
  • Animations run independently and can loop, reverse, or pause programmatically
  • Use transitions for micro-interactions - button hovers, menu reveals, form feedback
  • Use animations for attention-grabbing - loading spinners, notifications, onboarding
  • Performance tip: Both use GPU acceleration when transforming or opacity
  • Industry pattern: Transitions for UI feedback, animations for storytelling

Transform Property Fundamentals

/* Transform functions from prep work */
.element {
    /* 2D Transforms */
    transform: rotate(45deg);
    transform: scale(1.5);
    transform: translate(50px, 100px);
    transform: skew(10deg, 20deg);

    /* Multiple transforms chain */
    transform: rotate(45deg) scale(1.2) translateX(50px);

    /* Transform origin changes pivot */
    transform-origin: top left;  /* Default: center */
}
  • Transform doesn't affect layout - other elements don't reflow, huge for performance
  • Order matters in chaining - rotate then translate is different than translate then rotate
  • Transform-origin is the pivot point - critical for rotations and scales
  • Translate is better than position - uses GPU, doesn't trigger reflow
  • Scale maintains aspect ratio - use scaleX/scaleY for directional scaling
  • Professional tip: Always use transform for animations, never top/left

Keyframes Introduction Recap

/* Basic keyframe structure from prep */
@keyframes slidein {
    from {  /* or 0% */
        transform: translateX(-100%);
        opacity: 0;
    }
    to {    /* or 100% */
        transform: translateX(0);
        opacity: 1;
    }
}

/* Applying the animation */
.animated-element {
    animation: slidein 1s ease-out forwards;
    /*         name duration timing fill */
}
  • Keyframes define waypoints - from/to or percentages for complex paths
  • Animation shorthand order - name, duration, timing, delay, count, direction, fill, play
  • Fill-mode controls end state - forwards keeps final frame, backwards applies first frame early
  • Timing functions between keyframes - each segment can have different easing
  • Browser optimizes automatically - GPU acceleration for transform/opacity
  • Reusable across elements - define once, apply to many with different timings

CSS Variables (Custom Properties) Basics

/* From Chapter 10 optional prep */
:root {
    --primary-color: #007bff;
    --transition-speed: 0.3s;
    --border-radius: 8px;
    --spacing-unit: 8px;
}

.button {
    background: var(--primary-color);
    transition: all var(--transition-speed);
    border-radius: var(--border-radius);
    padding: calc(var(--spacing-unit) * 2);
}
  • Variables enable theming - change entire color scheme by updating root values
  • Scoping is powerful - override variables in specific components
  • Calc() with variables - create mathematical relationships between values
  • Runtime modification possible - JavaScript can update CSS variables dynamically
  • Fallback values supported - var(--color, #000) provides default
  • Industry standard: All modern design systems use CSS variables

Performance Considerations

GPU-Accelerated (Fast)

  • transform: translate/scale/rotate
  • opacity changes
  • filter effects

CPU-Bound (Slow)

  • width/height changes
  • top/left/right/bottom
  • margin/padding animations
  • GPU properties create layers - browser composites without repainting
  • Layout changes are expensive - trigger reflow for all descendant elements
  • will-change hints optimization - tells browser to prepare for animation
  • 60fps requires 16ms frames - animations must complete calculations quickly
  • Transform/opacity only rule - for smooth mobile performance, stick to these
  • DevTools Performance tab - profile animations to find janky frames

Part 2: CSS Transitions Deep Dive

Creating Smooth, Professional Interactions

Transition Anatomy

transition: property duration timing-function delay;

transition: background-color 0.3s ease-in-out 0.1s;

Multi-Property Transitions

/* Shopping cart item example */
.cart-item {
    background: white;
    transform: scale(1);
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    border: 2px solid transparent;

    /* Different timings for different properties */
    transition:
        transform 0.2s cubic-bezier(0.4, 0, 0.2, 1),
        box-shadow 0.3s ease-out,
        border-color 0.15s linear,
        background-color 0.25s ease-in;
}

.cart-item:hover {
    transform: scale(1.02);
    box-shadow: 0 8px 16px rgba(0,0,0,0.15);
    border-color: var(--primary-color);
    background: #f8f9fa;
}
  • Stagger timings for depth - faster transforms feel more responsive than color changes
  • Different easings per property - sharp for borders, smooth for shadows creates hierarchy
  • Scale before shadow - mimics real-world physics of lifting off page
  • Subtle is professional - 1.02 scale vs 1.2 scale makes huge difference
  • Border reveals on hover - transparent to color avoids layout shift
  • Orchestrated symphony - multiple properties create rich interaction

Custom Timing Functions

/* Bezier curves for precise control */
.smooth-bounce {
    transition: transform 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
    /* Creates overshoot effect */
}

.swift-out {
    transition: all 0.3s cubic-bezier(0.55, 0, 0.1, 1);
    /* Material Design "Swift Out" */
}

.elastic {
    transition: transform 0.8s cubic-bezier(0.68, -0.6, 0.32, 1.6);
    /* Elastic bounce effect */
}

/* Pre-defined vs custom */
.standard { transition: all 0.3s ease; }        /* cubic-bezier(0.25, 0.1, 0.25, 1) */
.custom { transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); }  /* Google Material */
  • Cubic-bezier creates personality - standard easings feel generic, custom ones feel designed
  • Four control points define curve - x1, y1, x2, y2 between 0 and 1 (mostly)
  • Y values beyond 0-1 create overshoot - negative or >1 makes elastic effects
  • Material Design standardized timings - Swift out, smooth in, specific curves for specific uses
  • Browser DevTools has curve editor - visual tool for creating custom beziers
  • Match timing to brand personality - playful brands use bounce, serious use linear

Transition Gotchas & Solutions

/* Problem: Height auto doesn't transition */
.accordion-wrong {
    height: 0;
    transition: height 0.3s;
}
.accordion-wrong.open {
    height: auto; /* Won't animate! */
}

/* Solution: Use max-height trick */
.accordion-right {
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.3s ease-out;
}
.accordion-right.open {
    max-height: 500px; /* Larger than content */
}
  • Auto values don't transition - browser can't interpolate between 0 and auto
  • Max-height workaround - set larger than needed, overflow hidden clips excess
  • Display none/block kills transitions - use opacity:0 with pointer-events:none instead
  • Transform percentage gotcha - percentages relative to element, not parent
  • Z-index doesn't transition - it's an integer, steps between values
  • Gradient transitions need tricks - animate background-position or use pseudo-elements

State-Based Transition Patterns

/* Loading → Success → Error states */
.submit-button {
    position: relative;
    background: var(--primary);
    transition: all 0.3s;
}

.submit-button.loading {
    background: #6c757d;
    pointer-events: none;
    transform: scale(0.98);
}

.submit-button.success {
    background: #28a745;
    transform: scale(1.05) translateY(-2px);
}

.submit-button.error {
    background: #dc3545;
    animation: shake 0.5s;
}
  • States need different feedback - loading subdued, success lifted, error attention-grabbing
  • Pointer-events control interaction - disable during loading to prevent double-submits
  • Transform for micro-animations - slight scale/translate makes states feel alive
  • Combine transitions with animations - smooth state changes, then attention animation
  • Color alone isn't enough - add motion for accessibility and clarity
  • Reset transitions matter too - returning to default should feel intentional

Accessible Transitions

/* Respect user preferences */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* Or more targeted approach */
@media (prefers-reduced-motion: reduce) {
    .animated-element {
        transition: opacity 0.2s; /* Keep fades, remove motion */
        transform: none !important;
    }
}
  • Motion can trigger vestibular disorders - respect prefers-reduced-motion media query
  • Don't remove all animation - instant changes are jarring, keep subtle fades
  • 0.01ms not 0ms - some browsers optimize away 0ms, breaking JavaScript hooks
  • Focus on opacity over motion - fades are generally safe, movement problematic
  • Test with motion preferences - OS settings let you enable reduced motion
  • Progressive enhancement approach - core functionality works without any animation

Part 3: Transform Property Mastery

2D and 3D Transformations

Transform Space

2D: X-axis (horizontal), Y-axis (vertical)

3D: + Z-axis (depth) + perspective

Transform Chaining & Order

/* Order matters! */
.card-flip {
    /* This moves then rotates */
    transform: translateX(100px) rotate(45deg);
    /* Different from rotate then move! */
}

/* Student roster card example */
.student-card {
    transform-origin: center bottom;
    transition: transform 0.3s;
}

.student-card:hover {
    /* Build complexity with chains */
    transform:
        perspective(1000px)
        rotateX(-10deg)
        translateY(-10px)
        scale(1.05);
}
  • Transforms apply right to left - last transform happens first mathematically
  • Translate before rotate - moves along original axes, not rotated ones
  • Scale affects all subsequent transforms - scaling first multiplies all pixel values
  • Perspective must come first - sets up 3D space for other transforms
  • Transform-origin is the pivot - crucial for realistic rotations
  • Mental model: Matrix multiplication - each transform multiplies the transformation matrix

3D Transforms & Perspective

/* 3D product card */
.product-showcase {
    perspective: 1000px; /* Parent sets viewport */
}

.product-card {
    transform-style: preserve-3d;
    transition: transform 0.6s;
    transform-origin: center;
}

.product-card:hover {
    transform: rotateY(180deg); /* Flip horizontally */
}

.product-card-front,
.product-card-back {
    position: absolute;
    backface-visibility: hidden;
}

.product-card-back {
    transform: rotateY(180deg); /* Pre-rotated */
}
  • Perspective creates depth - smaller values = more dramatic 3D effect
  • Transform-style: preserve-3d - children maintain 3D position, not flattened
  • Backface-visibility hidden - hides element when rotated away from viewer
  • RotateX = horizontal axis flip - like a door opening up/down
  • RotateY = vertical axis flip - like a door opening left/right
  • RotateZ = flat spin - like a record player, no 3D effect needed

Advanced Transform Patterns

/* Parallax scrolling effect */
.parallax-layer {
    transform: translateZ(-1px) scale(2);
    /* Moves back in Z, scales up to compensate */
}

/* Circular menu items */
.menu-item:nth-child(1) { transform: rotate(0deg) translateX(100px) rotate(0deg); }
.menu-item:nth-child(2) { transform: rotate(60deg) translateX(100px) rotate(-60deg); }
.menu-item:nth-child(3) { transform: rotate(120deg) translateX(100px) rotate(-120deg); }

/* Isometric grid */
.isometric {
    transform: rotateX(60deg) rotateZ(45deg);
}
  • Parallax uses Z-axis depth - different layers move at different speeds
  • Circular layouts with math - rotate, translate, counter-rotate keeps items upright
  • Isometric = 2.5D effect - specific rotation angles create pseudo-3D
  • Transform math is powerful - combine with CSS calc() for dynamic layouts
  • GPU acceleration automatic - transforms trigger hardware acceleration
  • Mobile consideration: 3D transforms can drain battery, use sparingly

Transform Performance Optimization

/* Force GPU layer */
.will-animate {
    will-change: transform; /* Hints browser */
    transform: translateZ(0); /* Hack to force layer */
}

/* Bad: Animating position */
@keyframes slide-bad {
    from { left: -100%; }
    to { left: 0; }
}

/* Good: Animating transform */
@keyframes slide-good {
    from { transform: translateX(-100%); }
    to { transform: translateX(0); }
}
  • will-change creates layer early - browser prepares for animation, costs memory
  • translateZ(0) hack - forces GPU layer without will-change (older technique)
  • Transform vs position performance - transform is 10x+ faster, no layout recalc
  • Composite-only properties best - transform and opacity bypass layout/paint
  • Remove will-change after animation - layers consume memory, clean up when done
  • Profile before optimizing - DevTools shows actual performance, not assumptions

Transform Real-World Examples

/* Navigation drawer pattern */
.nav-drawer {
    transform: translateX(-100%);
    transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.nav-drawer.open {
    transform: translateX(0);
}

/* Image zoom on hover */
.gallery-image {
    transform-origin: center;
    transition: transform 0.3s;
    overflow: hidden;
}
.gallery-image img {
    transform: scale(1);
    transition: transform 0.3s;
}
.gallery-image:hover img {
    transform: scale(1.2);
}
  • Off-canvas navigation standard - translateX for side menus, translateY for bottom sheets
  • Image zoom within container - overflow hidden contains scaled image
  • Transform for modals - scale from 0.9 to 1 feels like emergence
  • Mobile gestures with transform - track finger movement, apply as transform
  • Infinite scroll performance - transform for position, not top/left
  • Pattern: Always transform, never position - consistency ensures performance

Part 4: Keyframe Animations

Creating Complex, Multi-Step Animations

Keyframe Timeline

@keyframes timeline {
    0%   { /* Start */ }
    25%  { /* Quarter */ }
    50%  { /* Midpoint */ }
    75%  { /* Three quarters */ }
    100% { /* End */ }
}

Loading Animations

/* Spinning loader */
@keyframes spin {
    to { transform: rotate(360deg); }
}

.spinner {
    width: 40px;
    height: 40px;
    border: 4px solid #f3f3f3;
    border-top: 4px solid var(--primary);
    border-radius: 50%;
    animation: spin 1s linear infinite;
}

/* Pulsing dots loader */
@keyframes pulse {
    0%, 80%, 100% { transform: scale(1); opacity: 1; }
    40% { transform: scale(1.3); opacity: 0.7; }
}

.dot { animation: pulse 1.4s infinite ease-in-out; }
.dot:nth-child(2) { animation-delay: 0.2s; }
.dot:nth-child(3) { animation-delay: 0.4s; }
  • Infinite animations for waiting states - conveys ongoing process to user
  • Linear timing for constant motion - spinning should be mechanical, not organic
  • Animation-delay creates sequence - staggered dots feel more dynamic
  • Scale and opacity together - multiple properties make animation richer
  • Keep loaders lightweight - users see these when performance matters most
  • Industry standard: 1-2 second loops - faster feels frantic, slower feels broken

Notification Animations

/* Slide in notification */
@keyframes slideIn {
    from {
        transform: translateX(100%);
        opacity: 0;
    }
    to {
        transform: translateX(0);
        opacity: 1;
    }
}

/* Attention-grabbing shake */
@keyframes shake {
    0%, 100% { transform: translateX(0); }
    10%, 30%, 50%, 70%, 90% { transform: translateX(-10px); }
    20%, 40%, 60%, 80% { transform: translateX(10px); }
}

.notification {
    animation: slideIn 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
.notification.error {
    animation: slideIn 0.3s, shake 0.5s 0.3s; /* Chain animations */
}
  • Entry animations grab attention - motion in peripheral vision triggers focus
  • Overshoot easing feels energetic - slight bounce makes notifications feel urgent
  • Shake for errors is universal - mimics head shaking "no" gesture
  • Chain animations with delay - enter first, then attention animation
  • Exit animations provide closure - fadeOut or slideOut confirms dismissal
  • Duration psychology: 0.3s entry feels instant but smooth

Complex Multi-Step Animations

/* Student registration flow animation */
@keyframes formProgress {
    0% {
        transform: translateX(-100%);
        opacity: 0;
    }
    20% {
        transform: translateX(0);
        opacity: 1;
    }
    40% {
        transform: scale(1.05);
    }
    60% {
        transform: scale(1) rotate(1deg);
    }
    80% {
        transform: rotate(-1deg);
    }
    100% {
        transform: rotate(0);
        opacity: 1;
    }
}

.form-step {
    animation: formProgress 1.5s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
  • Percentages create story beats - enter, emphasize, settle, complete
  • Micro-movements add personality - slight rotation feels playful and human
  • Forwards fill mode essential - maintains end state after animation
  • 60% sweet spot for main action - not rushed, not dragging
  • Easing across entire animation - creates cohesive feel vs per-keyframe easing
  • Test at different speeds - users may have different motion preferences

Performance-Optimized Keyframes

/* Bad: Animating expensive properties */
@keyframes bad-resize {
    0% { width: 100px; height: 100px; }
    50% { width: 200px; height: 200px; }
    100% { width: 100px; height: 100px; }
}

/* Good: Using transform for same effect */
@keyframes good-resize {
    0%, 100% { transform: scale(1); }
    50% { transform: scale(2); }
}

/* Optimization: Reduce keyframe points */
@keyframes optimized {
    /* Only define changes */
    50% { transform: scale(1.5) rotate(180deg); }
    /* Browser interpolates 0% and 100% */
}
  • Transform-only keyframes perform best - avoid width, height, margin changes
  • Fewer keyframes = smoother animation - browser interpolation is optimized
  • Omit unchanged properties - if only transform changes, don't repeat other props
  • Animation-timing-function per keyframe - can optimize each segment
  • Test on slowest target device - animations that work on desktop may lag on mobile
  • Consider reduced motion - provide simpler alternatives for accessibility

Animation Orchestration

/* Staggered list animation */
.list-item {
    opacity: 0;
    transform: translateY(20px);
    animation: fadeInUp 0.5s forwards;
}

.list-item:nth-child(1) { animation-delay: 0.1s; }
.list-item:nth-child(2) { animation-delay: 0.2s; }
.list-item:nth-child(3) { animation-delay: 0.3s; }
/* Use CSS custom properties for dynamic delays */
.list-item { animation-delay: calc(var(--index) * 0.1s); }

@keyframes fadeInUp {
    to {
        opacity: 1;
        transform: translateY(0);
    }
}
  • Staggered animations create flow - elements appear to cascade naturally
  • Animation-delay orchestrates timing - precise control over sequence
  • CSS variables enable dynamic delays - JavaScript can set --index for each item
  • 0.1s stagger feels connected - longer delays feel disconnected
  • Forwards fill maintains end state - crucial for enter animations
  • Pattern scales to any list size - works for 3 items or 300

Part 5: Design Systems & CSS Variables

Building Maintainable, Themeable Interfaces

Colors
--primary
--secondary
Spacing
--space-xs
--space-md
Animation
--duration
--easing

CSS Variables for Animation Systems

/* Animation design tokens */
:root {
    /* Timing tokens */
    --duration-instant: 0.1s;
    --duration-fast: 0.2s;
    --duration-normal: 0.3s;
    --duration-slow: 0.5s;
    --duration-slower: 0.8s;

    /* Easing tokens */
    --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
    --ease-out: cubic-bezier(0.0, 0, 0.2, 1);
    --ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);

    /* Scale tokens */
    --scale-xs: 0.95;
    --scale-sm: 0.98;
    --scale-md: 1.05;
    --scale-lg: 1.1;
}
  • Tokens create consistency - all animations use same timing vocabulary
  • Semantic naming over values - "fast" not "200ms" makes intent clear
  • Progressive scale - consistent increments feel designed, not random
  • Custom easings as variables - change animation feel globally with one edit
  • Design system alignment - animation tokens match spacing/color token patterns
  • Documentation in code - variable names self-document usage

Dynamic Theme Switching

/* Base theme */
:root {
    --bg-primary: #ffffff;
    --text-primary: #333333;
    --transition-theme: all 0.3s var(--ease-in-out);
}

/* Dark theme */
[data-theme="dark"] {
    --bg-primary: #1a1a1a;
    --text-primary: #ffffff;
}

/* Components use variables */
.card {
    background: var(--bg-primary);
    color: var(--text-primary);
    transition: var(--transition-theme);
}

/* JavaScript toggle */
// document.body.dataset.theme = 'dark';
  • CSS variables enable runtime theming - no recompilation needed unlike Sass
  • Data attributes for theme state - cleaner than class toggling
  • Transition during theme change - smooth color shifts feel polished
  • Single source of truth - all colors derive from theme variables
  • Respects system preferences - can default to OS dark mode setting
  • Performance: CSS variables are fast - browser optimizes cascade

Component Animation Patterns

/* Reusable animation mixins via CSS */
.animate-in {
    animation: var(--animation-name, fadeIn)
              var(--duration-normal)
              var(--ease-out)
              forwards;
}

/* Component customization */
.modal { --animation-name: scaleIn; }
.drawer { --animation-name: slideIn; }
.toast { --animation-name: fadeInUp; }

/* Responsive animations */
@media (prefers-reduced-motion: no-preference) {
    .animate-in { /* Full animation */ }
}
@media (prefers-reduced-motion: reduce) {
    .animate-in { animation: fadeIn 0.2s; }
}
  • Variables make animations reusable - same class, different effects per component
  • Fallback values provide defaults - var(--name, fallback) syntax prevents breaks
  • Component-specific overrides - modals scale, drawers slide, toasts fade up
  • Media queries respect preferences - reduced motion gets simpler animations
  • Single animation utility class - DRY principle for animation logic
  • Composable patterns - combine multiple animation classes for complex effects

Advanced Variable Techniques

/* Calculated relationships */
:root {
    --base-duration: 0.2s;
    --duration-2x: calc(var(--base-duration) * 2);
    --duration-3x: calc(var(--base-duration) * 3);

    --base-size: 8px;
    --space-1: var(--base-size);
    --space-2: calc(var(--base-size) * 2);
    --space-3: calc(var(--base-size) * 3);
}

/* Contextual overrides */
.fast-zone {
    --base-duration: 0.1s; /* All children animate faster */
}

/* JavaScript integration */
.dynamic-element {
    transform: translateX(calc(var(--mouse-x) * 1px))
               translateY(calc(var(--mouse-y) * 1px));
}
  • Calc() with variables enables systems - mathematical relationships between values
  • Base values create rhythm - all timing/spacing derives from base unit
  • Scoped overrides for context - form areas faster, reading areas slower
  • JavaScript can set CSS variables - element.style.setProperty('--mouse-x', x)
  • Variables cascade like properties - inheritance and specificity apply
  • Performance: Calc is computed once - not recalculated on every frame

Production-Ready Animation System

/* Complete animation system */
:root {
    /* State: Normal */
    --state-duration: var(--duration-normal);
    --state-easing: var(--ease-in-out);

    /* State: Loading */
    --loading-duration: var(--duration-slow);
    --loading-easing: linear;

    /* State: Error */
    --error-duration: var(--duration-fast);
    --error-easing: var(--ease-bounce);
}

/* Usage */
.button {
    transition: all var(--state-duration) var(--state-easing);
}
.button.loading {
    --state-duration: var(--loading-duration);
    --state-easing: var(--loading-easing);
}
  • State-based animation systems - different timing for different states
  • Semantic state variables - loading, error, success have different feels
  • Single source for changes - update root, entire system updates
  • Designer-developer contract - variables become shared language
  • Testable and measurable - animation tokens can be validated
  • Production pattern: Used by GitHub, Stripe, Slack design systems

Week 12 Summary

Advanced CSS & Design Systems

Key Takeaways

  • Transitions for micro-interactions - hover states, form feedback, state changes
  • Transform for performant animation - always use instead of position properties
  • Keyframes for complex storytelling - multi-step animations, loaders, orchestration
  • CSS Variables for systems - theming, consistency, maintainability
  • Performance matters - GPU acceleration, reduced motion, optimization
  • Design systems thinking - tokens, patterns, reusability over one-offs

Moving Forward

These techniques form the foundation of modern UI development.

Practice with real components: modals, drawers, cards, notifications.

Always consider accessibility and performance.