webdevpuneet.com

Lesson Sidebar Navigation — Free Collapsible Course Sidebar UI

Lesson Sidebar Navigation · Navigation · Plain HTML, CSS & JS · Live preview

CategoryNavigation

Copy & paste

Source Code

<aside class="lsn-sidebar" id="lsnSidebar">
  <div class="lsn-head">
    <h2>Intro to Data Design</h2>
    <div class="lsn-course-progress"><div class="lsn-course-bar" id="lsnCourseBar"></div></div>
    <span class="lsn-course-pct" id="lsnCoursePct">40% complete</span>
  </div>
  <nav class="lsn-nav" id="lsnNav">
    <section class="lsn-section" data-open="true">
      <button class="lsn-section-head" type="button">
        <span class="lsn-chevron">&#9656;</span>
        <span class="lsn-section-title">1. Foundations</span>
        <span class="lsn-section-count">3/3</span>
      </button>
      <ul class="lsn-lessons">
        <li class="lsn-lesson" data-status="done"><span class="lsn-icon">&#10003;</span><span class="lsn-text">What is data design?</span><span class="lsn-time">4m</span></li>
        <li class="lsn-lesson" data-status="done"><span class="lsn-icon">&#10003;</span><span class="lsn-text">Reading a dataset</span><span class="lsn-time">6m</span></li>
        <li class="lsn-lesson" data-status="done"><span class="lsn-icon">&#10003;</span><span class="lsn-text">Signal vs. noise</span><span class="lsn-time">5m</span></li>
      </ul>
    </section>
    <section class="lsn-section" data-open="true">
      <button class="lsn-section-head" type="button">
        <span class="lsn-chevron">&#9656;</span>
        <span class="lsn-section-title">2. Structuring Views</span>
        <span class="lsn-section-count">1/4</span>
      </button>
      <ul class="lsn-lessons">
        <li class="lsn-lesson" data-status="done"><span class="lsn-icon">&#10003;</span><span class="lsn-text">Grids &amp; hierarchy</span><span class="lsn-time">7m</span></li>
        <li class="lsn-lesson" data-status="current"><span class="lsn-icon">&#9679;</span><span class="lsn-text">Choosing a chart type</span><span class="lsn-time">9m</span></li>
        <li class="lsn-lesson" data-status="todo"><span class="lsn-icon">&#9675;</span><span class="lsn-text">Color &amp; encoding</span><span class="lsn-time">6m</span></li>
        <li class="lsn-lesson" data-status="todo"><span class="lsn-icon">&#9675;</span><span class="lsn-text">Annotation basics</span><span class="lsn-time">5m</span></li>
      </ul>
    </section>
    <section class="lsn-section" data-open="false">
      <button class="lsn-section-head" type="button">
        <span class="lsn-chevron">&#9656;</span>
        <span class="lsn-section-title">3. Interaction</span>
        <span class="lsn-section-count">0/3</span>
      </button>
      <ul class="lsn-lessons">
        <li class="lsn-lesson" data-status="locked"><span class="lsn-icon">&#128274;</span><span class="lsn-text">Filters &amp; drilldowns</span><span class="lsn-time">8m</span></li>
        <li class="lsn-lesson" data-status="locked"><span class="lsn-icon">&#128274;</span><span class="lsn-text">Tooltips done right</span><span class="lsn-time">4m</span></li>
        <li class="lsn-lesson" data-status="locked"><span class="lsn-icon">&#128274;</span><span class="lsn-text">Final project</span><span class="lsn-time">15m</span></li>
      </ul>
    </section>
    <section class="lsn-section" data-open="false">
      <button class="lsn-section-head" type="button">
        <span class="lsn-chevron">&#9656;</span>
        <span class="lsn-section-title">4. Publishing</span>
        <span class="lsn-section-count">0/2</span>
      </button>
      <ul class="lsn-lessons">
        <li class="lsn-lesson" data-status="locked"><span class="lsn-icon">&#128274;</span><span class="lsn-text">Exporting &amp; sharing</span><span class="lsn-time">3m</span></li>
        <li class="lsn-lesson" data-status="locked"><span class="lsn-icon">&#128274;</span><span class="lsn-text">Course wrap-up</span><span class="lsn-time">2m</span></li>
      </ul>
    </section>
  </nav>
</aside>
<main class="lsn-stage">
  <h1 id="lsnStageTitle">Choosing a chart type</h1>
  <p id="lsnStageHint">Select any unlocked lesson in the sidebar to open it here.</p>
</main>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Collapsible sections
grid-template-rows animates height without fixed values.
Three status icons
Done, current, and locked read from data-status.
Disabled locked rows
Dimmed and inert — clicks are ignored.
Active lesson highlight
Left accent bar and background on the current row.
Auto-advance state
Selecting a lesson demotes the prior current to done.
Course progress bar
Header summarizes overall completion.
Per-section counts
Each header shows completed over total lessons.
Zero dependencies
Plain HTML, CSS, and JS — no framework needed.

About this UI Snippet

Lesson Sidebar Navigation — Collapsible Sections With Lesson Status

Screenshot of the Lesson Sidebar Navigation snippet rendered live

The lesson sidebar navigation is the primary way learners move through a course: sections that expand and collapse, lessons marked done, current, or locked, and one lesson always highlighted as active. This snippet builds it in plain HTML, CSS, and JavaScript — no dependencies.

Collapsible sections

Each .lsn-section carries a data-open attribute. Clicking its .lsn-section-head button flips the attribute, and CSS animates .lsn-lessons between grid-template-rows: 0fr and 1fr — a technique that transitions height without knowing the content's pixel height in advance, so lists of any length collapse and expand smoothly.

Status icons that mean something

Every lesson row carries data-status: done shows a green checkmark, current a cyan dot with a highlighted background and a left accent bar, todo a dim empty circle, and locked a lock glyph. The icon and row styling are driven entirely by that attribute, so changing a lesson's state is a one-line data change.

Locked lessons are truly disabled

Locked rows get opacity: .45, cursor: not-allowed, and their click handler returns immediately — they're visually and functionally inert, not just grayed out with an active click target underneath.

One active lesson at a time

Clicking an unlocked lesson clears current/active state from every other row, demotes the previous current lesson to done, and promotes the clicked lesson to current — mirroring how a real course marks your progress as you move forward.

Course-level progress

A slim progress bar and percentage in the header summarize completion across the whole sidebar, separate from the per-section counts shown next to each section title.

Customizing it

Swap the section and lesson data for your own course, add per-lesson duration or difficulty badges, or persist the active lesson to localStorage so a reload resumes where the learner left off. Pair it with a progress wizard for multi-step enrollment, or a checkbox tree for nested curriculum outlines.

Step by step

How to Use

  1. 1
    Paste the HTML, CSS, and JSA sidebar with four collapsible sections and a stage panel render.
  2. 2
    Click a section headerThe chevron rotates and its lessons expand or collapse.
  3. 3
    Click an unlocked lessonIt becomes the active lesson and the stage title updates.
  4. 4
    Try a locked lessonIt's dimmed and does nothing when clicked.
  5. 5
    Replace the section dataEdit the HTML list items for your own course.
  6. 6
    Tune the accent colorChange the gradient and highlight variables in CSS.

Real-world uses

Common Use Cases

Online course navigation
Let learners move through sections that expand and collapse, with each lesson marked done, current or locked from a data-status attribute.
Onboarding tracks
Structure product setup like an onboarding tour, with a progress wizard showing the overall step the learner is on.
Documentation chapters
Organise long guides into collapsible chapters for documentation sites, animating height through grid-template-rows without any fixed pixel values.
Certification prerequisites
Lock modules in certification programmes until earlier ones are completed, dimming locked rows and ignoring clicks on them entirely.
Internal training programmes
Pair with an onboarding checklist widget for new hires, with a left accent bar marking the active lesson.
Related: Off-Canvas Push Menu
See the Off-Canvas Push Menu for a related navigation pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Each .lsn-lessons list uses CSS grid with grid-template-rows toggled between 0fr and 1fr via the parent's data-open attribute, and a nested wrapper with overflow:hidden clips the content. Grid track sizes animate like any other property, so the browser interpolates from 0fr to 1fr smoothly regardless of how many lessons are inside, with no JavaScript height calculation required.

They stay in the DOM so the course structure remains visible — learners can see what's coming and how much is left, which is a UX expectation for courses. The click handler checks data-status === 'locked' and returns immediately, so the row is present and readable but functionally inert; combined with reduced opacity and cursor: not-allowed, it reads as disabled without hiding content.

From your video/quiz completion callback, find the corresponding .lsn-lesson element (by an id or data attribute you add), set its data-status to 'done', swap the icon glyph, and update the section's count label and the course-level progress bar's width and percentage text. The same function that click-selects a lesson can be extended to also mark the previous lesson done automatically.

Yes. When the last lesson in a section is marked done, loop the next section's lesson elements, set their data-status from 'locked' to 'todo', and remove the disabled click guard for that status. You can also auto-open the newly unlocked section by setting its data-open attribute to true so learners immediately see what's next.

Model the course as an array of sections, each with an array of lessons carrying a status field ('done' | 'current' | 'todo' | 'locked'). Render sections and lessons from that state, toggle an "open" boolean per section in component state instead of a DOM attribute, and update lesson status via your framework's state setter on selection. The CSS grid-template-rows collapse technique ports unchanged since it only depends on a data attribute selector.

Yes. While the Lesson Sidebar Navigation snippet gives you one pattern to copy, the free Responsive Navbar Builder at webdevpuneet.com/navbar-builder/ designs a whole navigation bar: logo, links and dropdown menus, a mobile hamburger menu (dropdown panel, slide-in drawer or full screen) and a scroll effect (sticky, shrink, hide on scroll or transparent). It has six presets, a live preview that runs the real code, and exports HTML + CSS + vanilla JS, React, Tailwind, Vue 3 or Angular — or you can click Fork & Edit to keep editing it in My Code.