#Active Navigation Menu Item - Unable to resolve after 4 variations in attempt

1 messages · Page 1 of 1 (latest)

magic moon
#

I have tried these following "solutions". None of which work for me.

https://discord.com/channels/830184174198718474/1071208396168110120
https://discord.com/channels/830184174198718474/1110715780716298260
https://gist.github.com/leabs/11e9f20a5580982428824148f62eb2a0
https://www.cyishere.dev/blog/astro-active-nav-item

I have taken this back with no "solution" from above in place.

I've spent hours now trying to figure out how to simply do a dang active state of a menu item when no more than 5 minutes would have sufficed in any other tool. WHY must this be so damn hard...Is there a foolproof ASTRO APPROVED and official way to handle a simple active state?

Screenshot attached. I simply need to add the active class based on the page you're on, on the href that links to the same page.

If the simple things like this continue to be hard I guess we can revert everything back to static html but something tells me this is just a major gap in missing documentation that it continues to be asked and asked again with no real solution from Astro documented.

Gist

Astro - Set active class on Navbar component. GitHub Gist: instantly share code, notes, and snippets.

Chen Yang, aka CY

Highligh navlink, nav items with active states for current page in Astro.

dreamy sparrow
#

What are you using for clientside navigation? Astro is server only so it really won't have any concept of whats going on in the user's browser

#

If the site is fully server-rendered and there isn't any client routing going on, you won't actually need the <script> at all. I usually do something like this to mark the current page

<nav>
  <a class:list={{ active: Astro.url.pathname === '/'}} href="/">Home</a>
  <a class:list={{ active: Astro.url.pathname === '/portfolio'}} href="/portfolio">Portfolio</a>
  <a class:list={{ active: Astro.url.pathname === '/posts'}} href="/posts">Article</a>
  <a class:list={{ active: Astro.url.pathname === '/about'}} href="/about">About Me</a>
  <a class:list={{ active: Astro.url.pathname === '/contact'}} href="/contact">Contact Me</a>
</nav>
#

Here's a more complete example with a few tweak

  • I like to set the list of routes in frontmatter to avoid duplicating the logic for checking active
  • I'll use aria-current="page" to mark the current page and style based off that selector instead of a class. This makes sure accessibility tools know it's the current navigation page
---
const pages = [
  { text: 'Home', href: '/' },
  { text: 'Portfolio', href: '/portfolio' },
  { text: 'Article', href: '/posts' },
  { text: 'About Me', href: '/about' },
  { text: 'Contact Me', href: '/contact' }
]
---

<nav>
  {pages.map(({ text, href }) => (
    <a href={href} aria-current={Astro.url.pathname === href ? 'page' : undefined}>{text}</a>
  )}
</nav>
#

Come to think of it I'll always have a removeTrailingSlash(str) helper utility floating around too and use that to sanitize pathname, just in case the trailing slash is only sometimes used

magic moon
#

Other than HTML & Astro? I'm using Turbopack, SSR is enabled shooting this over with the Vercel/Astro integration and the turbo build is successful, the vercel deploy is successful.

magic moon
#

@dreamy sparrow I like using the map obviously simplifies thing but I don't notice how it applies an aria state, or the alternative additional class of active. I can easily change the css to target the aria state but that's not going to make a difference without the aria-current working properly. Any ideas based on this?

dreamy sparrow
#

I've never actually used Turbopack with Astro, I thought Vercel's bundler was only working with NextJS 👀

aria-current={Astro.url.pathname === href ? 'page' : undefined} this attribute in the Astro component will set aria-current="page" on the link for the current page, the attribute will be skipped completely if it doesn't match since undefined here means "don't include this attribute"

If there's any kind of client navigation in the browser, a <script> would still be needed. The script in your original gist would do the trick to use your class, or could be tweaked to use aria-current instead

<script>
  const nav = document.querySelector(".navbar");
  const navLinks = nav.querySelectorAll("a");
  const currentURL = window.location.href;
  navLinks.forEach((link) => {
    // null here should remove the attribute
    link.ariaCurrent = link.href === currentURL ? "page" : null
  });
</script>
#

Anything clientside will get tricky and is a bit outside of what Astro actually handles since it's server only

Its heavily dependent on how the actual client navigation is working, something like HTMX would have a very different solution for updating the links than something like one of the react SPA routers

magic moon
#

@dreamy sparrow when all else failed you came in with a solution so for that hats-off to you and major thank you and appreciation for taking the time. This is what my code ended up being after tony's help. I simply adjusted my css to include .nav-item[aria-current="active"] and along with tony's contribution above I now get active state properly and as expected.

dreamy sparrow
#

Very nice, glad to help! These kinds of situations with tracking navigation across routes can get tricky/confusing fast

magic moon
#

@dreamy sparrow first-hand experience I cosign haha.

And earlier I mentioned Turbopack. I've only ever used astro inside a Turbo/pnpm monorepo. Turbo has always been a fit with Astro for me and it's never given me any issues. Its a mainstay for sure.

This is my turbo.json if you ever look to give it a shot.

  "$schema": "https://turborepo.org/schema.json",
  "globalDependencies": [
    "packages/**",
    "packages/prodkt-themes/**",
    "apps/**/public/**",
    "apps/**/astro.config.ts",
    "apps/**/package.json",
    "apps/**/tsconfig.json",
    "**/.env",
    "public/**"
  ],
  "pipeline": {
    "dev": {
      "dependsOn": ["^dev"]
    },
    "test": {
      "dependsOn": ["^test"]
    },
    "coverage": {
      "dependsOn": ["^coverage"]
    },
    "playwright": {
      "dependsOn": ["^playwright"]
    },
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".vercel/output/**"],
      "inputs": ["src/**", "vercel.json"]
    },
    "lint": {},
    "format": {}
  }
}

and my base.json tsconfig

{
  "$schema": "https://json.schemastore.org/tsconfig",
  "extends": "astro/tsconfigs/base",
  "display": "Default",
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "preact",
    "types": ["@astrojs/image/client"],
    "baseUrl": "../../",
    "paths": {
      "@packages/*": ["packages/*"]
    }
  },
  "exclude": ["node_modules"]
}
dreamy sparrow
#

Ah ok that was my mistake! I was thinking of the turbopack bundler that Vercel has been working on for NextJS, https://turbo.build/pack

Too many tools named "turbo___" these days 😅 With page navigation my mind actually first went to Turbo (formerly turbolinks), the library for swapping out HTML fragments on the page for something similar to clientside routing