Learning Web 07: Responsive navbar in raw CSS and new website theme
Decided to take a different path for a moment in my learning and create a theme of my own again. I’ve been working on a website for someone and learning stuff with that and decided to apply some to my own while also picking up new skills for that person’s website. So I sat out to make a responsive navbar setup with raw CSS instead of using tailwind or bootstrap. Another thing I decided to do different was to use a single list for both layouts–the tutorials I ran into all used two versions of the same list in two different divs that it hides and shows while I’m just using the one.
I also changed my theme to use this new menu and so the rest of the site is a bit raw. It’s mostly the default hugo theme with my modifications.
Sources
I hit up DuckDuckGo hard for this but the two sources that ended up being the most helpful were:
Both just have a wealth of information that all appears to be quite legit and maybe not even written by AI. It’s just good documentation. The latter especially impresses me.
The HTML
The HTML is kept clean of most style and I’m not using utility classes to indicate style either. The only one I do use is the “show” class in order to allow clients that don’t have JavaScript and have small screens to still use the menu. If I don’t do this then it will be invisible and the button that shows the menu won’t work because it needs JavaScript.
I do have to account for some layout in the HTML unfortunately. Because I want the menu to shift down below both the logo and the hamburger menu I have to make sure those two things and the menu are in different children of the same parent. There might be a way to eliminate this with more advanced flexbox use but I kept it relatively simple by allowing this one bit of layout to make it into the document.
<header>
<div id="logo-wrapper">
<h1 id="logo"><a href="http://localhost:1313/">Noah Roberts</a></h1>
<div id="nav-hamburger" >
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</div>
</div>
<nav id="main-nav" class="show">
<ul>
<li>
<a href="/posts">Blog</a>
</li>
<li>
<a href="/resume">Resume</a>
</li>
</ul>
</nav>
</header>
The logo-wrapper div provides that element that contains the logo and the hamburger. The hamburger is an icon created
with SVG and follows the standard you find. It came from an example of a navbar for tailwind.
The menu is a simple unordered list inside of a nav element. This semantic element is meant to indicate that this is a navigation list for screenreaders and such. I don’t actually need to put it in one, but it’s better if I do just for this reason.
Menu selection indicator
I decided I wanted a little tab like indicator thingy in my menu. The way to do this is by messing with pseudoelements in the CSS. These let add extra elements to the element you are styling. In my case I wanted to add a div after my menu item that is actually displayed at the bottom of it and has the shape and color I want.
#main-nav a {
position: relative;
/* ... other styling ... */
}
#main-nav a::after {
content: "";
position: absolute;
bottom: 0;
left: 25%;
width: 50%;
height: .5em;
background-color: var(--color-red-600);
border-radius: .2em .2em 0 0;
}
In both these cases I am refering to any link tag within an element with the id main-nav. In my
setup this is a menu item in the navbar. The first part refers to the element itself and the important
part for this is that it has relative positioning turned on.
The second part builds the tab indicator I want. It places an empty element at the bottom of the link element and a quarter a way from the left. It’s half the length of the link so that puts it in the middle (there’s no ‘center’ placement you can use). I round the top two corners to be a fancylad.
hovering
I wanted an indication if one of the tabs was being “pre-activated”. This means when the mouse is
hovering over it, which you control with the hover pseudoclass.
#main-nav a:hover::after {
left: 10%;
width: 80%;
}
The way you read this is that I’m matching something within an element with the main-nav id. It
is an a type with the hover pseudoclass and after pseudoelement. I make it a bit wider to
indicate it’s ready to be clicked.
I also want to account for keyboard navigation so I also want to mess with the focus-visible pseudoclass.
The a element already has some style regarding this pseudoclass so we need to remove it. Then we want
to have it do the same thing to the pseudoelement.
#main-nav a:focus-visible {
outline: none;
}
#main-nav a:focus-visible::after {
left: 10%;
width: 80%;
}
Now if you tab into the page and get to one of those links it’ll have the same visual indication as if your mouse was over it.
selected item
I also wanted to indicate the selected item, if any. The menu created by hugo in the theme when you create
a new theme uses the aria-current attribute to indicate this. I looked this up and it appears to be
the standard way to do this for screen readers. I want to be accessible (not that the rest of this probably is)
so I continued to use that to indicate the current page in the navbar. I can then make it a slightly
brighter color.
#main-nav a[aria-current]::after {
background-color: var(--color-red-500);
}
Alternative layout for small screens
This layout doesn’t work well if you have a small phone. It doesn’t look great if you have ANY phone. A better layout for phones is to have a top bar with the logo and then a “hamburger” menu that opens up a list of items across the whole screen. Phone apps work this way more often and we’ll follow the same setup here.
Item indicator
The menu links are going to get rearranged by the alternative layout in such a way that makes the
previous setup of pseudoelements look pretty bad. So I move them over to the left to come in from
that direction rather than up from the bottom. I can do all that within the main media query I
explain below, but in an effort to keep that part relatively simple and empty, and because I have
to use a different pseudoelement, I go ahead and define its features as usual but make this pseudoelement
not display. I also need alternative configurations for hover, focus-visible, and aria-current.
#main-nav a::before {
display: none;
content: "";
position: absolute;
left: 0;
top: 25%;
height: 50%;
width: .5em;
background-color: var(--color-red-600);
border-radius: 0 .2em .2em 0;
}
#main-nav a[aria-current]::before {
background-color: var(--color-red-500);
}
#main-nav a:hover::before {
width: .75em;
}
#main-nav a:focus-visible::before {
width: .75em;
}
Media query
You set up alternative layouts with “media queries”. A media query is an assertion about the display and other stuff followed by a block that contains style settings to change. I really just want two different versions: small and normal. So I do my normal styling as usual and that’s what I described above. Then I do my changes when the display gets smaller than some threshold and I picked 600px wide: below that and the display shifts to small mode.
@media only screen and (max-width: 599px) {
header {
display: inline;
}
#nav-hamburger {
display: block;
}
#main-nav {
display: none;
}
#main-nav.show {
display: block;
}
#main-nav li {
float: none;
}
#main-nav a {
display: block;
text-align: left;
}
#main-nav a::after {
display: none;
}
#main-nav a::before {
display: block;
}
}
What this does is:
- Remove the flex from the header and make it the default inline again.
- Show the hamburger
- Hide the main-nav unless it has the
showclass. - Unfloat the list
- Realign the links to be left oriented.
- Hide the horizontal indicators and show the vertical indicators
A note on colors
Another thing I did here was create some color pallates and enable some light/dark stuff. This is also done with media queries.
Creating pallates
I have been using shadecolr to create pallates. These are for
tailwind but I rather like the way tailwind is behaving here. You put these in a :root
pseudoclass.
:root {
--color-red: oklch(62.796% 0.25768 29.234);
--color-red-100: oklch(86.439% 0.06294 31.832);
--color-red-200: oklch(78.925% 0.10494 31.87);
--color-red-300: oklch(72.051% 0.14865 32.862);
--color-red-400: oklch(65.955% 0.18931 32.979);
--color-red-500: oklch(61.633% 0.22072 32.538);
--color-red-600: oklch(50.541% 0.17811 32.616);
--color-red-700: oklch(39.009% 0.13371 32.853);
--color-red-800: oklch(26.4% 0.08335 33.181);
--color-red-900: oklch(11.779% 0.02636 42.738);
/* ... more colors, including gray ... */
/* for light/dark */
--color-background: white;
--color-foreground: var(--color-gray-700);
}
User decides light/dark
To let the user use dark mode we use a media query. It changes the setting from the default defined above.
@media (prefers-color-scheme: dark) {
:root {
--color-background: black;
--color-foreground: var(--color-gray-200);
}
}
I then use those colors in the main part of the html and let them get inherited by everything else not overridden.
html {
margin: 0 auto;
max-width: 1024px;
background-color: var(--color-background);
color: var(--color-foreground);
}
Here I’m also limiting the width of my page and making it center when the display grows beyond that.
