Open demo

CSS code

HTML
<nav class="rail" aria-label="Main">
  <span class="rail-mark" aria-hidden="true">N</span>
  <ul>
    <li><a href="#" aria-current="page"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="4" y="4" width="7" height="7" rx="2"/><rect x="13" y="4" width="7" height="7" rx="2"/><rect x="4" y="13" width="7" height="7" rx="2"/><rect x="13" y="13" width="7" height="7" rx="2"/></svg><span><i>Dashboard</i></span></a></li>
    <li><a href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h10"/></svg><span><i>Projects</i></span></a></li>
    <li><a href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="8" r="3.4"/><path d="M5 20c0-3.4 3-5.6 7-5.6s7 2.2 7 5.6"/></svg><span><i>Team</i></span></a></li>
    <li><a href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 19V5M4 19h16M8 15l3-4 3 3 4-6"/></svg><span><i>Analytics</i></span></a></li>
    <li class="rail-push"><a href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.6 1.6 0 00.3 1.8M4.6 9a1.6 1.6 0 00-.3-1.8"/><circle cx="12" cy="12" r="9"/></svg><span><i>Settings</i></span></a></li>
  </ul>
</nav>
CSS
/* Icon rail that widens on hover or when keyboard focus enters it. Each label sits in a grid column that animates
   from 0fr to 1fr, so the text is always in the DOM and every link keeps its accessible name while collapsed. */
.rail{--rail-bg:#101828;--rail-text:#98a0b3;--rail-accent:#4f46e5;--rail-focus:#a5b4fc;--rail-ease:cubic-bezier(.4,0,.2,1);
  display:flex;flex-direction:column;flex-shrink:0;width:66px;padding:16px 13px;overflow:hidden;background:var(--rail-bg);color:var(--rail-text);
  transition:width .26s var(--rail-ease)}
.rail:hover,.rail:focus-within{width:222px}
.rail-mark{display:grid;place-items:center;flex-shrink:0;width:38px;height:38px;margin-bottom:14px;border-radius:11px;background:var(--rail-accent);
  color:#fff;font-weight:800}
.rail ul{display:flex;flex-direction:column;flex:1;gap:5px;margin:0;padding:0;list-style:none}
.rail .rail-push{margin-top:auto}
.rail a{display:flex;align-items:center;padding:11px 10px;border-radius:10px;color:inherit;text-decoration:none;white-space:nowrap;
  transition:background-color .15s,color .15s}
.rail a:hover{background:rgba(255,255,255,.08);color:#fff}
.rail a[aria-current]{background:rgba(99,102,241,.22);color:#fff}
.rail a:focus-visible{outline:2px solid var(--rail-focus);outline-offset:-2px}
.rail svg{display:block;flex-shrink:0;width:19px;height:19px;stroke:currentColor;fill:none;stroke-width:2}
.rail a span{display:grid;grid-template-columns:0fr;font-size:.89rem;font-weight:600;
  transition:grid-template-columns .26s var(--rail-ease),padding-left .26s}
.rail a span i{overflow:hidden;font-style:normal}
.rail:hover a span,.rail:focus-within a span{grid-template-columns:1fr;padding-left:13px}
@media (prefers-reduced-motion:reduce){.rail,.rail a,.rail a span{transition:none}}

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<nav class="rail" aria-label="Main">
  <span class="rail-mark" aria-hidden="true">N</span>
  <ul class="nav flex-column flex-nowrap">
    <li class="nav-item"><a class="nav-link active" href="#" aria-current="page"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="4" y="4" width="7" height="7" rx="2"/><rect x="13" y="4" width="7" height="7" rx="2"/><rect x="4" y="13" width="7" height="7" rx="2"/><rect x="13" y="13" width="7" height="7" rx="2"/></svg><span><i>Dashboard</i></span></a></li>
    <li class="nav-item"><a class="nav-link" href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h10"/></svg><span><i>Projects</i></span></a></li>
    <li class="nav-item"><a class="nav-link" href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="8" r="3.4"/><path d="M5 20c0-3.4 3-5.6 7-5.6s7 2.2 7 5.6"/></svg><span><i>Team</i></span></a></li>
    <li class="nav-item"><a class="nav-link" href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 19V5M4 19h16M8 15l3-4 3 3 4-6"/></svg><span><i>Analytics</i></span></a></li>
    <li class="nav-item mt-auto"><a class="nav-link" href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.6 1.6 0 00.3 1.8M4.6 9a1.6 1.6 0 00-.3-1.8"/><circle cx="12" cy="12" r="9"/></svg><span><i>Settings</i></span></a></li>
  </ul>
</nav>
CSS
/* Icon rail from a vertical Bootstrap .nav. It widens on hover or when keyboard focus enters it; each label sits in a
   grid column that animates from 0fr to 1fr, so the text stays in the DOM and every link keeps its accessible name. */
.rail{--rail-bg:#101828;--rail-text:#98a0b3;--rail-accent:#4f46e5;--rail-focus:#a5b4fc;--rail-ease:cubic-bezier(.4,0,.2,1);
  display:flex;flex-direction:column;flex-shrink:0;width:66px;padding:16px 13px;overflow:hidden;background:var(--rail-bg);
  transition:width .26s var(--rail-ease)}
.rail:hover,.rail:focus-within{width:222px}
.rail-mark{display:grid;place-items:center;flex-shrink:0;width:38px;height:38px;margin-bottom:14px;border-radius:11px;background:var(--rail-accent);
  color:#fff;font-weight:800}
.rail .nav{--bs-nav-link-padding-x:10px;--bs-nav-link-padding-y:11px;--bs-nav-link-color:var(--rail-text);--bs-nav-link-hover-color:#fff;flex:1;gap:5px}
.rail .nav-link{display:flex;align-items:center;border-radius:10px;line-height:normal;white-space:nowrap;transition:background-color .15s,color .15s}
.rail .nav-link:hover{background:rgba(255,255,255,.08)}
.rail .nav-link.active{background:rgba(99,102,241,.22);color:#fff}
.rail .nav-link:focus-visible{outline:2px solid var(--rail-focus);outline-offset:-2px;box-shadow:none}
.rail svg{display:block;flex-shrink:0;width:19px;height:19px;stroke:currentColor;fill:none;stroke-width:2}
.rail .nav-link span{display:grid;grid-template-columns:0fr;font-size:.89rem;font-weight:600;
  transition:grid-template-columns .26s var(--rail-ease),padding-left .26s}
.rail .nav-link span i{overflow:hidden;font-style:normal}
.rail:hover .nav-link span,.rail:focus-within .nav-link span{grid-template-columns:1fr;padding-left:13px}
@media (prefers-reduced-motion:reduce){.rail,.rail .nav-link,.rail .nav-link span{transition:none}}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<nav class="group flex w-[66px] shrink-0 flex-col overflow-hidden bg-[#101828] px-[13px] py-4 text-[#98a0b3] transition-[width] duration-[260ms] ease-[cubic-bezier(.4,0,.2,1)] focus-within:w-[222px] hover:w-[222px] motion-reduce:transition-none" aria-label="Main">
  <span class="mb-3.5 grid size-[38px] shrink-0 place-items-center rounded-[11px] bg-[#4f46e5] font-extrabold text-white" aria-hidden="true">N</span>
  <ul class="flex flex-1 flex-col gap-[5px]">
    <li><a class="flex items-center rounded-[10px] px-2.5 py-[11px] whitespace-nowrap transition-[color,background-color,border-color] duration-150 hover:bg-white/8 hover:text-white focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#a5b4fc] aria-[current=page]:bg-[#6366f1]/22 aria-[current=page]:text-white motion-reduce:transition-none" href="#" aria-current="page"><svg class="block size-[19px] shrink-0 fill-none stroke-current stroke-2" viewBox="0 0 24 24" aria-hidden="true"><rect x="4" y="4" width="7" height="7" rx="2"/><rect x="13" y="4" width="7" height="7" rx="2"/><rect x="4" y="13" width="7" height="7" rx="2"/><rect x="13" y="13" width="7" height="7" rx="2"/></svg><span class="grid grid-cols-[0fr] text-[.89rem] font-semibold transition-[grid-template-columns,padding-left] duration-[260ms] ease-[cubic-bezier(.4,0,.2,1)] group-focus-within:grid-cols-[1fr] group-focus-within:pl-[13px] group-hover:grid-cols-[1fr] group-hover:pl-[13px] motion-reduce:transition-none"><i class="overflow-hidden not-italic">Dashboard</i></span></a></li>
    <li><a class="flex items-center rounded-[10px] px-2.5 py-[11px] whitespace-nowrap transition-[color,background-color,border-color] duration-150 hover:bg-white/8 hover:text-white focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#a5b4fc] aria-[current=page]:bg-[#6366f1]/22 aria-[current=page]:text-white motion-reduce:transition-none" href="#"><svg class="block size-[19px] shrink-0 fill-none stroke-current stroke-2" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h10"/></svg><span class="grid grid-cols-[0fr] text-[.89rem] font-semibold transition-[grid-template-columns,padding-left] duration-[260ms] ease-[cubic-bezier(.4,0,.2,1)] group-focus-within:grid-cols-[1fr] group-focus-within:pl-[13px] group-hover:grid-cols-[1fr] group-hover:pl-[13px] motion-reduce:transition-none"><i class="overflow-hidden not-italic">Projects</i></span></a></li>
    <li><a class="flex items-center rounded-[10px] px-2.5 py-[11px] whitespace-nowrap transition-[color,background-color,border-color] duration-150 hover:bg-white/8 hover:text-white focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#a5b4fc] aria-[current=page]:bg-[#6366f1]/22 aria-[current=page]:text-white motion-reduce:transition-none" href="#"><svg class="block size-[19px] shrink-0 fill-none stroke-current stroke-2" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="8" r="3.4"/><path d="M5 20c0-3.4 3-5.6 7-5.6s7 2.2 7 5.6"/></svg><span class="grid grid-cols-[0fr] text-[.89rem] font-semibold transition-[grid-template-columns,padding-left] duration-[260ms] ease-[cubic-bezier(.4,0,.2,1)] group-focus-within:grid-cols-[1fr] group-focus-within:pl-[13px] group-hover:grid-cols-[1fr] group-hover:pl-[13px] motion-reduce:transition-none"><i class="overflow-hidden not-italic">Team</i></span></a></li>
    <li><a class="flex items-center rounded-[10px] px-2.5 py-[11px] whitespace-nowrap transition-[color,background-color,border-color] duration-150 hover:bg-white/8 hover:text-white focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#a5b4fc] aria-[current=page]:bg-[#6366f1]/22 aria-[current=page]:text-white motion-reduce:transition-none" href="#"><svg class="block size-[19px] shrink-0 fill-none stroke-current stroke-2" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 19V5M4 19h16M8 15l3-4 3 3 4-6"/></svg><span class="grid grid-cols-[0fr] text-[.89rem] font-semibold transition-[grid-template-columns,padding-left] duration-[260ms] ease-[cubic-bezier(.4,0,.2,1)] group-focus-within:grid-cols-[1fr] group-focus-within:pl-[13px] group-hover:grid-cols-[1fr] group-hover:pl-[13px] motion-reduce:transition-none"><i class="overflow-hidden not-italic">Analytics</i></span></a></li>
    <li class="mt-auto"><a class="flex items-center rounded-[10px] px-2.5 py-[11px] whitespace-nowrap transition-[color,background-color,border-color] duration-150 hover:bg-white/8 hover:text-white focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#a5b4fc] aria-[current=page]:bg-[#6366f1]/22 aria-[current=page]:text-white motion-reduce:transition-none" href="#"><svg class="block size-[19px] shrink-0 fill-none stroke-current stroke-2" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.6 1.6 0 00.3 1.8M4.6 9a1.6 1.6 0 00-.3-1.8"/><circle cx="12" cy="12" r="9"/></svg><span class="grid grid-cols-[0fr] text-[.89rem] font-semibold transition-[grid-template-columns,padding-left] duration-[260ms] ease-[cubic-bezier(.4,0,.2,1)] group-focus-within:grid-cols-[1fr] group-focus-within:pl-[13px] group-hover:grid-cols-[1fr] group-hover:pl-[13px] motion-reduce:transition-none"><i class="overflow-hidden not-italic">Settings</i></span></a></li>
  </ul>
</nav>

About this set

A dark 66px icon rail for app layouts that widens to 222px when a pointer rests on it or keyboard focus enters it, unrolling a text label beside every icon. It holds a brand mark, four main links (Dashboard, Projects, Team, Analytics) and Settings pinned to the bottom with margin-top:auto, and the current page gets an indigo-tinted background. Use it for dashboards and internal tools where screen space matters and people learn the icons quickly. The labels animate with a grid technique: each label sits in a one-column grid whose track goes from 0fr to 1fr, so the text slides out smoothly without measuring widths or animating max-width. Because the labels stay in the DOM the whole time, every link keeps its accessible name while the rail is collapsed. The rail opens on :hover and on :focus-within, so tabbing into it expands it too, and there is no JavaScript. On touch screens the rail stays collapsed and the icons remain working, named links. The Bootstrap version is a vertical .nav with .nav-link items.

What’s included

  • 66px rail that expands to 222px on hover or keyboard focus
  • Labels unroll with a 0fr to 1fr grid column, no JavaScript
  • Accessible names kept while collapsed
  • Settings pinned to the bottom with margin-top:auto
  • Bootstrap version uses a vertical .nav

Accessibility

The rail is a list of links in a nav labelled Main, with aria-current="page" on the current one. Icons are aria-hidden and every link is named by its label, which stays in the DOM when collapsed. Keyboard focus expands the rail through :focus-within and shows a light indigo outline. The width and label transitions switch off under prefers-reduced-motion.

Customise it

Change --rail-bg, --rail-text, --rail-accent and --rail-focus on .rail, and the 66px and 222px widths. In Bootstrap the link colours are --bs-nav-link-color and --bs-nav-link-hover-color on .rail .nav; in Tailwind edit w-[66px], hover:w-[222px] and focus-within:w-[222px].