Welcome to the second part of the UX series for Frontend Developers. If you haven't read the author's previous article yet, you can easily catch up by reading "UX through a Frontend Developer’s eyes - practical UX protips for FEDs".
Your interface may look right in a static mockup - but users don’t experience static pages. They see content appear, load, and shift. Perceived performance often matters more than raw performance metrics. A page that loads in 800ms but shows a blank screen feels slower than one that takes 1.2 seconds but shows skeleton placeholders from the first frame.
Some of the tips below might sound counterintuitive at first - I’ve even seen memes mocking the idea of a “minimum loader duration.” But trust the process. These patterns exist because they’ve been validated across countless products, and your users will thank you for implementing them.
Skeletons instead of empty screens
Nobody likes waiting. But when a user has to wait, the least you can do is let them know the page is still alive and working.
A blank screen immediately triggers the “something went wrong” alarm in a user’s brain. They start wondering: Did the page crash? Is my connection down? Should I refresh? That moment of uncertainty is a UX failure - even if the data arrives half a second later.
The current standard for handling this is to use skeleton screens - lightweight placeholder layouts that mirror the general structure of the content being loaded, paired with a subtle shimmer animation that signals “content is on its way.”
Skeleton screens beat blank pages and isolated spinners because they:
- Preserve the page structure - the user immediately understands the layout they’re about to interact with, even before the real data arrives. There’s no jarring rearrangement when content finally loads.
- Preview the content shape - rectangle blocks hint at where images, text blocks, and buttons will appear. This primes the user’s brain, making the transition to real content feel faster.
- Reduce uncertainty and perceived wait time - research consistently shows that users perceive skeleton-loaded pages as faster than spinner-loaded ones, even when the actual load time is identical.
They communicate progress from the very first frame - and that’s what makes the difference.

Minimum loader duration
A loader that flashes for 30 milliseconds and vanishes looks like a rendering glitch. Users see a flicker - something appeared and disappeared before their brain could even register what it was. Instead of feeling fast, it feels broken.
A short minimum display time - typically 300 to 600 milliseconds - gives the user’s brain enough time to register the loading state and understand the transition. The skeleton appeared, and the content replaced it. That’s a story the brain can follow. Without that minimum duration, it’s just visual noise.
This is not about artificially slowing down your system. If the data arrives in 20ms, you’re only adding a couple of hundred milliseconds - imperceptible in terms of actual productivity, but a massive improvement in how polished the interface feels. The goal is to avoid the “did that just glitch?” moment and replace it with a smooth, intentional transition.
A practical implementation is straightforward: track both the fetch start time and the response time, then delay the content reveal by whichever is larger - the actual load time or your minimum duration.

Loading animations should not be uniform
A spinner that rotates at a constant speed with a fixed arc length feels mechanical, lifeless, and - counterintuitively - slower. Our brains are wired to notice variation and interpret it as activity. Constant, uniform motion reads as “stuck” or “looping endlessly.”
Better loading animations use:
- Non-linear easing - the animation accelerates and decelerates rather than moving at a constant pace. This mimics real-world physics, where objects don’t start and stop instantly.
- Varying arc or segment lengths - the spinning element grows and shrinks as it rotates, creating a sense of “effort” and progress. The changing shape keeps the user’s attention and suggests that something is actively happening.
- Speed variation - brief moments of faster and slower movement make the animation feel organic rather than robotic. Even subtle changes in velocity create a sense of life.
Google’s Material Design indeterminate loader is the gold standard here. Watch how the arc stretches, contracts, and shifts speed as it circles - it feels like it’s actually doing something, not just spinning in place.

Progress bars when duration is unknown
When you don’t know how long something will take - data fetching, file uploads, background processing - a standard linear progress bar creates a problem. If it moves at a constant speed and the task takes longer than expected, the bar either stops dead (making the user think it’s frozen) or you’re forced to guess the total duration (making the bar inaccurate).
The solution is an asymptotic progress pattern, famously used by YouTube’s top-of-page loader:
- Fast initial progress to ~40–60% - the bar jumps forward quickly, immediately reassuring the user that something is happening. This leverages the psychological principle that seeing early, rapid progress makes the entire wait feel shorter.
- Gradual deceleration through the middle range - progress slows as it approaches higher percentages, stretching the perceived timeline without ever stopping.
- Approaching but never quite reaching 100% - the bar creeps asymptotically toward completion, so it’s always moving. When the actual task finishes, the bar snaps to 100% and completes.
Users see continuous, honest-feeling progress without false precision about timing. The bar never lies - it just optimistically front-loads the visual progress.

