# Fixing Anchor Link Scroll Issues in Lovable Single-Page Apps

- Tool: Lovable
- Difficulty: Intermediate
- Fix time: ~5 min
- Compatibility: All Lovable versions (React Router v6 with BrowserRouter)
- Last updated: March 2026

## TL;DR

Hash links like href="#features" do not scroll to the target section in Lovable apps because React Router intercepts the navigation instead of letting the browser handle the hash. Fix this by using scrollIntoView in an onClick handler: document.getElementById('features')?.scrollIntoView({ behavior: 'smooth' }). For hash links that need to work across different pages, install the react-router-hash-link package and replace Link with HashLink. Add scroll-mt-16 to target elements to offset fixed headers.

## Why anchor links do not scroll in Lovable single-page apps

In a traditional multi-page website, clicking a link like <a href="#features"> makes the browser scroll to the element with id="features". This is native browser behavior that works without any JavaScript. However, Lovable apps use React Router with BrowserRouter, which intercepts all navigation events.

When React Router intercepts a hash link click, it updates the URL hash but does not trigger the browser's native scroll-to-anchor behavior. The URL changes to include #features, but the page stays at its current scroll position. This is a well-known limitation of client-side routing in SPAs.

The problem is compounded when navigating to a hash link from a different page. If you click a link to /about#team from the home page, React Router navigates to /about but does not scroll to the #team section. The page loads at the top, and the user has to scroll down manually to find the team section. This is especially frustrating for landing pages that rely heavily on anchor navigation.

- React Router intercepts hash link clicks and does not trigger native browser scroll-to-anchor behavior
- The Link component from react-router-dom does not handle hash fragments for scrolling
- Target elements missing an id attribute that matches the hash in the URL
- Fixed headers or navigation bars covering the scroll target because scroll-margin-top is not set
- Smooth scrolling CSS (scroll-behavior: smooth) not applied to the html element

## Error messages you might see

**Clicking anchor link changes URL hash but page does not scroll**

This is the expected behavior in React Router SPAs. The URL updates but no scroll happens. You need to manually trigger scrollIntoView when the hash link is clicked.

**Hash link scrolls to the element but it is hidden behind the fixed header**

The browser scrolls the element to the very top of the viewport, but your fixed navigation bar covers it. Add scroll-margin-top (Tailwind: scroll-mt-16 or scroll-mt-20) to the target element to offset the header height.

## Before you start

- A Lovable project with sections you want to scroll to (e.g., a landing page with Features, Pricing, FAQ sections)
- Each target section has a unique id attribute (e.g., id="features")
- The project uses React Router (standard for Lovable projects)

## How to fix it

### 1. Use scrollIntoView for same-page anchor links

*scrollIntoView is the simplest approach for hash links within the same page*

Instead of using href="#features" on an anchor tag or Link component, add an onClick handler that finds the target element by ID and scrolls to it. Use scrollIntoView with behavior: 'smooth' for an animated scroll. This bypasses React Router entirely and uses the browser's native scroll API. This approach works for navigation menus, table of contents, and back-to-top links.

Before:

```
// Hash link — React Router intercepts it, no scrolling happens
<a href="#features">Features</a>
<a href="#pricing">Pricing</a>
<a href="#faq">FAQ</a>
```

After:

```
// onClick with scrollIntoView — smooth scroll to each section
<button
  onClick={() => document.getElementById("features")?.scrollIntoView({ behavior: "smooth" })}
  className="hover:underline"
>
  Features
</button>
<button
  onClick={() => document.getElementById("pricing")?.scrollIntoView({ behavior: "smooth" })}
  className="hover:underline"
>
  Pricing
</button>
<button
  onClick={() => document.getElementById("faq")?.scrollIntoView({ behavior: "smooth" })}
  className="hover:underline"
>
  FAQ
</button>
```

**Expected result:** Clicking each button smoothly scrolls to the corresponding section. The URL does not change, avoiding React Router interference.

### 2. Add scroll-margin-top to offset fixed headers

*Without a scroll margin, the target element scrolls behind your fixed navigation bar*

If your Lovable app has a fixed header (position: fixed or sticky), the scroll target will be hidden behind it. Add Tailwind's scroll-mt class to each target section to offset the header height. Measure your header height and use the matching Tailwind class: scroll-mt-16 for a 4rem header, scroll-mt-20 for a 5rem header. Add the id attribute to the same element.

Before:

```
<!-- Section scrolls behind the fixed 64px header -->
<section id="features">
  <h2>Features</h2>
  <p>Our amazing features...</p>
</section>
```

After:

```
<!-- scroll-mt-16 adds 64px offset so the section is visible below the header -->
<section id="features" className="scroll-mt-16">
  <h2 className="text-3xl font-bold">Features</h2>
  <p>Our amazing features...</p>
</section>
```

**Expected result:** Scrolling to #features positions the section heading just below the fixed header, fully visible to the user.

### 3. Create a reusable ScrollLink component

*A reusable component avoids repeating the scrollIntoView logic across your entire app*

Create a component that accepts a target section ID and renders a styled clickable element with the scroll behavior built in. This component can be used in your navigation, footer, sidebar, or anywhere you need anchor links. It handles the smooth scroll and optional URL hash update.

Before:

```
// Repeating scrollIntoView logic everywhere
<button onClick={() => document.getElementById("features")?.scrollIntoView({ behavior: "smooth" })}>
  Features
</button>
// Copy-pasted for every anchor link...
```

After:

```
// Using the reusable ScrollLink component
import ScrollLink from "@/components/ScrollLink";

<ScrollLink targetId="features">Features</ScrollLink>
<ScrollLink targetId="pricing">Pricing</ScrollLink>
<ScrollLink targetId="faq">FAQ</ScrollLink>
```

