A Dashing Navbar Solution
Published 1 year, 10 months pastOne of the many things Igalia does is maintain an official port of WebKit for embedded devices called WPE WebKit, and as you might expect, it has a web site. The design had gotten a little stale since its launch a few years ago, so we asked Denis Radenković at 38one to come up with a new design, which we launched yesterday. And I got to turn it into HTML and CSS! Which was mostly normal stuff, margins and font sizing and all that, but also had some bits that called for some creativity.
There was one aspect of the design that I honestly thought wasn’t going to be possible, which was the way the “current page” link in the site navbar connected up to the rest of the page’s design via a dashed line. You can see it on the site, or in this animation about how each navlink was designed to appear.
I thought about using bog-standard dashed element borders: one on a filler element or pseudo-element that spanned the mostly-empty left side of the navbar, and one on each navlink before the current one, and then… I don’t know. I didn’t get that far, because I realized the dashed borders would almost certainly stutter, visually. What I mean is, the dashes wouldn’t follow a regular on-off pattern, which would look fairly broken.
My next thought was to figure out how to size a filler element or pseudo-element so that it was the exact width needed to reach the middle of the active navlink. Maybe there’s a clever way to do that with just HTML and CSS, but I couldn’t think of a way to do it that wasn’t either structurally nauseating or dependent on me writing clever JavaScript. So I tossed that idea, though I’ll return to it at the end of the post.
In the end, it was the interim development styles that eventually got me there. See, while building out other parts of the design, I just threw a dashed border on the bottom of the navbar, which (as is the way of borders) spanned its entire bottom edge. At some point I glanced up at it and thought, If only I could figure out a mask that would clip off the part of the border I don’t need. And slowly, I realized that with a little coding sleight-of-hand, I could almost exactly do that.
First, I removed the bottom border from the navbar and replaced it with a dashed linear gradient background, like this:
nav.global {
background-image: linear-gradient(
90deg,
currentColor 25%,
transparent 25% 75%,
currentColor 75%
);
background-position: 0 100%;
background-size: 8px 1px;
background-repeat: repeat-x;
}
(Okay, I actually used the background
shorthand form of the above — linear-gradient(…) 0 100% / 8px 1px repeat-x
— but the result is the same.)
I thought about using repeating-linear-gradient
, which would have let me skip having to declare a background-repeat
, but would have required me to size the gradient’s color stops using length units instead of percentages. I liked how, with the above, I could set the color stops with percentages, and then experiment with the size of the image using background-size
. (7px 1px
? 9px 1px
? 8px 1.33px
? Tried ’em all, and then some.) But that’s mostly a personal preference, not based in any obvious performance or clarity win, so if you want to try this with a repeating gradient, go for it.
But wait. What was the point of recreating the border’s built-in dash effect with a gradient background? Well, as I said, it made it easier to experiment with different sizes. It also allowed me to create a dash pattern that would be very consistent across browsers, which border-style
dashes very much are not.
But primarily, I wanted the dashes to be in the background of the navbar because I could then add small solid-color gradients to the backgrounds of the navlinks that come after the active one.
nav.global li.currentPage ~ li {
background: linear-gradient(0deg, #FFF 2px, transparent 2px);
}
That selects all the following-sibling list items of the
currentPage
-classed list item, which here is the “Learn
& Discover” link.
<ul class="about off">
<li><a class="nav-link" href="…">Home</a></li>
<li class="currentPage"><a class="nav-link" href="…">Learn & Discover</a></li>
<li><a class="nav-link" href="…">Blog</a></li>
<li><a class="nav-link" href="…">Developers</a></li>
<li><a class="btn cta" href="…">Get Started</a></li>
</ul>
Here’s the result, with the gradient set to be visible with a pinkish fill instead of #FFF
so we can see it:
You can see how the pink hides the dashes. In the actual styles, because the #FFF
is the same as the design’s page background, those list items’ background gradients are placed over top of (and thus hide) the navbar’s background dash.
I should point out that the color stop on the white solid gradient could have been at 1px
rather than 2px
and still worked, but I decided to give myself a little bit of extra coverage, just for peace of mind. I should also point out that I didn’t
just fill the backgrounds of the list items with background-color: #FFF
because the navbar has a semitransparent white background fill and a blurring background-filter
, so the page content can be hazily visible through the navbar, as shown here.
The white line is slightly suboptimal in this situation, but it doesn’t really stand out and does add a tiny bit of visual accent to the navbar’s edge, so I was willing to go with it.
The next step was a little trickier: there needs to be a vertical dashed line “connecting” to the navbar’s dashed line at the horizontal center of the link, and also the navbar’s dashed line needs to be hidden, but only to the right of the vertical dashed line. I thought about doing two background gradients on the list item, one for the vertical dashed line and one for the white “mask”, but I realized that constraining the vertical dashed line to be half the height of the list item while also getting the repeating pattern correct was too much for my brain to figure out.
Instead, I positioned and sized a generated pseudo-element like this:
nav.global ul li.currentPage {
position: relative;
}
nav.global ul li.currentPage::before {
content: '';
position: absolute;
z-index: 1;
top: 50%;
bottom: 0;
left: 50%;
right: 0;
background:
linear-gradient(180deg,
currentColor 25%,
transparent 25% 75%,
currentColor 75%) 0 0 / 1px 8px repeat-y,
linear-gradient(0deg, #FFFF 2px, transparent 2px);
background-size: 1px 0.5em, auto;
}
That has the generated pseudo-element fill the bottom right quadrant of the active link’s list item, with a vertical dashed linear gradient running along its left edge and a solid white two-pixel gradient along its bottom edge, with the solid white below the vertical dash. Here it is, with the background pinkishly filled in to be visible behind the two gradients.
::before
with its background filled in a rosy pink instead of the usual transparency.
With that handled, the last step was to add the dash across the bottom of the current-page link and then mask the vertical line with a background color, like so:
nav.global ul li.currentPage a {
position: relative;
z-index: 2;
background: var(--dashH); /* converted the dashes to variables */
background-size: 0.5em 1px;
background-position: 50% 100%;
background-color: #FFF;
}
And that was it. Now, any of the navbar links can be flagged as the current page (with a class
of currentPage
on the enclosing list item) and will automatically link up with the dashes across the bottom of the navbar, with the remainder of that navbar dash hidden by the various solid white gradient background images.
So, it’s kind of a hack, and I wish there were a cleaner way to do this. And maybe there is! I pondered setting up a fine-grained grid and adding an SVG with a dashed path, or maybe a filler <span>
, to join the current link to the line. That also feels like a hack, but maybe less of one. Or maybe not!
What I believe I want are the capabilities promised by the Anchored Positioning proposal. I think I could have done something like:
nav.global {position: relative;}
nav.global .currentPage {anchor-name: --navLink;}
nav.global::before {
position: absolute;
left: 0;
bottom: 0;
right: var(--center);
--center: anchor(--navLink 50%);
top: anchor(--navLink bottom);
}
…and then used that to run the dashed line over from the left side of the page to underneath the midpoint of the current link, and then up to the bottom edge of that link. Which would have done away with the need for the li ~ li
overlaid-background hack, and nearly all the other hackery. I mean, I enjoy hackery as much as the next codemonaut, but I’m happier when the hacks are more elegant and minimal.
Comments (2)
Would be great if you included aria-current=”page” on the current page link anchor tag. #accessibility
Good point, Daryl. Added!