Preventing layout shift (CLS as a UX issue)
Layout shifts - when elements on the page suddenly jump to a new position - are one of the most disorienting experiences a user can encounter. You’re reading a paragraph, and suddenly it leaps 200 pixels down because an image loaded above it. You’re about to click a button, and it moves because a banner appears. It’s jarring, it breaks trust, and it’s entirely preventable.
This is so impactful that Google made it one of the three Core Web Vitals: Cumulative Layout Shift (CLS). But beyond SEO implications, it’s fundamentally a UX issue.
Strategies to prevent layout shifts:
- Reserve space for dynamic content - if you know an image is coming, set explicit
widthandheightattributes (or use the CSSaspect-ratioproperty) so the browser can allocate space before the image downloads. - Use min-height on containers - for sections that load asynchronously (ads, embeds, lazy-loaded components), set a
min-height that approximates the expected content height. Overestimating slightly is better than not reserving any space at all. - Show placeholders and skeletons - skeleton screens aren’t just for perceived performance. They also lock in the layout dimensions, preventing content below from shifting when real content loads.
- Choose the right hiding strategy -
visibility: hiddenkeeps an element in the document flow (occupying its space) while making it invisible, preventing layout shift.display: noneremoves it from the flow entirely, causing everything around it to collapse and then re-expand when it appears. Usevisibility: hiddenwhen you want to reserve space for content that will appear shortly. Note that both approaches also hide content from screen readers - see the Accessibility section for how to hide elements visually while keeping them accessible. - Be careful with dynamically injected content - inserting elements above the user’s current scroll position (cookie banners, notification bars, lazy-loaded items in a feed) almost always causes a visible shift. Insert below the viewport, or push content down with a smooth animation rather than an instant jump.
Visual stability builds trust. When the layout stays put, users feel in control - they can read, scan, and click without second-guessing where things will end up.

UX of data operations and user actions
The page loaded smoothly - now the user starts interacting. What happens when they click a button, submit a form, or delete an item? The way your interface responds to user actions is where “functional” and “delightful” diverge the most. A half-second delay between clicking “Like” and seeing the heart fill in can make your entire app feel sluggish, even if everything else is fast.
Optimistic update
Optimistic updates flip the traditional request flow on its head. Instead of the classic sequence - user clicks, UI shows a spinner, backend responds, UI updates - you update the UI immediately and assume the server will confirm. The request still happens in the background, but the user doesn’t have to wait for it.
This pattern works beautifully for:
- Likes and reactions - the heart fills in the moment you tap it. Waiting for a round trip to the server would make the interaction feel unresponsive and broken.
- Toggles and switches - enabling dark mode, toggling a notification setting, or switching a filter should feel instant. The UI state change is the feedback.
- Inline renames - renaming a file, a project, or a label should reflect immediately in the UI. The user sees their new name right away.
- Drag-and-drop reordering - when a user drags an item to a new position, it should remain there immediately. Reverting to the original order while waiting for a server response would feel broken.
- List removals - removing an item from a list should cause it to disappear (ideally with a smooth animation) right when the user clicks remove.
The benefits are significant:
- Instant feedback - the UI responds in a frame. No spinners, no loading states, no perceptible delay.
- Smoother interaction flow - the user can perform multiple actions in rapid succession without waiting for each one to resolve.
- Dramatically improved perceived speed - the app feels fast even over slow connections because the UI never blocks on network requests.
A simplified optimistic toggle:
But optimistic updates come with engineering requirements:
- Rollback support - if the server rejects the change (validation error, permission issue, or conflict), you need to gracefully revert the UI to its previous state. This means keeping a reference to the pre-update state.
- Error handling with user communication - when a rollback occurs, show a clear, non-disruptive message explaining what happened. A toast saying “Couldn’t save - reverted” is usually enough.
- Retry strategy - for transient failures (network hiccup, server timeout), you may want to retry automatically before reverting.
- Sync indicators for long operations - for actions where the server-side effect matters (sending a message, publishing a post), consider a subtle “syncing” indicator that confirms when the backend has caught up.
One important boundary: avoid optimistic updates for high-risk or irreversible operations. Deleting an account, transferring money, or sending an email should wait for server confirmation before reflecting in the UI.

