npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-dockbar

v1.3.0

Published

A macOS-Dock-style, animated, drill-down app bar for React 18/19, with pill, glass and solid light/dark themes.

Readme

react-dockbar

npm version npm downloads bundle size CI Release Storybook license semantic-release Conventional Commits

React TypeScript Vite Jest Testing Library Storybook ESLint Biome Node.js

A macOS-Dock-style, animated app bar for React 18 and 19: hover magnification, drill-down navigation with an auto-inserted Back item, and pill, glass and solid themes for light and dark.

react-dockbar pill toolbar with a tooltip over the hovered item

Live demo (Storybook)

Screenshots

| | | | --- | --- | | Open windows: hover an app to see its windows as thumbnails.Previews panel with three window thumbnails over the Chat item | Drill-down: nested levels with an auto-inserted Back button.Submenu level with the Back button next to the dock | | Dark mode: colorScheme="dark" (or auto).Pill toolbar in dark mode | Variants: pill, glass and solid, light and dark.The three variants in light and dark |

Positions: pin it to any edge or corner with position, or keep it inline.

The dock in every position of the viewport

Installation

npm install react-dockbar

Every release is also published to GitHub Packages as @eljijuna/react-dockbar. GitHub requires authentication even for public packages: add this .npmrc next to your package.json, with a token that has the read:packages scope in GITHUB_TOKEN:

@eljijuna:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
npm install @eljijuna/react-dockbar

Import from @eljijuna/react-dockbar (and @eljijuna/react-dockbar/style.css) instead.

import { DockBar } from 'react-dockbar';
import 'react-dockbar/style.css';

function AppMenuBar() {
  return (
    <DockBar
      items={[
        { id: 'finder', label: 'Finder', icon: <FinderIcon />, onSelect: () => openFinder() },
        {
          id: 'settings',
          label: 'Settings',
          icon: <SettingsIcon />,
          children: [
            { id: 'wifi', label: 'Wi-Fi', icon: <WifiIcon />, onSelect: () => openWifi() },
            { id: 'display', label: 'Display', icon: <DisplayIcon />, onSelect: () => openDisplay() },
          ],
        },
      ]}
      colorScheme="auto"
    />
  );
}