**Expected result:** All anchor links in the app use the same consistent scroll behavior. Adding new scroll links requires just one line of code.

### 4. Handle cross-page hash links with react-router-hash-link

*When navigating from /about#team, the page must first load /about and then scroll to #team*

For hash links that navigate to a section on a different page, the built-in scrollIntoView approach does not work because the target element does not exist yet on the current page. Install react-router-hash-link by asking Lovable: 'Add the react-router-hash-link npm package to the project.' Then replace Link components with HashLink for cross-page anchor navigation. If integrating this library across your generated navigation requires changes in multiple components, RapidDev's engineers have handled this pattern across 600+ Lovable projects.

Before:

```
// Link component — navigates to /about but ignores #team
import { Link } from "react-router-dom";
<Link to="/about#team">Meet the Team</Link>
// Page loads at /about but stays at the top, does not scroll to #team
```

After:

```
// HashLink — navigates to /about and scrolls to #team
import { HashLink } from "react-router-hash-link";
<HashLink smooth to="/about#team">Meet the Team</HashLink>
// Page loads at /about and smoothly scrolls to the #team section
```

**Expected result:** Clicking the link navigates to /about and automatically scrolls to the element with id='team' after the page renders.

## Complete code example

File: `src/components/ScrollLink.tsx`

```typescript
import { cn } from "@/lib/utils";

interface ScrollLinkProps {
  targetId: string;
  children: React.ReactNode;
  className?: string;
  offset?: number; // Extra offset in pixels for fixed headers
}

// Reusable scroll-to-section link component
// Bypasses React Router for same-page anchor navigation
const ScrollLink = ({ targetId, children, className, offset = 0 }: ScrollLinkProps) => {
  const handleClick = (e: React.MouseEvent) => {
    e.preventDefault();
    const element = document.getElementById(targetId);
    if (!element) return;

    // Calculate position accounting for fixed header offset
    const elementPosition = element.getBoundingClientRect().top + window.scrollY;
    const offsetPosition = elementPosition - offset;

    window.scrollTo({
      top: offsetPosition,
      behavior: "smooth",
    });

    // Update URL hash without triggering React Router
    window.history.pushState(null, "", `#${targetId}`);
  };

  return (
    <button
      onClick={handleClick}
      className={cn(
        "text-foreground hover:text-primary transition-colors cursor-pointer",
        className
      )}
    >
      {children}
    </button>
  );
};

// Example: Landing page navigation using ScrollLink
const LandingNav = () => {
  return (
    <nav className="fixed top-0 w-full bg-background/80 backdrop-blur z-50 border-b">
      <div className="container flex items-center justify-between h-16">
        <span className="text-xl font-bold">MyApp</span>
        <div className="flex gap-6">
          <ScrollLink targetId="features" offset={64}>Features</ScrollLink>
          <ScrollLink targetId="pricing" offset={64}>Pricing</ScrollLink>
          <ScrollLink targetId="faq" offset={64}>FAQ</ScrollLink>
        </div>
      </div>
    </nav>
  );
};

export default ScrollLink;
```

## Best practices

- Use scrollIntoView with behavior: 'smooth' for same-page anchor links — it is simpler and more reliable than React Router hash handling
- Add scroll-mt-16 (or appropriate size) to target sections when using a fixed header to prevent the section from being hidden
- Give every scroll target a unique id attribute that matches the hash in the link (id="features" for #features)
- For cross-page hash links, use react-router-hash-link's HashLink component instead of React Router's Link
- Create a reusable ScrollLink component to avoid repeating scrollIntoView logic across your app
- Use window.history.pushState to update the URL hash without triggering React Router navigation
- Test scroll behavior on mobile devices — touch scrolling and fixed headers behave differently than on desktop

## Frequently asked questions

### Why don't anchor links scroll in my Lovable app?

React Router intercepts navigation events, including hash links, and does not trigger the browser's native scroll-to-anchor behavior. Use scrollIntoView in an onClick handler or the react-router-hash-link package instead of standard hash links.

### How do I add smooth scrolling to anchor links in React?

Call document.getElementById('sectionId')?.scrollIntoView({ behavior: 'smooth' }) in an onClick handler. This provides a smooth animated scroll to the target element without any additional libraries.

### Why does my section scroll behind the fixed header?

The browser scrolls the element to the top of the viewport, but your fixed header covers that area. Add Tailwind's scroll-mt-16 class (or appropriate size matching your header height) to the target section element to offset the scroll position.

### How do I scroll to a section on a different page?

Install the react-router-hash-link package and use its HashLink component with the smooth prop: <HashLink smooth to='/about#team'>Team</HashLink>. This first navigates to /about, then scrolls to the #team section after the page renders.

### Can I update the URL hash when scrolling without triggering React Router?

Yes. Use window.history.pushState(null, '', '#sectionId') after scrolling. This updates the URL hash for bookmarking and sharing without triggering React Router's navigation system.

### Does scroll-behavior: smooth in CSS work with React Router?

Adding scroll-behavior: smooth to the html element enables smooth scrolling for any programmatic scroll, but it does not fix the core issue of React Router intercepting hash links. You still need scrollIntoView or HashLink to trigger the scroll.

### What if I can't fix anchor scrolling myself?

If your landing page has complex scroll interactions spanning multiple components and routes, RapidDev's engineers can implement reliable anchor navigation. They have built scroll-based navigation patterns across 600+ Lovable projects.

---

Source: https://www.rapidevelopers.com/lovable-issues/fixing-anchor-link-scroll-issues-in-lovable-single-page-apps
© RapidDev — https://www.rapidevelopers.com/lovable-issues/fixing-anchor-link-scroll-issues-in-lovable-single-page-apps
