Working With Express Partial Navigation Bars
I kept wanting to make the nav look beautiful before it worked everywhere.
Picking the perfect color. Tweaking the icon weight.
The discipline I had to learn:
get the structure working at every breakpoint first, then style it.
This maps to a principle I think more devs need to internalize when it comes to separation of concerns.
HTML describes structure. CSS describes appearance. JavaScript describes behavior.
When you mix them in inline styles, embedded scripts, decisions duplicated across files.
You lose the ability to change one without breaking the others.
So I built one nav partial (views/partials/nav.ejs).
<nav class="main-nav">
<button class="nav-toggle" aria-label="Open navigation menu" aria-expanded="false">
☰
</button>
<ul class="nav-links">
<li><a href="#" class="nav-item" <%- currentPage === 'sermons' ? 'aria-current="page"' : ''
%>>Sermons</a></li>
<li><a href="#" class="nav-item" <%- currentPage === 'events' ? 'aria-current="page"' : ''
%>>Events</a></li>
<li><a href="/donations" class="nav-item" <%- currentPage === 'donations' ? 'aria-current="page"' :
'' %>>Donations</a></li>
<li><a href="#" class="nav-item" <%- currentPage === 'pittStop' ? 'aria-current="page"' : '' %>>Pitt
Stop</a></li>
<li><a href="/contacts" class="nav-item" <%- currentPage === 'contacts' ? 'aria-current="page"' : ''
%>>Contact</a></li>
</ul>
</nav>
One stylesheet (public/css/nav.css).
.main-nav {
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 200px;
background: #333;
color: white;
padding: 20px 10px;
}
.nav-links {
list-style: none;
padding: 20px;
margin: 0;
display: flex;
flex-direction: column;
gap: 20px;
}
a.nav-item {
display: block;
color: white;
text-decoration: none;
padding: 12px 20px;
font-size: 1.12rem;
transition: background 0.3s ease;
}
.nav-item:hover {
background: #555;
}
.nav-item[aria-current="page"] {
color: #FFD166;
font-weight: 700;
border-left: 4px solid #FFD166;
padding-left: 16px;
}
.nav-toggle {
display: none;
}
@media (max-width: 768px) {
.main-nav {
background: #333;
/* border: 2px solid red; */
width: auto;
height: auto;
}
.nav-links {
display: none;
color: white;
}
One script
(public/js/nav.js).
const hamburgerMenu = document.querySelector('.nav-toggle');
const navLinks = document.querySelector('.nav-links');
function clickListen() {
const isOpen = navLinks.classList.toggle("open");
hamburgerMenu.setAttribute('aria-expanded', isOpen);
}
hamburgerMenu.addEventListener('click', clickListen);
The same of links serves both the desktop rail and the mobile slide-out.
CSS decides what each layout looks like at different widths then changes a link in one place, it updates everywhere.
That's DRY in action, and it's what keeps a small project scalable.
CSS Specificity Is a Quiet Bully
I had display: none on .nav-links inside my mobile breakpoint. The links should have been hidden, but they weren't.
Took me a while to find out why.
My base rule said ul.nav-links { display: flex } — which has higher specificity than .nav-links { display: none } because of that ul element selector.
The base rule was winning the cascade even inside the media query.
The fix was four characters.
Drop the ul from the base rule. Both rules now have equal specificity.
The mobile rule wins because it comes later in the cascade.
Major Lesson: Keep your base rules un-specific so overrides can compose cleanly.
The more selectors you stack onto a base rule, the harder you make it for any future override to win. Less specificity = more flexibility.
Accessibility Is Three Signals, Not One
When a user lands on Donations,
the Donations link in the nav should look different.
Most devs do this with color alone -- make it gold, bold, done.
This is not enough.
About 1 in 12 men have some form of color blindness.
If your only signal is color, those users have no idea where they are.
The fix for this layer: color, weight, and shape.
My active link is gold (#FFD166), bold (font-weight: 700), with a 4px gold left border. Three signals. Even a fully color-blind user can tell which page they're on.
But the visual is only half the story.
Screen reader users need a semantic signal too.
That's aria-current="page" on the active link, passed dynamically from the route's currentPage variable through the EJS template.
And on the hamburger button, aria-expanded flips between true and false as the menu opens.
so blind users hear the state, not just "button."
Accessibility isn't paint. It's plumbing.
What I Take With Me
I'm building this site because my father pours his life into people, and I want his work to reach further than the four walls of any building.
That changes how I code.
Every gotcha I work through > every specificity battle, every accessibility decision is one that won't trip up the next person who sits in front of his message.
Plumbing before paint. Separation of concerns. Keep it accessible.
Keep walking, friends.




