webdevpuneet.com

Drill-Down Settings Navigation — Free iOS-Style Panel Nav JS Snippet

Drill-Down Settings Navigation · Navigation · Plain HTML, CSS & JS · Live preview

CategoryNavigation

Copy & paste

Source Code

<div class="dd-app">
  <div class="dd-panels" id="ddPanels">
    <div class="dd-panel" data-panel="root" data-depth="0">
      <div class="dd-panel-header">
        <span class="dd-panel-title">Settings</span>
      </div>
      <button class="dd-row" data-target="account">
        <span>Account</span>
        <span class="dd-chevron">›</span>
      </button>
      <button class="dd-row" data-target="notifications">
        <span>Notifications</span>
        <span class="dd-chevron">›</span>
      </button>
      <button class="dd-row" data-target="privacy">
        <span>Privacy &amp; Security</span>
        <span class="dd-chevron">›</span>
      </button>
      <button class="dd-row" data-leaf="1">
        <span>About</span>
      </button>
    </div>

    <div class="dd-panel" data-panel="account" data-depth="1">
      <div class="dd-panel-header">
        <button class="dd-back" data-back="1">‹ Settings</button>
        <span class="dd-panel-title">Account</span>
      </div>
      <button class="dd-row" data-target="account-profile">
        <span>Profile</span>
        <span class="dd-chevron">›</span>
      </button>
      <button class="dd-row" data-leaf="1"><span>Change password</span></button>
      <button class="dd-row" data-leaf="1"><span>Linked devices</span></button>
    </div>

    <div class="dd-panel" data-panel="account-profile" data-depth="2">
      <div class="dd-panel-header">
        <button class="dd-back" data-back="1">‹ Account</button>
        <span class="dd-panel-title">Profile</span>
      </div>
      <button class="dd-row" data-leaf="1"><span>Display name</span></button>
      <button class="dd-row" data-leaf="1"><span>Avatar</span></button>
      <button class="dd-row" data-leaf="1"><span>Bio</span></button>
    </div>

    <div class="dd-panel" data-panel="notifications" data-depth="1">
      <div class="dd-panel-header">
        <button class="dd-back" data-back="1">‹ Settings</button>
        <span class="dd-panel-title">Notifications</span>
      </div>
      <button class="dd-row" data-leaf="1"><span>Email</span></button>
      <button class="dd-row" data-leaf="1"><span>Push</span></button>
      <button class="dd-row" data-leaf="1"><span>SMS</span></button>
    </div>

    <div class="dd-panel" data-panel="privacy" data-depth="1">
      <div class="dd-panel-header">
        <button class="dd-back" data-back="1">‹ Settings</button>
        <span class="dd-panel-title">Privacy &amp; Security</span>
      </div>
      <button class="dd-row" data-leaf="1"><span>Two-factor authentication</span></button>
      <button class="dd-row" data-leaf="1"><span>Active sessions</span></button>
      <button class="dd-row" data-leaf="1"><span>Data export</span></button>
    </div>
  </div>
  <p class="dd-crumb" id="ddCrumb">Settings</p>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), 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

Navigation state is a single plain array used as a stack — no router or framework state library
Three-state CSS transform system (active, behind, off-screen) drives directional slide animation both ways
Delegated click handling on one container resolves both Back and forward-navigation clicks
Live breadcrumb trail derived directly from the current stack, no separate tracking needed
Panels are matched purely by data-panel/data-target string attributes — no numeric indices to keep in sync
Leaf rows (no data-target) are structurally distinct from navigable rows, ready for custom click handling
Works at any nesting depth — three levels are demonstrated but the stack has no hardcoded limit
Pure CSS transitions handle the slide animation, no animation library required

About this UI Snippet

Drill-Down Settings Navigation — Push/Pop Panel Stack, iOS Settings Style

Screenshot of the Drill-Down Settings Navigation snippet rendered live

A drill-down navigation is the pattern behind the iOS Settings app and countless mobile-style preference screens: tapping a row slides in a deeper panel from the right, and a Back button at the top slides it back out, building up a stack of nested categories rather than expanding everything inline in a tree. This snippet implements the pattern as a genuine navigation stack in vanilla JavaScript — an array of panel names, push and pop operations, and CSS transforms that animate whichever panel just became active.

A plain array as the navigation stack

The entire navigation state is one array, stack, initialized to ['root']. goTo(name) pushes a new panel name onto the end; goBack() pops the last entry off. There is no router, no history API, and no framework state management involved — the current screen is always stack[stack.length - 1], and the full path the user took to get there is the array itself, which crumbTrail() joins into a breadcrumb string for display.

