Sveltekit: Styling active links with $page.path
Asked Answered
B

6

11

I'm working on a sveltekit app with a sidenav containing links. I can't get the active class styling on my links to work properly.

The links are components in NavLink.svelte:

<script>
  import { page } from '$app/stores';
  import Icon from '$lib/Icon.svelte';
  export let title;
  export let href;
</script>

<a {href} class="link" class:active={$page.path == href}>
  <Icon {title} />
</a>

<style>
a.active {
  background-color: rgba(0,0,0,0.24);
</style>

These links are used in SideNav.svelte:

<script>
  import NavLink from '$lib/NavLink.svelte';
</script>

<nav class="container">
  <div id="links">
    <NavLink href="/link1" title="icon1" />
    <NavLink href="/link2" title="icon2" />
    <NavLink href="/link3" title="icon3" />
  </div>
</nav>

And finally, the sidenav is loaded in my __layout.svelte:

<SideNav />
<slot />
<Footer />

Now when I click one of my sidenav links, I am routed to the proper page but my NavLink is not styled with the .active class. If I inspect the link, however, devtools shows me this: <a class="link active:true"> and the other links have active:false.

So it looks like the function is working, but my active style is not applied (the background color). What am I missing?

I tried moving the Active class code to the SideNav component instead of the NavLink component and observed the same behavior. I could not figure it out, so I found a new method that works just fine.

In my NavLink.svelte:

<script>
  import {onMount} from "svelte";
  import Icon from '$lib/Icon.svelte';

  let currentPath;
  onMount(() => {
    currentPath = window.location.pathname;
  });
  export let title;
  export let href;
</script>

<a {href} class:active={currentPath == href}>
  <Icon {title} />
</a>

And the rest of the code is the same. Now my links get the proper styling. It's worth noting that they simply have <a class="active"> and not <a class="active:true">. Why wasn't it working with the other method?

Bystander answered 6/1, 2022 at 20:19 Comment(1)
I figured it out.... I think I just forgot to include quotes. D'oh. Working code (that also accounts for sub-pages) for the NavLink is: <a {href} class:active=""{$page.path.includes(href)}">Bystander
B
14

I figured it out.... I think I just forgot to include quotes. D'oh. Working code (that also accounts for sub-pages) for the NavLink is:

<a {href} class:active="{$page.path.includes(href)}">

Bystander answered 7/1, 2022 at 2:37 Comment(3)
I think the properties of $page might have changed since this was posted. What worked for me was $page.url.pathname rather than $page.pathStalemate
In sveltekit 1.0 and later it's $page.url.pathname.includes(href)Kaleykaleyard
Also two birds / one stone and comply with accessibility by assigning it to [ aria-current='page'] e.g. aria-current={$page.url.pathname === '/dashboard' ? 'page' : undefined} and using the css selector a[aria-current='page'] { ... style ... }Hemistich
H
12

To achieve this in the latest version of SvelteKit, after the introduction of some breaking changes, you can do:

<a {href} class:active={$page.url.pathname === href}>

$page.url returns something like:

URL { 
    href: "http://localhost:5173/accounts/login", 
    origin: "http://localhost:5173", 
    protocol: "HTTP:", 
    username: "", 
    password: "", 
    host: "localhost:5173", 
    hostname: "localhost", 
    port: "5173", 
    pathname: "/accounts/login", 
    search: "" 
}

You can then compare $page.url.pathname with the href of the anchor tag.

Homocyclic answered 4/11, 2022 at 11:6 Comment(0)
I
1

After trying all of these without any luck, I added an !important rule to the css property I was targeting.

The issue came from using a parent .navbar class to select child links, which I guess has a higher specificity (and renders later) than the non-nested .active class. Styling the link elements directly fixed the issue. e.g:

from

.navbar > a {
    color: white;
}
.active {
    color: blue;
}

to

a {
    color: white;
}
.active {
    color: blue;
}

I'm not sure how obvious this is to other people, but I'm like 99% sure I've used the exact same pattern (.class > element) + .active several other times without issue?

Incorporated answered 18/6, 2023 at 5:32 Comment(0)
C
0

Here is my solution to only match the first segment of the current URL:

<a
  class:active={$page.url.pathname.split("/")[1] ===item.href.split("/")[1]}
  href={item.href}>
  {item.label}
</a>
  a.active {
   background-color:red;
  }
Communal answered 21/1, 2023 at 12:38 Comment(0)
K
0

You might want to use $page.route.id which contains the route id only, even if you have params in the path.

Example, the path /catalogue/123 will gives:

  • $page.url.pathname === /catalogue/123 (different for each item, does not work everywhere)
  • $page.route.id === /catalogue/[id] (same for all items)

Then you can use in your links:

<a href="/catalogue/123" class:active={$page.route.id === '/catalogue/[id]'}>catalogue</a>

Inn the end this will work for static links (/catalogue) and dynamic links (/catalogue/456).

Kassala answered 9/6, 2023 at 0:36 Comment(0)
I
0

This is the approach that I use frequently using $page store.

First, I structure the navigation data in following format:

item = [{ link: '/hello/world/about', title: 'About }, ...]

Then, I compare current URL's path ($page.url.pathname) with the file path (item.link)

<a href={item.link} class:active={$page.url.pathname === item.link}>{item.title}</a>

Alternatively, we can also create a generic method for the same:

export function isActive(page: Page<Record<string, string>>, path: string) {
  const pathname = page.url.pathname;
  return pathname === path;
}

and use that as:

<a href={item.link} class:active={isActive($page, item.link)}>{item.title}</a>
Insulate answered 13/6, 2023 at 18:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.