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

@baukit/navigation

v0.10.14

Published

Shared collapsible navigation for React web and React Native products.

Downloads

2,327

Readme

@baukit/navigation

Collapsible navigation for React DOM and React Native. Products supply routes, copy, icon renderers and tokens. The package imports no router, icon library or Tailwind code.

Entries

  • @baukit/navigation contains the TypeScript model, validation, active-route resolution, section rotation, disclosure reducer and layout selection. It has no React runtime import.
  • @baukit/navigation/web contains AppNavigation and SectionPicker for DOM. It never imports React Native, including through a11y-core.
  • @baukit/navigation/native contains those components for React Native and React Native Web. Use AppNavigation as an Expo Router custom tabBar.
  • @baukit/navigation/web.css styles the DOM entry with --bk-* variables.
import { AppNavigation, SectionPicker } from '@baukit/navigation/web';
import '@baukit/navigation/web.css';

const progress = {
  id: 'progress',
  label: 'Progress',
  href: '/progress',
  icon: ({ active, size }) => <ChartIcon filled={active} size={size} />,
  children: [
    { id: 'overview', label: 'Overview', href: '/progress' },
    { id: 'history', label: 'History', href: '/progress/history' },
  ],
};

<AppNavigation
  label={copy.primary}
  collapseLabel={copy.collapse}
  expandLabel={copy.expand}
  closeLabel={copy.close}
  items={[progress]}
  pathname={pathname}
  onNavigate={(href) => router.navigate(href)}
  profile={{ label: 'Account', initials: 'AB', href: '/profile' }}
/>;
<SectionPicker closeLabel={copy.close} item={progress} pathname={pathname} onNavigate={navigate} />;

Declare items as readonly NavigationItem<NavigationIcon>[] to type icon parameters. Web and native entries each export NavigationIcon. Renderers receive { active, size }. The wrapper hides decorative icons from assistive technology. Include section roots in children when they belong in the cycle.

The default route match compares paths and ignores query strings and hashes. It accepts an exact path or a slash-delimited descendant. matches(pathname) replaces it. Children win over parents, and the longest matching href wins among children. Pass a custom matcher for route aliases, search parameters or hashes. A missing match selects nothing. nextSectionHref returns the first child for a missing match and wraps at the last child.

Below 1024 pixels the component renders a bottom bar with icons above labels. It supports five main items plus an optional profile. Keep labels short enough for the compact bar. Longer labels ellipsize inside their buttons; accessible labels retain the full text. At 1024 pixels it renders a 280-pixel rail. Collapse reduces it to 76 pixels. The active section starts open. Clicking a collapsed disclosure expands the rail and opens that section.

collapsed, defaultCollapsed, and onCollapsedChange support controlled and uncontrolled state. Persist the controlled value in the product's preference store. Supply collapseLabel, expandLabel, and label from the product's catalog. Web and native AppNavigation and SectionPicker also require closeLabel for the visible and accessible menu Close control. Components have no English defaults.

Web uses real anchors. onNavigate intercepts only an unmodified primary click. Without it the browser follows the href. renderLink(props) can return a router Link. Forward every supplied prop, especially href, ref, onKeyDown, onClick and ARIA attributes. Keep modified clicks in the browser. TanStack Router products can map href to to, or use onNavigate with their router instance.

Set replace on SectionPicker to replace the current history entry when a section is picked. It defaults to false. Existing callbacks keep their arguments unless replacement is requested. Web receives onNavigate(href, event, { replace: true }); native receives onNavigate(href, { replace: true }). The product's adapter must apply the option:

// Web with React Router
<SectionPicker
  closeLabel={copy.close}
  item={progress}
  pathname={pathname}
  replace
  onNavigate={(href, _event, options) => navigate(href, { replace: options?.replace ?? false })}
/>;

// Native with Expo Router, alongside the required item, labels, pathname and theme
const onNavigate = (href: string, options?: { readonly replace: boolean }) => {
  if (options?.replace === true) router.replace(href);
  else router.navigate(href);
};

Web renderLink receives replace: true for picker links when replacement is requested. Otherwise the field is omitted, so existing renderers keep their props. A router Link can apply it. Remove it before spreading props onto a DOM anchor. The default anchor renderer removes it. Modified clicks keep normal browser behavior.

A profile accepts either href or a nonempty menu. Menu entries have unique ids, labels and either href or synchronous onSelect. Set tone: 'danger' for destructive actions. Web uses color.status.danger; native uses the NavigationTheme.danger token against background. Check that pair at 4.5:1 in both themes. Native invokes callbacks after the menu closes, using iOS onDismiss or the first frame after unanimated Modal removal on Android/web. Link entries accept matches(pathname). Only the most-specific matching path is selected in a menu. A product owns async action errors. Start an async sign-out in onSelect and handle its rejection in the product. imageUrl renders an avatar, with initials after an image failure. renderAvatar({ active, size }) replaces the image or initials with a product glyph or frame on web and native. It receives the route selection state and the 28-unit avatar size. Return decorative content. The wrapper hides it from assistive technology; the profile label and subtitle still name the button. Web custom avatars have no default background or circular frame. The profile stays last in the bar and at the bottom of the rail.