Three CSS states instead of one active flag

render() doesn't just show the current panel and hide everything else — it assigns one of three states to every panel element. The current panel gets .active (translateX(0), fully in view). Any panel still present somewhere earlier in stack gets .behind (translateX(-30%), partially visible off to the left, mimicking iOS's parallax-peek of the previous screen). Everything else keeps its default translateX(100%), parked off-screen to the right, ready to slide in when it's next pushed. This three-state system is what makes both forward and backward navigation feel directional rather than like a hard cut.

Delegated click handling for both actions

A single click listener on the #ddPanels container handles two different actions by checking what was actually clicked: e.target.closest('[data-back]') catches any Back button regardless of which panel it lives in, and e.target.closest('.dd-row') catches any navigable row, reading its target from a data-target attribute. Rows without a data-target (marked data-leaf="1" for clarity) are terminal settings items — clicking them does nothing in this demo, which is exactly where you'd hook in opening a detail screen, a modal, or a toggle.

Why this differs from an expand/collapse tree

A nested sidebar with active-path highlighting keeps the whole hierarchy visible at once and shows depth via indentation — appropriate for desktop, where screen space is abundant. Drill-down navigation instead shows exactly one level of the hierarchy at a time, at full width, which is the right trade-off on narrow viewports where indentation quickly runs out of room and a full list of every nested option would require constant scrolling.

Step by step

How to Use

  1. 1
    Click any row with a chevronRows with a data-target attribute call goTo(name), pushing that panel onto the stack and sliding it in from the right.
  2. 2
    Click Back to go up one levelThe Back button in each panel header calls goBack(), popping the stack and sliding the previous panel back into view.
  3. 3
    Watch the breadcrumb trail updateThe line beneath the panel container shows crumbTrail() — every panel title currently in the stack, joined with a small arrow.
  4. 4
    Add a new nested panelAdd a new .dd-panel element with a unique data-panel name, then add a row anywhere with a matching data-target to link into it.
  5. 5
    Wire up leaf rowsRows marked data-leaf="1" have no data-target and do nothing on click by default — attach your own click handling for opening a modal, toggling a setting, or navigating to a full page.
  6. 6
    Adjust the animation feelChange the 0.28s duration or the -30% "behind" offset in the CSS panel to make the slide faster or show more or less of the previous screen.

Real-world uses

Common Use Cases

Mobile-style settings and preferences screens
The definitive use case — recreate the iOS or Android Settings app drill-down feel for a web app's account or preferences area without a native app shell.
ADMIN
Nested admin configuration menus
Organize a large, deeply-categorized admin configuration area (integrations, billing, security, team) without cramming an expand/collapse tree into a narrow panel.
Multi-level onboarding or setup wizards
Adapt the same push/pop stack for a guided setup flow where later steps depend on which branch of earlier categories the user chose.
Reference implementation of stack-based UI navigation
A minimal, readable example of modeling navigation history as an array rather than reaching for a full routing library — useful groundwork before introducing something like the History API.
Panel-peek animation pattern reference
The partially-visible "behind" panel state is directly reusable anywhere a UI wants to hint at the previous screen during a forward or backward transition.

Got questions?

Frequently Asked Questions

The entire navigation state is one plain array called stack. The current screen is always stack[stack.length - 1]. goTo(name) pushes a new panel name onto it; goBack() pops the last one off. There is no separate index variable or router involved.

render() assigns .active to the current top-of-stack panel (fully visible), .behind to any panel still present earlier in the stack (partially visible via a -30% translateX, mimicking the iOS peek-of-previous-screen effect), and leaves every other panel at its default off-screen-right position, ready to slide in next. This three-state system is what makes forward and backward navigation both feel directional.

Add a new element with class dd-panel and a unique data-panel attribute (matching the target name), including a .dd-panel-header with a Back button and a title. Then add a row anywhere else in the markup with a data-target attribute matching that same panel name.

A row with a data-target attribute calls goTo() when clicked, pushing a new panel onto the stack. A row marked data-leaf="1" has no data-target and does nothing by default in this demo — it represents a terminal setting like a toggle or a text field, where you would attach your own click handling instead of further navigation.

No, this demo keeps navigation state entirely in the in-memory stack array with no URL or history integration. To support the browser back button or deep-linking, you would push a history.pushState() entry alongside each goTo() call and read the current panel from the URL on popstate.

Yes. Keep the stack array in component state (or a simple store), derive the current panel from its last element, and render each panel's CSS class (active/behind/default) based on its position relative to the stack, exactly as render() does here — the push/pop logic itself needs no changes.

Yes. While the Drill-Down Settings 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.