Web design concepts
A quick reference of web design and interactive scroll techniques, to bring them back at a glance.
Interactive scroll
- Smooth scroll Lenis Scroll with inertia and easing that makes the page feel fluid and premium. Foundation of almost every scroll effect; gives that buttery feel. Seen on: lenis.darkroom.engineering, the library home page, running on Lenis. Ask your agent for it: Add Lenis smooth scroll to this page. Install it, start it at the entry point and hook its loop to requestAnimationFrame. Do not touch scroll-behavior in CSS and do not break anchor links: check that a link to #section still gets there. Respect prefers-reduced-motion by turning the inertia off.
- Parallax CSS · JS Layers moving at different speeds while scrolling, creating a sense of depth. Immersive heroes and visual storytelling. Ask your agent for it: Make this section background move slower than the content on scroll. Use transform: translate3d, never top or background-position, which force a relayout. Keep the amplitude to a few pixels, not half the screen, and turn it off under prefers-reduced-motion.
- Scroll Snap CSS Scrolling snaps automatically to each section or slide (scroll-snap-type). Full-screen presentations, carousels and slide-like sections. Seen on: stripe.com, its homepage carousels snap with scroll-snap. Ask your agent for it: Turn these sections into slides that snap on scroll, in plain CSS: scroll-snap-type on the container and scroll-snap-align on each child. Use proximity rather than mandatory if a section can be taller than the screen, otherwise people cannot reach the bottom of it.
- Scroll-driven animations CSS scroll()/view() Animations tied to scroll progress, now native in CSS with no JS needed. Progress bars, reveals and light parallax at 0 KB. Seen on: stripe.com, animates with animation-timeline, no scroll JavaScript. Ask your agent for it: Tie this animation to scroll progress with native CSS: animation-timeline with scroll() or view(), no JavaScript. Leave a fallback for browsers without support using @supports (animation-timeline: view()), and make sure the element still looks right, just static, when unsupported.
- Scroll reveal IntersectionObserver Elements that appear or animate as they enter the viewport. Reveal sections on scroll; with stagger it looks very elegant. Seen on: awwwards.com, cards appear as they enter the viewport, with IntersectionObserver rather than a scroll listener. Ask your agent for it: Make these blocks appear as they enter the viewport, with IntersectionObserver rather than a scroll listener. Animate each element once and then stop observing it. Important: the content must be in the HTML and visible by default, with the animation hiding it only when JavaScript runs, so it never disappears for a crawler.
- Sticky / Pin CSS · GSAP An element sticks on screen while the rest of the content keeps moving. Stacking cards, pinned demos and chapters. Seen on: vercel.com, eleven elements that stick as you scroll. Ask your agent for it: Pin this column while the one next to it keeps scrolling, using position: sticky and top. Before assuming it works, check that no ancestor has overflow hidden, auto or scroll: that is what breaks sticky nine times out of ten, and it fails silently.
- Horizontal scroll GSAP Sideways movement driven by the vertical scroll. Galleries, timelines and portfolios. Ask your agent for it: Turn this section into a sideways move driven by vertical scroll. Pin the container and shift the cards with transform. Leave an exit: when the strip ends the page must keep scrolling normally, and on mobile a scroll-snap carousel beats hijacking the scroll.
- Marquee CSS · JS A strip of text or logos looping infinitely. Client logo strips and moving headlines. Seen on: vercel.com, the logo strip that never stops moving. Ask your agent for it: Build a logo strip that loops with no visible seam. Duplicate the content and animate the whole set with transform: translateX in a linear @keyframes, which is what hides the jump. Pause it on hover and under prefers-reduced-motion. Do not use the marquee tag, it is dead.
- Scrollytelling JS Telling a story driven by scroll, with text and visuals in sync. Interactive features, 'how it works' sections, narrated data. Ask your agent for it: Build a scroll-driven story: one visual pinned to one side and text steps on the other that change it. Detect the active step with IntersectionObserver. Every step has to make sense read on its own, without the visual, because that is what a crawler or a broken-JavaScript visitor gets.
- Page transitions View Transitions · barba.js Animated transition when navigating between pages or views. Make navigation feel app-like, without harsh reloads. Ask your agent for it: Add a transition between pages with the browser View Transitions API, not a library. Wrap the change in document.startViewTransition and check it exists first. Without support, navigation must stay instant and correct: the transition is a bonus, never a requirement.
Pointer & micro-interactions
- Tilt 3D JS · CSS An element tilts in 3D following the mouse (perspective + rotateX/Y). Product cards, avatars and highlighted elements. Ask your agent for it: Make this card tilt in 3D following the mouse. The container gets perspective and the card rotateX/rotateY computed from the cursor position inside it. Ease it back on leave and cap the tilt around 8 degrees: more than that is dizzying and warps the text. Do not mount it on touch, where there is no cursor and the listener is pure waste.
- Hover magnético JS A button attracts the cursor by moving slightly toward it. CTAs and buttons that invite a click. Ask your agent for it: Make this button drift toward the cursor when the mouse comes close. Compute the distance to its center and move it with transform, 10px at most, easing back on leave. The detection area can be larger than the button, but the CLICKABLE area must not move with it: a button that runs away never gets pressed.
- Cursor personalizado (lerp) JS A custom cursor that follows the mouse with easing (linear interpolation). Creative brands and portfolios with personality. Ask your agent for it: Replace the cursor with a dot that trails it, interpolating its position each frame with requestAnimationFrame. Watch three things: hiding the system cursor removes affordances for people with low vision, so only do it under @media (pointer: fine); do not mount it on touch; and switch it off under prefers-reduced-motion.
- Micro-interacciones CSS Tiny feedbacks on hover, focus and click (scale, color, shadow). They make the UI feel alive; they reinforce each action. Seen on: apple.com, every transition carries its own curve, not the default one. Ask your agent for it: Give these controls a response on hover, focus and press: a small change of scale, color or shadow, with its own easing curve rather than the default. Make the FOCUS state as visible as the hover one, because keyboard users only get that one. Nothing over 200ms: a slow micro-interaction reads as lag.
- Cursor blend mix-blend-mode A cursor or shapes that invert the color of what's underneath. Eye-catching effect in portfolios and creative brands. Ask your agent for it: Make this custom cursor invert whatever is under it, with mix-blend-mode: difference. Check first that no ancestor has a backdrop-filter or a filter: either one creates a stacking context and the blend stops seeing the background. And check the text underneath stays readable, which is what this usually breaks.
- Spotlight / glow radial-gradient · JS A glow that follows the cursor inside a card or button. Brings cards to life (used on this very site on hover). Ask your agent for it: Make these cards light up a glow that follows the cursor. Store the mouse position in two CSS variables from JavaScript and paint the glow with a radial-gradient that reads them, so the per-frame work is writing two numbers instead of recomputing styles. One listener on the container, not one per card. The glow sits behind the content with pointer-events: none.
Aesthetics & effects
- Glassmorphism backdrop-filter Frosted-glass effect: blurred, translucent background. Modern floating bars, cards and menus. Seen on: tailwindcss.com, the translucent layers in its interface. Ask your agent for it: Give this panel a frosted-glass look: semi-transparent background plus backdrop-filter with blur and a touch of saturate. Two warnings that change the outcome: backdrop-filter creates a stacking context, so anything position: fixed inside it stops covering the viewport; and text over a moving backdrop still has to clear 4.5:1, so raise the opacity until it does.
- Neumorphism box-shadow Surfaces with soft shadows that look like extruded plastic. Buttons and controls with a tactile feel (watch the contrast). Ask your agent for it: Give these controls the neumorphic relief, with two box-shadows (a light one top-left, a dark one bottom-right) in the SAME color as the background. Before accepting it, measure the contrast: this style usually leaves edges under 3:1 and then nobody can tell what is pressable. If it falls short, add a border or a background change on focus.
- Aurora / mesh gradient CSS Organic, diffuse gradients, often animated, used as background. Lively backgrounds without clutter (like this very site). Seen on: astro.build, the background is stacked gradients, not an image. Ask your agent for it: Build an aurora background: several overlapping, blurred radial-gradients drifting slowly. Do it in CSS with transform, no canvas and no images. Animate transform and opacity only, never background-position, which repaints the whole surface. Make the cycle tens of seconds long and stop it under prefers-reduced-motion.
- Clip-path y máscaras CSS Reveal or crop content with shapes (clip-path, mask). Stylish reveals, uncovering text and image transitions. Seen on: vercel.com, crops and fades edges with CSS masks, no images. Ask your agent for it: Crop this section into a shape with clip-path, or fade its edge with mask-image and a gradient. Two things: clipped content still exists for a screen reader, so if it is truly redundant remove it from the DOM and not just from view; and clip-path also cuts the focus ring of any control near the edge, so leave them room.
- View Transitions CSS/JS API Native animated transition between two states or pages. Theme changes and smooth navigation (used by this site's theme toggle). Seen on: astro.build, page-to-page transitions handled by the browser API. Ask your agent for it: Animate the change between these two states with the View Transitions API: wrap the DOM update in document.startViewTransition and check it exists first. To make an element travel between states give it the same view-transition-name in both, and keep that name UNIQUE on the page: two elements sharing one at the same time cancel the whole transition.
- Skeleton loaders CSS Blocks shaped like what is coming, shown while the real content loads. Two versions: still, or with a shimmer running across. Perceived speed; they avoid the blank screen and the layout jump on arrival. Seen on: github.com, loading gaps keep the shape of what is coming. Ask your agent for it: While loading, show a skeleton shaped like what is coming, not a spinner: blocks the real size of the title, the text and the image. Give me both versions, the flat one and the one with a shimmer running across, and use the flat one when many blocks show at once, because each animated one is a layer the browser repaints nonstop. Make it take EXACTLY the same space as the final content, otherwise the jump when it arrives counts as CLS. Turn the shimmer off under prefers-reduced-motion, and mark it aria-hidden with aria-busy on the container.
- Tipografía cinética GSAP SplitText Animate text by letters or words (appear, slide, distort). High-impact headlines and brand intros. Seen on: gsap.com, its own home page loads SplitText, the plugin that splits text into letters. Ask your agent for it: Animate this heading letter by letter on entry. Split the text into spans and stagger their movement. Important so nothing breaks: the heading must still be ONE sentence for anyone listening, so put the full text in an aria-label on the container and aria-hidden on the pieces. With no JavaScript, the heading shows whole and static.
- Noise / grain SVG · CSS Subtle grain texture over backgrounds and gradients. Gives a warm, analog finish, less flat. Seen on: linear.app, a grain layer over the color to take the plastic out. Ask your agent for it: Add a subtle grain layer over this background to take the plastic out of it. Generate the texture with an inline SVG feTurbulence as a data URI, not an image you have to download. Put it on a pseudo-element with pointer-events: none, very low opacity (0.03 to 0.06) and under the content: if the text loses sharpness, you overdid it.
- Preloader JS An initial loading screen or bar while the site gets ready. Cover heavy resource loading, in moderation (never endless). Ask your agent for it: Build an initial loading screen that leaves when the page is ready. Give it a hard time cap: if it has not finished in two seconds, it goes anyway. A preloader with no limit turns any network hiccup into a permanently blank page. Keep the content in the HTML behind it, not inside the preloader, so a crawler reads it even if the JavaScript never runs.
Performance & best practices
- Animar transform/opacity GPU Animate only transform and opacity, never top/left/width/height. It's the only thing the GPU composes cheaply, giving 60fps with no jank. Seen on: apple.com, moves with transform, never top or left, which forces a relayout. Ask your agent for it: Go through the animations in this file and move them to transform and opacity. Anything animating top, left, width, height or margin forces a layout recalculation every frame; the same movement with translate touches no layout at all. Show me what you changed and why, and check in the performance panel that there are no per-frame recalculations left.
- will-change CSS Tells the browser which property will change so it prepares a layer. Use it sparingly: overusing it wastes memory. Seen on: framer.com, warns the browser what is about to move, 258 times on its home page. Ask your agent for it: Tell the browser what is about to move on this element with will-change, and remove it when the animation ends. The rule everyone skips: a permanent will-change on many elements creates one compositing layer each and eats video memory, especially on mobile. It is a hint for a specific moment, not an attribute you leave on.
- Core Web Vitals Métrica LCP (<2.5s), INP (<200ms) and CLS (<0.1): Google's experience metrics. If an effect worsens them, lower its intensity or move it to native CSS. Seen on: developer.mozilla.org, flags which image loads first so the LCP does not suffer. Ask your agent for it: Measure this page's Core Web Vitals and fix them in order of impact. For LCP, find which element is the largest in the first viewport and give it fetchpriority="high", removing any lazy loading from it. For CLS, reserve space with width and height or aspect-ratio on images, iframes and ads. Give me the numbers before and after, not just what you changed.
- prefers-reduced-motion CSS media System preference to reduce animations (accessibility). Respect it to avoid motion sickness; or, if your brand decides, offer a switch to turn motion off. Seen on: stripe.com, a hundred and ten rules honoring reduced motion. Ask your agent for it: Go through this project and respect prefers-reduced-motion in every animation, CSS and JavaScript alike. Reducing is NOT killing everything and leaving a dead interface: opacity changes and short fades stay; what goes is large movement, parallax, zoom and spin, which are the ones that actually make people sick.
- Progressive enhancement Método Content works and reads without JS; the effect goes on top. Robustness: if JS fails, the site is still usable. Seen on: apple.com, the page still says something with JavaScript off. Ask your agent for it: Check what is left of this page with JavaScript disabled and fix whatever falls over. Content and navigation must work without JavaScript; effects go on top. Specifically: no text that only exists after mounting, links are real <a> tags with a real href, and forms have action and method. Tell me what was broken before you touched it.
- Responsive / mobile-first CSS Design for mobile first and scale up. Most traffic is mobile; effects must run at 60fps there too. Seen on: framer.com, serves a different image depending on screen width. Ask your agent for it: Convert this CSS to mobile-first: base styles are the mobile ones and media queries only add upward with min-width. Test at 320px wide, which is where everything breaks, and make sure no block overflows horizontally. For images, srcset with several sizes plus sizes: serving the desktop one to a phone is the most common way to sink the LCP.
- Lazy loading loading="lazy" Load images and resources only as they approach the viewport. Speeds up initial load and saves data. Seen on: clerk.com, ninety-seven images that wait until needed. Ask your agent for it: Add loading="lazy" to the images and iframes on this page, but NOT to the ones in the first viewport: lazy-loading the hero image delays the LCP, which is the opposite of the point. Give them all width and height so they reserve their space, and decoding="async" on the non-critical ones.
- Debounce y throttle JS Limit how often a function runs on frequent events (scroll, resize, input). Avoids extra work and keeps things smooth. Ask your agent for it: Limit how often this runs on an event that fires constantly, and pick the right one: DEBOUNCE if only the end matters (a search-as-you-type, a resize); THROTTLE if you need to see the progress (scroll, pointer move). And if it is scroll or pointer, what you almost certainly want is requestAnimationFrame rather than a timer.
- requestAnimationFrame JS Sync JS animations with the browser's screen refresh. Smooth 60fps animations without clogging the main thread. Seen on: vercel.com, hooks its animations to the browser frame clock. Ask your agent for it: Move this animation to requestAnimationFrame, so it runs in sync with the screen refresh instead of a setInterval. Compute progress from the timestamp the callback receives, not a fixed increment, or it runs twice as fast on a 120Hz display. Cancel the loop on unmount with cancelAnimationFrame, otherwise it keeps running invisibly.
Modern CSS
- Container queries @container Styles based on the container's size, not the window's. Truly reusable components that adapt to their slot. Seen on: vercel.com, its CSS carries ninety-six container queries. Ask your agent for it: Make this component adapt to the width of ITS CONTAINER rather than the viewport, with container queries. Declare container-type: inline-size on the parent and use @container on the child. Careful with container-type: size, which demands an explicit height and collapses the element; for almost everything you want, inline-size is the right one.
- Fluid typography clamp() Font sizes that scale smoothly between a min and a max. Type that looks good from mobile to large screens with no jumps. Seen on: vercel.com, sizes are computed with clamp, no jumps. Ask your agent for it: Move this type scale to fluid sizes with clamp(), so it grows on its own between mobile and desktop with no breakpoint jumps. Use a relative unit in the scaling part (rem plus vw) rather than bare vw, because with pure vw the text stops responding to browser zoom, which fails accessibility. Check it still grows at 200% zoom.
- :has() CSS The 'parent selector': style an element based on what it contains. Styling logic previously impossible without JS. Seen on: vercel.com, used over a hundred times to react to a child. Ask your agent for it: Use :has() to style this element based on what it contains, instead of adding a class from JavaScript. Good for the parent of an invalid input, the card that has an image, the form with a focused field. Two warnings: :has() is not lazy and a very broad selector gets evaluated a lot, so scope it; and check the page stays usable without support.
- Cascade layers @layer Organize CSS priority into layers to avoid specificity wars. Keep CSS tidy in large projects. Seen on: stripe.com, orders its CSS in layers instead of fighting specificity. Ask your agent for it: Organize this CSS into layers with @layer to end the specificity war. Declare the layer order ONCE and up front (reset, base, components, utilities), because that order is what wins, not where each rule is written. And know that anything OUTSIDE every layer beats everything inside one, which is the opposite of what it looks like.
- color-mix() CSS Mix two colors directly in CSS. Generate shades, hovers and transparencies from a base color (used a lot on this site). Seen on: tailwindcss.com, over a thousand mixed colors in its stylesheet. Ask your agent for it: Generate these shades with color-mix() instead of hand-writing every hex: hover, border and disabled all come from the base color mixed with the background or with transparent. Work in oklch rather than srgb, because in srgb the mixes go through muddy greys. When you are done check the contrast: a derived shade can land under 4.5:1 without anyone noticing.
- Subgrid CSS grid Let a child element inherit the parent's grid lines. Align cards and lists perfectly. Seen on: vercel.com, child grids lining up with their parent. Ask your agent for it: Line these cards up with subgrid, so their titles, bodies and footers sit on the same lines even with different content. The card declares grid-template-rows: subgrid and spans several parent rows with grid-row: span N. This is how you fix it without fixed heights or JavaScript measuring, which is how it used to be done and always broke.
- Logical properties CSS Properties by logical axis (inline/block) instead of left/right/top/bottom. Automatic support for right-to-left (RTL) languages. Seen on: stripe.com, logical margins, so the site can flip direction. Ask your agent for it: Swap the physical properties in this CSS for logical ones: margin-inline instead of margin-left and margin-right, padding-block instead of top and bottom, inset-inline-start instead of left. With that the layout flips itself for Arabic or Hebrew with no separate rules. Start with spacing and borders, which is where ninety percent of it lives.