Active leaves use a muted accent background with an accent icon and bold label. In the expanded rail, the parent of an active child has accent text and icon without a fill. The active icon renderer receives active: true for both rows. In the bar and collapsed rail, the parent gets the fill. Web rows expose data-active="page" or data-active="ancestor"; inactive rows omit the attribute. Override --bk-navigation-active-background, --bk-navigation-active-text and --bk-navigation-ancestor-text on .bk-navigation to match a product palette. The default background mixes 14% accent over the navigation background.

Native tokens and shell

Supply a NavigationTheme from the product's compiled ui-tokens. Do not use raw brand colors in the navigation component.

| NavigationTheme field | Semantic token | | --------------------- | ---------------------------------------------------- | | background | color.background.primary or surface | | text | color.text.primary | | danger | color.status.danger | | muted | color.text.muted | | activeBackground | blendColors(accent, background, 0.14) from ui-tokens | | activeText | color.background.accent | | ancestorText | color.background.accent | | border | color.border.primary | | focus | color.focus.ring | | spacing | space.small | | radius | radius.small |

Use a tested contrast pair for activeText and the muted activeBackground. Check ancestorText against background too. blendColors and contrastRatio from ui-tokens check these pairs in both themes. Native changes are immediate. Web width transitions run only after reduced motion resolves and only if motion is allowed.

Set Expo Tabs' tabBarPosition to left in rail mode and bottom otherwise. Pass the safe-area insets and pathname to the custom tab bar. The component occupies layout space on native. The DOM component is fixed; reserve its width or bottom height in the content shell. NAVIGATION_DIMENSIONS exports those numbers, including 44-unit web and iOS targets and 48 dp Android targets. Match the shell's margin and width transitions to the rail, or reserve the expanded width. Enable transitions only for data-motion="standard". Include the bottom safe-area inset. The section picker belongs inside the current section's content. The DOM picker measures the space below its trigger and above the compact bottom bar. Long sections scroll inside that space. It updates the limit when the viewport, content layout, or scroll position changes.

For compact native content, useNavigationBarHeight(bottomInset, theme) reserves space using the current font scale and the bar's typography and spacing. Pass the same theme as AppNavigation when you customize it. The default height is 75 dp before the bottom inset. getNavigationBarHeight(fontScale, bottomInset, metrics) provides the same calculation without a hook. Its optional metrics are spacing, fontSize and lineHeight. NAVIGATION_DIMENSIONS.nativeBar is the default native height; bar remains the 64-pixel DOM height.

Keep route-heading focus in the product. Use createRouteFocusController from @baukit/a11y-core/web for DOM route changes, and the product's screen-transition adapter on native. Navigation does not know which heading has mounted.

The DOM stylesheet uses stable bk-navigation* classes and data-layout, data-collapsed, data-active, data-open, and data-motion attributes. Products can override these selectors in plain CSS or Tailwind.

Verification

pnpm test runs model and DOM tests, native tests with React Native Testing Library and the React Native Jest preset, and checks the packed exports. Expo is not needed for these tests. pnpm test:browser runs Playwright through Vitest in Chromium and WebKit at 320, 1023 and 1024 pixels, with short and normal heights. It checks geometry, 44-pixel targets, browser warnings and axe.

A profile can supply subtitle for a name or sync status. The expanded rail and profile menu display it below the label. The profile button includes both lines in its accessible label, including in collapsed and compact navigation.

Native NavigationTheme.typography can set fontFamily, fontSize, barFontSize, subtitleFontSize, fontWeight, activeFontWeight, lineHeight and letterSpacing. Sizes and line height use React Native units. Omitted fields keep the default sizes and weights. The font family also applies to menu entries, initials and section pickers, as the web navigation inherits its CSS font family.

Brand and accessory slots

Pass renderBrand(state) for a wordmark or logo and renderAccessory(state) for status or a product control. Both receive { layout: 'bar' | 'rail', collapsed }. Return a small logo and compact status in a collapsed rail. Supply accessible names for logos and controls. Slot content keeps its own semantics.

The rail puts both slots above its controls. The compact bar puts them above the stacked icon and label targets. Web wraps the slot row when its content does not fit. Native measures the slot row and includes it in getNavigationBarHeight through NavigationBarMetrics.slotHeight. useNavigationBarHeight(bottomInset, theme, slotHeight) accepts the same height.

On both platforms, onBarHeightChange(height) reports the full compact bar height, including slots and safe areas. Store it in the content shell to reserve bottom space. Web reports rendered height with a ResizeObserver, including font changes. Native reports the height from the same calculation it uses to size the bar. Use this callback when slots can change size.