Scrolling Against the Grain: Inverting User Experience with React

engiNerdengiNerd
Mar 19, 2025·
6 min read

As software engineers, we're constantly looking for ways to create unique and memorable user experiences. Today, I'm excited to share a component from a project I've been working on that completely inverts the traditional scrolling paradigm rendering the view at the bottom of the HTML element rather than the top.

The Bottom Up Approach to UI

Most web experiences start at the top and scroll down, it's how we've trained users for decades. But what if we flipped this expectation on its head? That's exactly what this component does, effectively implementing a form of scroll jacking. Scroll jacking is a technique where developers override the browser's default scrolling behavior to create custom experiences.

My Inspiration: The Space Elevator

Before diving into the code, let's look at a brilliant example of this technique in action: Neal Agarwal's Space Elevator interactive experience. This web app takes users on a journey from Earth deep into space, using scroll jacking in a way that makes perfect sense for the content.

As you "scroll up" from Earth's surface, you ascend into the atmosphere, then space, passing satellites, the ISS, and eventually traveling beyond our solar system. The bottom-up approach here creates an intuitive mental model that aligns with the physical concept of ascending upward.

Advantages of Scroll Jacking

This inverted scrolling paradigm offers several interesting benefits:

  1. Novel user experience: It immediately signals to users that they're interacting with something different and memorable.

  2. Metaphorical alignment: For certain content types (like the space elevator example), it creates a more intuitive mental model.

  3. Contrasting information hierarchy: It can emphasize importance by inverting the traditional importance cascade.

  4. Engagement boost: The unexpected interaction pattern can increase curiosity and time-on-page.

Challenges to Consider When Inverting UI

Of course, this approach isn't without its challenges:

  1. Breaking user expectations: Users expect to scroll down, not up. This means you'll need to provide clear visual cues to prompt the desired behavior.

  2. Learning curve: There's a cognitive adjustment required when users encounter your interface.

  3. Accessibility concerns: Non-standard scrolling behaviors can create barriers for some users if not implemented thoughtfully.

  4. Mobile considerations: Touch scrolling patterns are deeply ingrained, so mobile implementation requires extra care.

The biggest hurdle is undoubtedly the need to prompt users to scroll up. This goes against decades of muscle memory and learned behavior. To overcome this, you can implement:

  • Animated indicators (like a bouncing scroll wheel pointing upward)

  • Clear call-to-action text ("Scroll up to continue")

  • Visual design that implies upward movement

  • An animation that demonstrates the intended interaction

Building the React Component

Now, let's get technical. The space elevator is coded in Vue. Here's how I implemented the same component in React:

"use client";

import React, { useEffect, useRef } from "react";
import styles from "./ScrollToTop.module.css";

const ScrollToTop: React.FC = () => {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const observer = new MutationObserver(() => {
      if (containerRef.current?.offsetHeight) {
        window.scrollTo(0, document.body.scrollHeight);
        observer.disconnect();
      }
    });

    observer.observe(document.body, {
      childList: true,
      subtree: true,
    });

    return () => observer.disconnect();
  }, []);

  return (
    <div>
      <div className={styles.contentWrapper} ref={containerRef}>
        <div className={styles.sectionHeader}>
          <h1>Scroll Up</h1>
          <p>^^^^^^</p>
        </div>
      </div>
    </div>
  );
};

export default ScrollToTop;

Breaking Down the Code

Let's dissect how this component works:

1. The "use client" Directive

The component starts with "use client" at the top, which is crucial when working with React Server Components. This directive explicitly tells React that this module and its dependencies should be executed on the client-side (browser) rather than the server. This is necessary because we need to access browser-specific APIs like window and DOM manipulation functions.

2. Component Setup and References

We import the necessary React hooks: useEffect for handling side effects and useRef for creating a reference to our DOM element. The containerRef will be attached to the main content wrapper div, allowing us to monitor when it has been fully rendered.

3. The MutationObserver Magic

The real magic happens in the useEffect hook, where we use a MutationObserver to watch for changes to the DOM:

useEffect(() => {
  const observer = new MutationObserver(() => {
    if (containerRef.current?.offsetHeight) {
      window.scrollTo(0, document.body.scrollHeight);
      observer.disconnect();
    }
  });

  observer.observe(document.body, {
    childList: true,
    subtree: true,
  });

  return () => observer.disconnect();
}, []);

This code:

  • Creates a MutationObserver that watches for changes to the DOM

  • When it detects that our referenced container has content with height (meaning it's been rendered), it scrolls the page to the bottom

  • It then disconnects the observer since its job is complete

  • The return function ensures proper cleanup by disconnecting the observer when the component unmounts

4. The Component Structure

The component renders a simple structure with visual cues to prompt users to scroll up:

return (
  <div>
    <div className={styles.contentWrapper} ref={containerRef}>
      <div className={styles.sectionHeader}>
        <h1>Scroll Up</h1>
        <p>^^^^^^</p>
      </div>
    </div>
  </div>
);

The key here is connecting our containerRef to the content wrapper div via the ref attribute, which allows our code in useEffect to check when this element has been fully rendered.

Making It Accessible

Accessibility shouldn't be an afterthought. Here's how to ensure your inverted scrolling component remains accessible:

  1. Provide keyboard navigation alternatives: Ensure users can navigate through your content using keyboard commands, not just scrolling.

  2. Use appropriate ARIA attributes: Add aria-label and other relevant attributes to explain the non-standard navigation.

  3. Respect user preferences: Check for prefers-reduced-motion media queries and provide alternatives for users who may experience motion sickness.

  4. Clear instructions: Make sure instructions for navigating your UI are clear and available in multiple formats (visual, text, etc.).

  5. Test with screen readers: Verify that screen reader users can understand and navigate your content coherently.

When to Use This Approach

This inverted scrolling pattern isn't appropriate for every project. Consider using it when:

  • The content naturally follows a bottom-up metaphor (like the Space Elevator example)

  • You're creating an artistic or experimental interface where novelty is expected

  • The user journey benefits from a reversed information hierarchy

  • You can provide clear navigational cues to guide users

Avoid this approach for content heavy websites, task oriented interfaces, or any context where efficient information retrieval is the primary goal.

Conclusion

Playing with scroll direction is more than just a novelty, it's about finding new ways to map interface interactions to mental models that make sense for specific content. When implemented thoughtfully with clear user guidance and attention to accessibility, inverting the scroll direction can create memorable, engaging experiences.

The React component shared demonstrates how to implement this technique efficiently, using modern React patterns like hooks, refs, and the MutationObserver API. It waits until content is fully rendered before triggering the scroll action and properly cleans up resources when finished.

11
Subscribe to my newsletter

Read articles from engiNerd directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

engiNerd
engiNerd