The active nav link automatically follows the user's scroll position. Scroll to "Carta" and the red pill jumps to "Carta." A small detail that makes the site feel alive.
Scroll inside the frame — watch the red pill move from Inicio → Nosotros → Carta → Contacto as each section comes into view.
Scroll down — the active pill in the nav will follow you.
Notice how the red pill jumps to "Nosotros" once enough of this section is on screen.
The threshold sits around 50% of the section being visible — feels deliberate but not laggy.
Scroll back up and the pill returns. IntersectionObserver handles both directions.
An IntersectionObserver watches every section. When a section's intersection ratio passes a threshold (typically 0.5 — 50% visible), its data-section ID is captured. The corresponding nav link gets .is-active; all other links lose it. The CSS handles the visual swap.
<nav>
<a href="#inicio" data-spy="inicio" class="is-active">Inicio</a>
<a href="#nosotros" data-spy="nosotros">Nosotros</a>
<a href="#carta" data-spy="carta">Carta</a>
<a href="#contacto" data-spy="contacto">Contacto</a>
</nav>
<section id="inicio" data-section="inicio">...</section>
<section id="nosotros" data-section="nosotros">...</section>
<section id="carta" data-section="carta">...</section>
<section id="contacto" data-section="contacto">...</section>
const links = document.querySelectorAll('[data-spy]');
const byId = id => document.querySelector(`[data-spy="${id}"]`);
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
links.forEach(l => l.classList.remove('is-active'));
byId(entry.target.dataset.section)?.classList.add('is-active');
}
});
}, { threshold: 0.5, rootMargin: '-72px 0px 0px 0px' });
// rootMargin pulls the trigger zone down by header height (72px),
// so a section becomes "active" when its visible portion (below the header) crosses 50%.
document.querySelectorAll('[data-section]').forEach(s => observer.observe(s));
rootMargin compensates for sticky header. If your header is 72px tall and you don't offset for it, sections become "active" too early (before the user can actually see them under the header). Use rootMargin: '-72px 0px 0px 0px' to push the observation zone below the header.
Threshold tuning — 0.5 (50% visible) is the sweet spot. Lower (0.2) makes the nav feel jumpy; higher (0.8) makes it lag behind. Test by scrolling slowly with your eyes on the nav.
Short final section — if the last section is shorter than the threshold can register, it never becomes "active." Either shorten the threshold for it specifically, or accept that "Contacto" stays active when you reach the footer too.
Click-to-scroll keeps working — the nav links are real anchor links. The IntersectionObserver fires after the smooth scroll lands. Don't override the click — let the browser do its thing.
Source: Chevy 55 main navigation behavior (user-uploaded HTML, 2026-05-18).