Navigation

Sticky Nav
v0.0.6
Documentation Under Review

A flexible navigation component that sticks to the top of the viewport, built with Radix UI Navigation Menu primitives. Supports tab-based navigation with active states, gradient styling, and responsive behavior.

Installation

Install the StickyNav component from the Catalyst component library:

npm install @pmi/catalyst-sticky-nav

Overview

The Sticky Nav component provides a sticky navigation interface built on Radix UI's Navigation Menu primitives. It features:

  • Sticky positioning - Stays at the top of the viewport while scrolling
  • Active state management - Highlights the current section with visual indicators
  • Gradient styling - Supports customizable gradient border effects
  • Responsive design - Adapts to mobile views with dropdown select menus
  • Intersection Observer - Automatically updates active state based on scroll position
  • Accessible - Built with WCAG 2.1 AA compliant Radix UI primitives

Examples

Basic Tab Navigation

A basic sticky navigation with tab-style links and active state tracking:

Gradient Primary

Sticky navigation with a primary gradient border effect on the active tab:

Gradient Secondary

Sticky navigation with a secondary gradient border effect:

Gradient Tertiary

Sticky navigation with a tertiary gradient border effect:

Responsive Navigation with Intersection Observer

A complete responsive navigation example that uses Intersection Observer to track scroll position and switches to a dropdown menu on mobile:

Preview

Overview

Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit reprehenderit tenetur obcaecati amet ipsum esse mollitia impedit.

What you'll learn

Lorem ipsum, dolor sit amet consectetur adipisicing elit. Ab sed saepe nobis, facilis necessitatibus doloremque optio molestiae dolores natus qui earum corrupti.

You may also like

Lorem ipsum dolor sit amet consectetur adipisicing elit. Debitis molestiae omnis ratione nobis nostrum corporis sint iste quam totam quod.

Component Structure

The StickyNav component is composed of several sub-components that work together:

  • StickyNavRoot - The container component that wraps the entire navigation
  • StickyNavList - The list container for navigation items
  • StickyNavItem - Individual navigation item wrapper
  • StickyNavLink - The clickable link element with active state support
  • StickyNavContent - Content area for dropdown menus (optional)
  • StickyNavTrigger - Trigger element for dropdown menus (optional)
  • StickyNavSub - Sub-navigation container (optional)
  • StickyNavViewport - Viewport for dropdown content (optional)
  • StickyNavIndicator - Visual indicator for active items (optional)

API Reference

Prop

Type

Prop

Type

Prop

Type

Prop

Type

Styling with Tailwind

The Sticky Nav component uses Tailwind CSS design tokens and data attributes for styling:

Active State Styling

Use the data-[active] attribute to style active navigation items:

<StickyNavLink
  className={cn(
    'text-[color:var(--text-neutral-soft)]',
    'data-[active]:text-[var(--text-primary)]',
    'data-[active]:border-b-[length:var(--border-md)]',
    'data-[active]:border-[var(--border-off-black-dark)]'
  )}
  active={isActive}
>
  Tab Name
</StickyNavLink>

Gradient Borders

Apply gradient borders using CSS custom properties:

<StickyNavLink
  className={cn(
    'border-b-[length:var(--border-md)]',
    'data-[active]:[border-image:linear-gradient(to_right,var(--gradient-medium-primary-from),var(--gradient-medium-primary-to))_1]'
  )}
  active={isActive}
>
  Tab Name
</StickyNavLink>

Design Tokens

Common design tokens used in Sticky Nav:

  • Colors: --text-primary, --text-neutral-soft, --surface-primary
  • Borders: --border-md, --border-off-black-dark, --components-navigation-menu-border
  • Spacing: --scale-24, --scale-56, --scale-80, --scale-96, --scale-160
  • Gradients: --gradient-medium-primary-from/to, --gradient-medium-secondary-from/to, --gradient-medium-tertiary-from/to

Patterns and Best Practices

Intersection Observer Pattern

Use Intersection Observer to automatically update the active state based on scroll position:

useEffect(() => {
  const observer = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          setActiveSection(`#${entry.target.id}`);
        }
      });
    },
    {
      threshold: 1,
      rootMargin: '0px 0px -75%',
    }
  );

  const anchors = sections.map((section) => document.querySelector(section.href)!);
  anchors.forEach((node) => observer.observe(node));

  return () => {
    anchors.forEach((node) => observer.unobserve(node));
  };
}, [sections]);

Hash Change Handling

Track URL hash changes to update active state:

useEffect(() => {
  const hashChangeHandler = () => {
    const hashValue = window.location.hash;
    const tab = tabs.find((tab) => tab.href === hashValue);
    if (tab) {
      setActiveTab(hashValue);
    }
  };
  window.addEventListener('hashchange', hashChangeHandler);

  return () => {
    window.removeEventListener('hashchange', hashChangeHandler);
  };
}, [tabs]);

Responsive Navigation

Provide a mobile-friendly dropdown alternative:

{
  /* Desktop */
}
<div className="hidden sm:block">
  <StickyNavRoot>{/* Navigation items */}</StickyNavRoot>
</div>;

{
  /* Mobile */
}
<div className="sm:hidden">
  <SelectRoot value={activeSection} onValueChange={changeSection}>
    {/* Dropdown menu */}
  </SelectRoot>
</div>;

Scroll Offset

Add scroll margin to account for sticky navigation height:

<h2 id="section-id" className="scroll-mt-[var(--scale-96)] sm:scroll-mt-[var(--scale-160)]">
  Section Title
</h2>

Accessibility

The Sticky Nav component is built with accessibility in mind, using Radix UI's Navigation Menu primitive:

WCAG 2.1 AA Compliance

  • Keyboard Navigation: Full keyboard support with Tab, Arrow keys, Escape, and Enter
  • Focus Management: Visible focus indicators and logical focus order
  • Screen Reader Support: Proper ARIA labels and roles for assistive technologies
  • Color Contrast: Design tokens ensure sufficient contrast ratios (4.5:1 minimum)

Keyboard Interactions

KeyDescription
TabMove focus to the next focusable element
Shift + TabMove focus to the previous focusable element
Enter / SpaceActivate the focused link
Arrow Left/RightNavigate between navigation items (horizontal orientation)
Arrow Up/DownNavigate between navigation items (vertical orientation)
HomeMove focus to the first navigation item
EndMove focus to the last navigation item

ARIA Attributes

The component automatically applies appropriate ARIA attributes:

  • role="navigation" - Identifies the navigation landmark
  • aria-current="page" - Indicates the current active item
  • aria-label - Provides accessible names for navigation regions

Best Practices

  1. Provide meaningful link text - Use descriptive labels that clearly indicate the destination
  2. Maintain focus visibility - Ensure focus indicators are clearly visible
  3. Use semantic HTML - The component uses proper nav and link elements
  4. Test with screen readers - Verify navigation is announced correctly
  5. Ensure sufficient contrast - Active and inactive states should have adequate color contrast
  • Select - Used in responsive navigation for mobile dropdowns
  • Separator - Visual separator between content sections
  • Icons - Icons used in navigation UI elements

See Also

On this page