Open demo

CSS code

HTML
<header class="vh">
  <a class="vh-logo" href="#">Studio Halden</a>
  <button class="vh-toggle" type="button" aria-expanded="false" aria-controls="vh-menu" aria-label="Menu" data-menu-button><i></i><i></i><i></i></button>
  <div class="vh-menu" id="vh-menu">
    <nav aria-label="Main">
      <ul>
        <li><a href="#" aria-current="page">Work</a></li>
        <li><a href="#">Studio</a></li>
        <li><a href="#">Journal</a></li>
        <li><a href="#">Contact</a></li>
      </ul>
    </nav>
    <p class="vh-foot">Riga & London<br>[email protected]</p>
  </div>
</header>
CSS
/* Vertical header: a 224px dark rail down the left edge, sticky at full height, with the footer note pushed to
   the bottom. Below 992px it becomes a top bar and the links fold behind a menu button.
   Put .vh-layout on the element that holds the header and <main> (here, <body>). */
.vh-layout{display:flex;min-height:100vh}
.vh-layout > main{flex:1;min-width:0}
.vh{flex:none;width:224px;position:sticky;top:0;height:100vh;display:flex;flex-direction:column;gap:38px;
  padding:32px 26px;background:#12151d;color:#fff}
.vh,.vh *{box-sizing:border-box}
.vh a{text-decoration:none}
.vh ul{list-style:none;margin:0;padding:0}
.vh a:focus-visible,.vh button:focus-visible{outline:2px solid #a5b4fc;outline-offset:2px}
.vh-logo{font-weight:800;font-size:1.1rem;color:#fff;letter-spacing:-.01em}
.vh-menu{flex:1;display:flex;flex-direction:column}
.vh-menu ul{display:flex;flex-direction:column;gap:2px}
.vh-menu a{display:block;padding:10px 13px;font-size:.9rem;font-weight:600;color:#9aa2b5;border-radius:9px}
.vh-menu a:hover{background:rgba(255,255,255,.07);color:#fff}
.vh-menu a[aria-current="page"]{background:rgba(255,255,255,.1);color:#fff}
.vh-foot{margin:auto 0 0;font-size:.78rem;color:#8b93a6;line-height:1.7}
.vh-toggle{display:none;flex:none;width:44px;height:44px;flex-direction:column;align-items:center;justify-content:center;
  gap:5px;padding:0;background:transparent;border:1px solid #3b404d;border-radius:10px;cursor:pointer}
.vh-toggle i{display:block;width:20px;height:2px;border-radius:2px;background:#fff;transition:transform .2s,opacity .2s}
.vh-toggle[aria-expanded="true"] i:nth-child(1){transform:translateY(7px) rotate(45deg)}
.vh-toggle[aria-expanded="true"] i:nth-child(2){opacity:0}
.vh-toggle[aria-expanded="true"] i:nth-child(3){transform:translateY(-7px) rotate(-45deg)}
@media (max-width:991.98px){
  .vh-layout{display:block}
  .vh{width:auto;height:auto;position:static;flex-direction:row;flex-wrap:wrap;align-items:center;justify-content:space-between;
    gap:0;padding:10px 16px}
  .vh-toggle{display:flex}
  .vh-menu{display:none;flex-basis:100%;padding:6px 0 16px;border-top:1px solid rgba(255,255,255,.1)}
  .vh-menu[data-open]{display:block}
  .vh-foot{margin-top:14px;padding:0 13px}
}
@media (prefers-reduced-motion:reduce){.vh *{transition:none!important}}
JavaScript
/* Menu button: toggles aria-expanded on itself and data-open on the menu it controls (aria-controls).
   Esc closes an open menu and returns focus to the button; widening past 992px resets it. */
document.querySelectorAll('[data-menu-button]').forEach(btn => {
  const menu = document.getElementById(btn.getAttribute('aria-controls'));
  if (!menu) return;
  const set = open => { btn.setAttribute('aria-expanded', String(open)); menu.toggleAttribute('data-open', open); };
  btn.addEventListener('click', () => set(btn.getAttribute('aria-expanded') !== 'true'));
  document.addEventListener('keydown', e => {
    if (e.key === 'Escape' && btn.getAttribute('aria-expanded') === 'true') { set(false); btn.focus(); }
  });
  matchMedia('(min-width: 992px)').addEventListener('change', e => { if (e.matches) set(false); });
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS, Bootstrap 5.3.8 JS bundle

HTML
<header class="navbar navbar-expand-lg vh">
  <a class="navbar-brand vh-logo" href="#">Studio Halden</a>
  <button class="navbar-toggler vh-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#vh-menu" aria-controls="vh-menu" aria-expanded="false" aria-label="Menu"><i></i><i></i><i></i></button>
  <div class="collapse navbar-collapse" id="vh-menu">
    <nav aria-label="Main">
      <ul class="navbar-nav">
        <li class="nav-item"><a class="nav-link active" href="#" aria-current="page">Work</a></li>
        <li class="nav-item"><a class="nav-link" href="#">Studio</a></li>
        <li class="nav-item"><a class="nav-link" href="#">Journal</a></li>
        <li class="nav-item"><a class="nav-link" href="#">Contact</a></li>
      </ul>
    </nav>
    <p class="vh-foot">Riga & London<br>[email protected]</p>
  </div>
</header>
CSS
/* Bootstrap .navbar-expand-lg turned on its side: from 992px the navbar is a sticky full-height column and the
   .navbar-collapse and .navbar-nav stack vertically. Below 992px it is an ordinary top bar with a collapse.
   Put .vh-layout on the element that holds the header and <main> (here, <body>). */
.vh{--bs-navbar-padding-x:16px;--bs-navbar-padding-y:10px;--bs-navbar-color:#9aa2b5;--bs-navbar-hover-color:#fff;
  --bs-navbar-active-color:#fff;--bs-navbar-brand-color:#fff;--bs-navbar-brand-hover-color:#fff;
  --bs-navbar-brand-padding-y:0;--bs-navbar-brand-margin-end:0;--bs-navbar-brand-font-size:1.1rem;--bs-navbar-nav-link-padding-x:13px;
  background:#12151d;color:#fff}
.vh a{text-decoration:none}
.vh a:focus-visible,.vh button:focus-visible{outline:2px solid #a5b4fc;outline-offset:2px;box-shadow:none}
.vh-logo{font-weight:800;letter-spacing:-.01em}
.vh .navbar-nav{--bs-nav-link-padding-x:13px;--bs-nav-link-padding-y:10px;--bs-nav-link-font-size:.9rem;--bs-nav-link-font-weight:600;gap:2px}
.vh .nav-link{border-radius:9px}
.vh .nav-link:hover{background:rgba(255,255,255,.07)}
.vh .nav-link.active{background:rgba(255,255,255,.1)}
.vh-foot{margin:14px 0 16px;padding:0 13px;font-size:.78rem;color:#8b93a6;line-height:1.7}
.vh .navbar-toggler{width:44px;height:44px;padding:0;display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:5px;background:transparent;border:1px solid #3b404d;border-radius:10px}
.vh .navbar-toggler:focus{box-shadow:none}
.vh .navbar-toggler i{display:block;width:20px;height:2px;border-radius:2px;background:#fff;transition:transform .2s,opacity .2s}
.vh .navbar-toggler[aria-expanded="true"] i:nth-child(1){transform:translateY(7px) rotate(45deg)}
.vh .navbar-toggler[aria-expanded="true"] i:nth-child(2){opacity:0}
.vh .navbar-toggler[aria-expanded="true"] i:nth-child(3){transform:translateY(-7px) rotate(-45deg)}
@media (min-width:992px){.vh .navbar-toggler{display:none}}
@media (min-width:992px){
  .vh-layout{display:flex;min-height:100vh}
  .vh-layout > main{flex:1;min-width:0}
  .vh{flex:none;width:224px;position:sticky;top:0;height:100vh;flex-direction:column;align-items:stretch;gap:38px;padding:32px 26px}
  .vh .navbar-collapse{flex-direction:column;align-items:stretch}
  .vh .navbar-nav{flex-direction:column}
  .vh-foot{margin:auto 0 0;padding:0}
}
@media (max-width:991.98px){
  .vh .navbar-collapse > nav{padding-top:6px;border-top:1px solid rgba(255,255,255,.1)}
}
@media (prefers-reduced-motion:reduce){.vh *{transition:none!important}}
JavaScript
/* Bootstrap's Collapse has no Esc handling of its own: close an open navbar menu on Esc
   and return focus to its toggler. */
document.addEventListener('keydown', e => {
  if (e.key !== 'Escape') return;
  document.querySelectorAll('.navbar-collapse.show').forEach(menu => {
    bootstrap.Collapse.getOrCreateInstance(menu, { toggle: false }).hide();
    document.querySelector(`[data-bs-target="#${menu.id}"]`)?.focus();
  });
});

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<header class="flex flex-wrap items-center justify-between bg-[#12151d] px-4 py-2.5 text-white lg:sticky lg:top-0 lg:h-screen lg:w-56 lg:flex-none lg:flex-col lg:flex-nowrap lg:items-stretch lg:justify-start lg:gap-[38px] lg:px-[26px] lg:py-8">
  <a class="text-[1.1rem] font-extrabold tracking-[-.01em] text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#a5b4fc]" href="#">Studio Halden</a>
  <button class="group flex size-11 flex-none cursor-pointer flex-col items-center justify-center gap-[5px] rounded-[10px] border border-[#3b404d] bg-transparent p-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#a5b4fc] lg:hidden " type="button" aria-expanded="false" aria-controls="vh-menu" aria-label="Menu" data-menu-button><i class="block h-0.5 w-5 rounded-sm bg-white transition-transform duration-200 motion-reduce:transition-none group-aria-expanded:translate-y-[7px] group-aria-expanded:rotate-45"></i><i class="block h-0.5 w-5 rounded-sm bg-white transition-opacity duration-200 motion-reduce:transition-none group-aria-expanded:opacity-0"></i><i class="block h-0.5 w-5 rounded-sm bg-white transition-transform duration-200 motion-reduce:transition-none group-aria-expanded:-translate-y-[7px] group-aria-expanded:-rotate-45"></i></button>
  <div class="hidden basis-full border-t border-white/10 pt-1.5 pb-4 data-open:block lg:flex lg:flex-1 lg:basis-auto lg:flex-col lg:border-0 lg:p-0" id="vh-menu">
    <nav aria-label="Main">
      <ul class="flex flex-col gap-0.5">
        <li><a class="block rounded-[9px] px-[13px] py-2.5 text-[.9rem] font-semibold text-[#9aa2b5] hover:bg-white/[.07] hover:text-white aria-[current=page]:bg-white/10 aria-[current=page]:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#a5b4fc]" href="#" aria-current="page">Work</a></li>
        <li><a class="block rounded-[9px] px-[13px] py-2.5 text-[.9rem] font-semibold text-[#9aa2b5] hover:bg-white/[.07] hover:text-white aria-[current=page]:bg-white/10 aria-[current=page]:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#a5b4fc]" href="#">Studio</a></li>
        <li><a class="block rounded-[9px] px-[13px] py-2.5 text-[.9rem] font-semibold text-[#9aa2b5] hover:bg-white/[.07] hover:text-white aria-[current=page]:bg-white/10 aria-[current=page]:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#a5b4fc]" href="#">Journal</a></li>
        <li><a class="block rounded-[9px] px-[13px] py-2.5 text-[.9rem] font-semibold text-[#9aa2b5] hover:bg-white/[.07] hover:text-white aria-[current=page]:bg-white/10 aria-[current=page]:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#a5b4fc]" href="#">Contact</a></li>
      </ul>
    </nav>
    <p class="mt-3.5 px-[13px] text-[.78rem] leading-[1.7] text-[#8b93a6] lg:mt-auto lg:px-0">Riga & London<br>[email protected]</p>
  </div>
</header>
CSS
@theme {
  --breakpoint-lg: 62rem; /* 992px, the same collapse point as Bootstrap's navbar-expand-lg */
}
JavaScript
/* Menu button: toggles aria-expanded on itself and data-open on the menu it controls (aria-controls).
   Esc closes an open menu and returns focus to the button; widening past 992px resets it. */
document.querySelectorAll('[data-menu-button]').forEach(btn => {
  const menu = document.getElementById(btn.getAttribute('aria-controls'));
  if (!menu) return;
  const set = open => { btn.setAttribute('aria-expanded', String(open)); menu.toggleAttribute('data-open', open); };
  btn.addEventListener('click', () => set(btn.getAttribute('aria-expanded') !== 'true'));
  document.addEventListener('keydown', e => {
    if (e.key === 'Escape' && btn.getAttribute('aria-expanded') === 'true') { set(false); btn.focus(); }
  });
  matchMedia('(min-width: 992px)').addEventListener('change', e => { if (e.matches) set(false); });
});

About this set

A dark header that runs down the left edge of the page instead of across the top: a 224px column holding the studio name, four links and a contact note pushed to the bottom with margin-top:auto. It is sticky at full viewport height, so it stays in place while the page scrolls beside it. It suits a portfolio or studio site where the top of the page is better spent on work. A side rail costs too much width on a phone, so below 992px the column becomes an ordinary top bar and the links and contact note fold behind a menu button. The layout needs one class, .vh-layout, on the element that holds the header and the main content, which turns it into a flex row on wide screens. The current link is lit from aria-current=”page”. The Bootstrap version turns .navbar-expand-lg on its side with flex-column from 992px.

What’s included

  • Sticky 224px side rail at full viewport height
  • Footer note pinned to the bottom with margin-top:auto
  • Becomes a top bar with a menu button below 992px
  • Current page styled from aria-current="page"
  • Bootstrap navbar turned vertical with flex-lg-column rules

Accessibility

The links are in a nav labelled Main and the current one has aria-current="page". Grey link text and the contact note both meet 4.5:1 on the dark rail. The menu button is a real button with an accessible name that reports aria-expanded; it opens with Enter or Space, and Esc closes the menu and returns focus to the button. Focus shows a light indigo outline that stands out on the dark background.

Customise it

Change the 224px width and #12151d background on .vh, and #9aa2b5 for the link colour. In Bootstrap the link colours are the --bs-navbar-* variables on .vh; in Tailwind edit w-56, bg-[#12151d] and the text-[…] utilities.