Skip to content

Repository files navigation

Priority Nav

A lightweight, zero-dependency TypeScript implementation of the Priority+ navigation pattern. When horizontal space runs out, lower-priority items move into an accessible overflow menu while the first items stay visible.

Features

  • Content-aware navigation without viewport breakpoints
  • Keeps items in DOM order and moves the lowest-priority items first
  • Hidden measurement copy avoids layout flicker
  • Reacts to resize, font loading, content changes, and dynamic items
  • Accessible overflow toggle with outside-click and Escape handling
  • Multiple independent instances
  • Full TypeScript declarations
  • ESM and UMD/CommonJS bundles
  • Zero runtime dependencies

Installation

npm install @jmartsch/priority-nav
# or
bun add @jmartsch/priority-nav

Markup

Items are prioritized in source order: the first item has the highest priority.

<nav class="priority-nav" data-priority-nav aria-label="Primary navigation">
  <div class="priority-nav__items" data-priority-nav-items>
    <a href="/">Home</a>
    <a href="/schedule">Schedule</a>
    <a href="/speakers">Speakers</a>
    <a href="/partners">Partners</a>
  </div>

  <div class="priority-nav__control">
    <button type="button" data-priority-nav-toggle hidden>More</button>
    <div class="priority-nav__menu" data-priority-nav-menu hidden>
      <div class="priority-nav__overflow" data-priority-nav-overflow></div>
    </div>
  </div>
</nav>

The items, toggle, menu, and overflow elements must be descendants of the root passed to PriorityNav.

Required layout CSS

.priority-nav {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.priority-nav__items {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  align-items: center;
  justify-content: flex-end;
  gap: 1rem;
  white-space: nowrap;
}

.priority-nav__control {
  position: relative;
  flex: 0 0 auto;
}

.priority-nav__menu {
  position: absolute;
  top: 100%;
  right: 0;
  z-index: 10;
}

.priority-nav__overflow {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  white-space: nowrap;
}

[hidden] {
  display: none !important;
}

Quick start

import PriorityNav from '@jmartsch/priority-nav'

const root = document.querySelector<HTMLElement>('[data-priority-nav]')
const navigation = new PriorityNav(root)

The constructor initializes the instance automatically.

Configuration

const navigation = new PriorityNav(root, {
  items: '.my-visible-items',
  overflow: '.my-overflow-items',
  toggle: '.my-overflow-toggle',
  menu: '.my-overflow-menu',
  offsetPixels: 8,
  classNames: {
    overflowing: 'has-overflow',
    open: 'overflow-is-open',
  },
  onChange: ({ visibleCount, overflowCount }) => {
    console.log({ visibleCount, overflowCount })
  },
})

Each element option accepts either a selector resolved inside the root or an HTMLElement.

Option Default Description
items [data-priority-nav-items] Container for currently visible items
overflow [data-priority-nav-overflow] Container receiving overflow items
toggle [data-priority-nav-toggle] Overflow menu button
menu [data-priority-nav-menu] Overflow menu panel
offsetPixels 0 Extra safety space subtracted before fitting items
classNames.overflowing priority-nav--overflowing Root class while items overflow
classNames.open priority-nav--open Root class while the menu is open
onChange Called when visible or overflow counts change

API

refresh()

Rebuilds the measurement copy and recalculates the distribution. Normal DOM mutations and resizes are detected automatically.

open(), close(), and toggle()

Control the overflow menu programmatically.

destroy()

Disconnects observers and listeners, removes the measurement copy, and restores all items to their original container and order.

isOverflowing and visibleCount

Read the current state.

PriorityNav.isSupported()

Checks for required browser APIs.

Events

The root dispatches prioritynavchange whenever the distribution changes:

root.addEventListener('prioritynavchange', (event) => {
  const { visibleCount, overflowCount } = (event as CustomEvent).detail
  console.log({ visibleCount, overflowCount })
})

The overflow button receives a data-count attribute with the current number of hidden items.

Browser and SSR behavior

The package targets modern Chrome, Firefox, Safari, and Edge. It requires ResizeObserver, MutationObserver, and requestAnimationFrame. Importing the package during SSR is safe; constructing an instance must happen in the browser.

Development

bun install
bun run verify
bun run build
npm pack --dry-run

Publishing

The package is configured as the public scoped package @jmartsch/priority-nav. Because npm only allows trusted-publisher configuration after a package exists, publish the initial version manually:

npm login
npm publish --access public

After the initial release, configure jmartsch/priority-nav and .github/workflows/publish.yml as the package's trusted GitHub Actions publisher on npm. Subsequent GitHub releases are then published automatically with provenance.

License

MPL-2.0

About

Lightweight TypeScript Priority+ navigation library

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages