PageNavigation

PreviousNext

A responsive page-level navigation bar where the active item is highlighted with an animated black pill. On desktop, all tabs display their text label. On mobile, only the active tab shows its label; inactive tabs show an icon instead.

'use client';

import { useState } from 'react';
import { PageNavigation } from '@/ui/PageNavigation';

const items = [
  { id: 'accommodation', label: 'Accommodation', href: '#accommodation', icon: 'Home' as const },
  { id: 'transport', label: 'Transport', href: '#transport', icon: 'PlaneDefault' as const },
  { id: 'people', label: 'People', href: '#people', icon: 'PeopleSingle' as const },
  { id: 'activities', label: 'Activities', href: '#activities', icon: 'Multisports' as const },
];

export function PageNavigationDemo() {
  const [active, setActive] = useState('accommodation');

  return (
    <nav
      aria-label="Demo wrapper"
      onClick={(e) => {
        const anchor = (e.target as HTMLElement).closest('a');
        if (anchor) {
          e.preventDefault();
          const id = anchor.getAttribute('href')?.replace('#', '');
          if (id) setActive(id);
        }
      }}
    >
      <PageNavigation items={items} activeItem={active} />
    </nav>
  );
}

Installation

npx shadcn@latest add https://develop.trident-ui.pro.clubmed/r/page-navigation.json

Usage

import { PageNavigation } from '@/ui/PageNavigation';
 
const items = [
  { id: 'accommodation', label: 'Accommodation', href: '/accommodation', icon: 'Home' },
  { id: 'transport', label: 'Transport', href: '/transport', icon: 'PlaneDefault' },
  { id: 'activities', label: 'Activities', href: '/activities', icon: 'Multisports' },
];
 
export function Component() {
  return <PageNavigation items={items} activeItem="accommodation" />;
}

Responsive behaviour

ViewportActive itemInactive items
Mobile (< md)Black pill + labelIcon only (if icon provided)
Desktop (≥ md)Black pill + labelLabel only

API Reference

PropTypeDefaultDescription
itemsPageNavigationItem[]RequiredOrdered list of navigation items.
activeItemstringRequiredThe id of the currently active item. Controls the black pill indicator position and which label/icon is shown on mobile.
classNamestring—Additional CSS classes applied to the <nav> element.
FieldTypeDefaultDescription
idstringRequiredUnique identifier used to match against activeItem.
labelstringRequiredHuman-readable label displayed in the tab.
hrefstringRequiredURL the tab links to. Rendered as a native <a> element.
iconIconicNames—Optional trident-icons icon name shown on mobile for inactive tabs. When omitted, the item shows nothing on mobile while inactive.

Notes

  • Each item is a native <a> link — no JavaScript navigation is required for basic usage. Pass onClick to intercept navigation in a SPA context.
  • The active item is marked with aria-current="page" for screen readers.
  • The black pill is absolutely positioned behind the link text using z-index, ensuring the link remains fully interactive.
  • The pill uses transition-all duration-300 — for a sliding animation across tabs, integrate a shared animated indicator using CSS or a motion library such as motion/react.
  • Icon names must be valid IconicNames from @clubmed/trident-icons. Common choices for navigation: Home, PlaneDefault, PeopleSingle, Multisports, CalendarDefault, Search.
  • The component extends ComponentPropsWithoutRef<'nav'> so all standard HTML nav attributes (id, style, data-*, etc.) are forwarded.