Behavior

  • Position: by default the dock is pinned to the bottom center of the viewport (position: fixed, respecting safe-area insets). position accepts 'bottom-center', 'bottom-left', 'bottom-right', 'top-center', 'top-left', 'top-right', 'left-center' and 'right-center' (pair the side ones with orientation="vertical"), or 'inline' to render it in the document flow inside your own layout. Tooltips and magnification open away from the edge. Tune the edge distance with --dockbar-offset (default 16px) and stacking with --dockbar-z-index (default 1000).

  • Auto-hide: autoHide keeps the pinned dock off-screen, like the macOS Dock. It slides in when the pointer reaches its edge (a --dockbar-reveal-size, default 4px, strip along it), stays while the pointer is over it, focus is inside (so Tab always reveals it) or a previews panel is open, and hides again after autoHide={{ delay }} ms (default 400). On touch, a tap on the edge reveals it until the next tap outside. An auto-hiding dock overlays the page, so it does not publish --dockbar-inset-<edge>. Ignored with position="inline".

  • Reserving space for the pinned dock: while pinned, the dock publishes the space it takes from its edge (distance to the edge plus its thickness) on <html> as --dockbar-inset-bottom, --dockbar-inset-top, --dockbar-inset-left or --dockbar-inset-right. It only changes when the dock's size does (not while items magnify) and is removed on unmount or with position="inline". Use it so content scrolls clear of the dock, and so focused or anchored elements are not scrolled behind it:

    body {
      padding-bottom: var(--dockbar-inset-bottom, 0px);
    }
    html {
      scroll-padding-bottom: var(--dockbar-inset-bottom, 0px);
    }
  • Flat items: leaf items call onSelect({ item, path, nativeEvent }) when activated.

  • Nested items: clicking an item with children shrinks the current level toward the center, then expands into that item's children. A circular Back button appears in its own bubble to the left of the dock (above it when vertical); clicking it (or pressing Escape) reverses the animation back up one level. Spacing is tunable via --dockbar-back-gap.

  • Active item: pass activeId (controlled, e.g. from your router) or defaultActiveId (uncontrolled; activating a leaf makes it active). The active item gets a dot indicator and aria-current; parents containing a nested active item get a dimmer dot. Add openActiveLevel to start inside the submenu that holds the active item (mount only — later activeId changes never move the user away from the level they are browsing).

  • Hover magnification: items scale up under the pointer, tapering off for nearby neighbors, matching macOS Dock behavior. Defaults depend on the variant (pronounced for glass/solid, subtle for pill); pass magnification={{ scale, distance }} to override any part of it, or false to disable.

  • Badges: badge shows a count or short text on the item, and it is included in the accessible name ("Mail, 3 notifications"; wording set by labels.badge). For a badge that is not text, such as a dot or an icon, pass badgeLabel (e.g. 'unread messages') so screen readers get it too. A falsy badge (e.g. 0) is neither shown nor announced.

  • Separators: add { type: 'separator', id: 'sep-1' } entries to split items into groups.

  • Toggle items: give a leaf item pressed: boolean to make it a toggle button (aria-pressed) — e.g. panels that can be open at the same time. It is controlled: flip it in onSelect. Toggling never changes the active item, so both can be shown together.

  • Open windows (previews): give a leaf item previews ({ id, title, thumbnail, active?, onSelect?, onClose? }[]) to show its open windows. The item gets up to three dots, and hovering it opens a panel of thumbnails over it (after previewDelay.open, 400ms; it stays previewDelay.close, 200ms, after the pointer leaves so the pointer can reach it). Clicking an item with two or more windows pins the panel instead of calling onSelect; with one window the click selects as usual. Selecting a thumbnail calls its onSelect, closes the panel and makes the item active; onClose adds a close button (and Delete). The thumbnails are yours to render (<img>, <canvas>, <video>…) and are only mounted while the panel is open. The panel opens away from the screen edge, in the top layer (Popover API), and can be controlled with openPreviewsId/onPreviewsOpenChange. Keyboard: the arrow pointing at the panel (↑ for a bottom dock) opens it and focuses the front window, arrows move between windows, Escape closes it and returns focus to the item. Style it with --dockbar-preview-width, --dockbar-preview-aspect-ratio, --dockbar-preview-radius and --dockbar-preview-bg.

  • Theming: colorScheme ('light' | 'dark' | 'auto') and variant switch themes: 'pill' (default: a compact rounded toolbar with a filled active item), 'glass' (a translucent macOS-style dock with a dot under the active item) or 'solid' (the same dock, opaque). Deeper customization is available through CSS custom properties (--dockbar-bg, --dockbar-blur, --dockbar-accent, --dockbar-item-size, ...) documented in src/theme/tokens.css.

  • Accessibility: renders as a role="toolbar" of native <button>/<a> elements, moves focus to/from the Back button on navigation, announces level changes via a live region, and respects prefers-reduced-motion (or force it via the reducedMotion prop). Keyboard follows the WAI-ARIA toolbar pattern: a single Tab stop, arrow keys (↑/↓ when vertical) to move, Home/End, and Escape to go back. In forced-colors mode (Windows High Contrast) the active, pressed and submenu-parent states, dots, separators, badges and tooltips are redrawn with system colors so they stay distinguishable.

Translating screen-reader texts

The dock name and the Back label are props; the dynamic texts are builder functions, so each language controls its own grammar and plurals. Any builder you omit falls back to English.

<DockBar
  items={items}
  ariaLabel="Barra de herramientas"
  backItem={{ label: 'Atrás' }}
  labels={{
    parentItem: (label, count) => `${label}, abre ${count} ${count === 1 ? 'opción' : 'opciones'}`,
    backTo: (label) => `Volver a ${label}`,
    enteredLevel: (label, depth) => `${label}, nivel ${depth + 1}`,
    returnedTo: (label) => (label ? `De vuelta en ${label}` : 'De vuelta en el menú principal'),
    badge: (name, badge) =>
      typeof badge === 'number'
        ? `${name}, ${badge} ${badge === 1 ? 'notificación' : 'notificaciones'}`
        : `${name}, ${badge}`,
    previewsItem: (label, count) =>
      `${label}, ${count} ${count === 1 ? 'ventana abierta' : 'ventanas abiertas'}`,
    previewsPanel: (label) => `Ventanas de ${label}`,
    closePreview: (title) => `Cerrar ${title}`,
  }}
/>

Migrating from 1.x

2.0 changes two defaults. To keep the 1.x look and layout, pass both props:

<DockBar items={items} variant="glass" position="inline" />
  • variant now defaults to 'pill' (was 'glass').
  • position now defaults to 'bottom-center', pinned to the viewport (1.x rendered it in the document flow, which is position="inline" now). This change already shipped in 1.2.0.

Development

npm install
npm run dev          # Storybook
npm test             # Jest + Testing Library
npm run lint          # eslint + biome
npm run build         # library build (ESM + CJS + types + style.css)

Linting, formatting, and TypeScript/Jest configuration are provided by super-configs.

License

MIT