Delete + Undo instead of confirm modals
The classic deletion pattern goes like this: user clicks delete, a modal pops up asking “Are you sure?”, the user clicks “Yes” (or more often just mashes Enter because the modal is expected and annoying), and the item gets deleted.
The problems with this flow:
- It breaks the user’s flow - a modal is a full-stop interruption. The user has to context-switch from “I’m managing my list” to “I’m reading and responding to a dialog box.” This cognitive interruption adds up fast when you’re doing bulk operations.
- It adds friction to every single deletion - even when the user is 100% certain they want to delete something, they’re forced through an extra step. Over time, this friction erodes trust in the interface’s efficiency.
- Users auto-click through it - after seeing the same confirmation modal 20 times, the “Yes, delete” button becomes muscle memory. The modal stops being a safety net and becomes an empty ritual. When a user actually does need the safety net, they’ll click through it anyway.
The better pattern - used by Gmail, Slack, Notion, and most modern productivity tools:
- Remove the item from the UI immediately - the delete happens visually right when the user clicks. No dialog, no interruption.
- Show a toast notification with an Undo option - a non-blocking toast slides in: “Item deleted - Undo.” The user can continue working or click Undo within a few seconds.
- Delay the actual server request (or use soft delete) - the backend deletion happens after a short delay (5–10 seconds), giving the Undo window time to work. Alternatively, use soft delete on the server side (mark as deleted, hard-delete later) - though keep in mind that soft delete adds backend complexity: queries need to filter out deleted records, indexes grow larger, and you’ll need a cleanup strategy that respects data retention policies like GDPR.
The advantages are clear:
- No blocking dialog - the user’s flow is uninterrupted. They can keep working immediately after clicking delete.
- Faster interaction - one click instead of two (or three, if the modal includes a “Are you really sure?” checkbox).
- Real, usable recovery - unlike a confirmation modal (which users click through blindly), an Undo toast is actually useful. It catches genuine mistakes because it appears after the action, when the user can see the consequence and decide if it was correct.
This pattern connects naturally with the toast-based feedback discussed in the next section.

UX through a Frontend Developer’s eyes
Download full guideMicrointeractions and attention guidance
Actions need responses - and the quality of those responses is what separates a functional interface from a polished one. Microinteractions - small, purposeful animations and feedback loops - are how your interface communicates with the user at a subliminal level. They say “this worked,” “look here,” or “this is changing” without requiring the user to read a single word.
Subtle motion instead of UI jumps
When something changes on screen - a panel opens, a list item appears, a section expands - the user’s brain needs a moment to understand what happened. If the change is instant (the element is suddenly there), the brain has to work to figure out the new state. But if the change is animated, even briefly, the motion itself tells the story.
Use small transitions instead of abrupt changes:
- Fade - new elements appearing with a quick opacity transition (150–250ms) feel intentional rather than jarring. This works well for content that loads asynchronously, tooltip appearances, and notification popups.
- Scale - a subtle scale-up from 95% to 100% when an element appears gives it a sense of “arriving.” Combined with a fade, this is the bread-and-butter entrance animation used by virtually every polished UI.
- Slide - elements that enter from a logical direction (a sidebar sliding in from the left, a dropdown sliding down from its trigger) help the user understand where the element “came from” and where it will “go back to” when dismissed.
- Easing curves - never use linear timing for UI transitions.
ease-outfor entrances (fast start, gentle landing),ease-infor exits (gentle start, fast departure), andease-in-outfor state changes. Linear motion feels robotic; eased motion feels natural.
Interfaces feel smoother and more predictable when changes are animated. The key is keeping animations short (150–300ms) - long enough to register, short enough not to feel sluggish.
One important caveat: always respect the prefers-reduced-motion media query. Some users have motion sensitivity or vestibular disorders, and animations that feel polished to most people can cause discomfort or nausea for them. Provide instant (or heavily reduced) alternatives:

Highlight errors, don’t just describe them
A validation error message at the top of a form that says “Please check the highlighted fields” is useless if no fields are actually highlighted. And even a message like “Email is invalid” is less effective when the user has to scan 15 form fields to find the email input.
Effective validation feedback should include:
- Field-level highlighting - the offending input should have a distinct visual treatment: a red border, a tinted background, or both. The user should be able to identify the problem field without reading a single word, purely from the visual change.
- Animated border or glow - a subtle pulse or glow animation on the error field draws the eye more effectively than a static color change. CSS
box-shadowwith a red tint and a brief animation (0.3s) works well. This is especially important when multiple fields have errors - the animation helps the user spot them while scanning. - Inline error indicators - place the error message directly below the field it refers to, not in a banner at the top of the page. Inline placement eliminates the need for the user to map “error in email field” to the actual email input.
A simple error glow in CSS:
The principle: message + visual target always beats message alone. The user should be able to fix the error without any mental mapping or scrolling.

Smooth scroll as guidance
When a user submits a form, and there’s a validation error on a field that’s currently off-screen, what should happen? If the answer is “a red banner appears at the top that says ‘Fix errors below’” - the user now has to manually scroll down, find the field, and fix it. That’s friction you’re imposing on someone who already made a mistake.
Smooth scrolling is useful for:
- Jumping to validation errors - after form submission, automatically scroll to the first error field and focus it. The user is taken directly to the problem without having to hunt for it. Combine this with the field highlighting described above for maximum effect.
- Navigating long forms or pages - anchor links, table-of-contents navigation, and “Back to top” buttons should all use smooth scrolling. The animation gives the user spatial context - they can see the page moving and understand where they’ve landed relative to where they were.
- Revealing results - after a search or filter action, scroll to the results section so the user doesn’t have to wonder where the output appeared.
A typical implementation after form validation:
Use smooth scrolling with care - don’t hijack the user’s scroll behavior or override native scrolling mechanics. Reserve it for intentional, app-initiated navigation (like error focus or anchor jumps), not as a replacement for the user’s own scrolling.

Toasts as lightweight feedback
After a user performs an action, they need confirmation that it worked. But not every action deserves a full modal or page reload. That’s where toasts come in - small, non-blocking notifications that slide into view, deliver their message, and disappear after a few seconds.
Toasts are perfect for:
- Saves and updates - “Changes saved” or “Profile updated” as a brief confirmation that disappears on its own. The user doesn’t have to click “OK” or close anything.
- Clipboard copies - when the user clicks a “Copy” button, a toast saying “Copied to clipboard” confirms the action worked. Without it, the user has to paste somewhere to verify - unnecessary friction.
- Deletions with undo - as discussed earlier, “Item deleted - Undo” gives the user a recovery window without interrupting their flow.
- Background completions - “Export ready” or “Upload complete” for operations that happened asynchronously while the user was doing something else.

Good toast design follows a few rules: they appear in a consistent position (typically bottom-right or top-right, mirrored in RTL layouts), they stack if multiple appear at once, and they include a close button for users who want to dismiss them immediately.
For informational toasts (“Changes saved”), auto-dismiss after 3–5 seconds is fine. For actionable toasts with an Undo button, use a longer window - 5–10 seconds - to give the user enough time to notice, read, and act. They confirm actions without blocking interaction - the user never has to stop what they’re doing to acknowledge a toast. For screen reader accessibility, toasts need aria-live regions - see the Accessibility section for details.
Fuzzy search instead of exact match
When a user types “drk md” into a settings search, they clearly mean “Dark mode.” When they type “notif,” they want “Notification preferences.” But a traditional exact-substring search returns nothing for “drk md” - it only matches if the characters appear consecutively in the original string.f
This is a common friction point in search inputs, command palettes, settings panels, and any filterable list. Users don’t think in exact substrings - they think in approximations, abbreviations, and partial recall.
Fuzzy search solves this by matching characters in order, but not necessarily adjacent:
- “drk md” matches “Dark mode” - all characters appear in sequence, just with gaps.
- “notif” matches both “Notification preferences” and “Email notifications” - ranked by how tight the match is.
- “2fa” matches “Two-factor authentication” - through keyword aliases, even when the literal characters don’t appear in the name.
Implementation tips:
- Highlight matched characters - show the user why a result matched by highlighting the individual characters that correspond to their query. This builds trust in the search and helps the user refine their input.
- Rank by match quality - a match at the start of the string (“Not…” for “not”) should rank higher than a match scattered across the middle. Tighter character clusters score better than spread-out matches.
- Include keyword aliases - map common synonyms and abbreviations to items: “2fa” → “Two-factor authentication,” “theme” → “Dark mode.” This catches cases where the user’s mental model uses different words from the label.
- Use established libraries - don’t roll your own fuzzy matcher for production. Libraries like Fuse.js or uFuzzy handle scoring, ranking, and edge cases out of the box.

Avoid modal overuse
Modals are the nuclear option of UI communication. They halt everything - the user can’t interact with anything behind the overlay, they must read and respond before continuing. Sometimes that’s exactly right (confirming a destructive action on a financial transaction, acknowledging a critical error). But too often, modals are used as the default pattern for every interaction.
Too many modals:
- Break context - every modal is a context switch. The user was focused on a task, but now they’re dealing with a dialog. When dismissed, they need to reorient and remember what they were doing.
- Increase cognitive load - “Are you sure?” modals force a decision. Confirmation dialogs inside other dialogs (modal inception) create genuine confusion. Each modal is a mental interruption.
- Slow down workflows - in admin panels and content management tools, users often perform repetitive bulk actions. If each action triggers a modal, the workflow grinds to a halt.
Before reaching for a modal, consider the alternatives:
- Inline expansion - instead of a modal for editing an item, expand the item in-place with inline editing fields. The user stays in context and can see the surrounding items.
- Drawers and slide-over panels - a panel that slides in from the right provides a focused editing space without completely blocking the underlying page. The user can still see the list they were browsing.
- Progressive reveal - instead of a modal asking for details upfront, reveal additional fields or options gradually as the user interacts with the interface.
- Undo toasts - as covered earlier, a “deleted - Undo” toast is almost always better than a “Are you sure?” modal for deletion flows.

A lot of material has been covered in this second installment. To complete the guide, read "Mobile-first interaction patterns".



