---
title: "Navigation Bar — React"
canonical: https://sky-ui.cf.sky.com/components/navigation-bar/react
apiPackages: [{"name":"@sky-uk/ui-core","representedVersion":"13.2.0"}]
---

# Navigation Bar — React

NavBar is used for page navigation, or anchor links within the page.

```js
import { NavBar } from "@sky-uk/ui-core";
```

---

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| $appearance | string | default | The `$appearance` prop adjusts the NavBar styles for various backgrounds, including a light version for dark backgrounds. It's [responsive](/core/hooks/use-responsive-props/), allowing breakpoint-specific adjustments.<br><br>`'default'` \| `'light'` |
| $fill | boolean | false | Dictates whether the NavBar proportionately fill the available space. This prop is [responsive](/core/hooks/use-responsive-props/) and can be used to set the `$fill` value at different breakpoints. |
| as | string | nav | The `as` prop determines the semantic element used for the component. `nav` is a standard navigation landmark, `tabs` is used for tabbed interfaces, and `anchor` is for anchor link navigation. |
| activeIndex | number |  | Controls which item is active using a zero-based index. Use this for a controlled NavBar. |
| onItemChange | (index: number) => void |  | The `onItemChange` callback triggers when an item is changed, providing the index of the changed item as a number. This enables custom actions based on the specific item interaction. |
| onItemTrigger | (nextIndex: number, currentIndex: number) => boolean \| Promise<boolean> |  | The `onItemTrigger` callback is triggered when an item is triggered. It receives the index of the item that is being triggered and the index of the currently active item. This callback can return a boolean or a Promise that resolves to a boolean to allow or prevent the activation from proceeding. |

### NavBar.Item

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| $active | boolean |  | Sets the initially active item when the NavBar is uncontrolled. |
| $hover | boolean |  | Displays the item in its hover state. |
| $focused | boolean |  | Displays the item in its focused state. |

---

## Basic NavBar

```tsx
<NavBar>
  <NavBar.Item href="#">
    One
  </NavBar.Item>
  <NavBar.Item href="#">
    Two
  </NavBar.Item>
  <NavBar.Item href="#">
    Three
  </NavBar.Item>
  <NavBar.Item href="#">
    Four
  </NavBar.Item>
</NavBar>
```

---

## Page Navigation

NavBar can be used for page navigation by passing `href` props to `NavBar.Item`. In this example, the component is rendered as a `nav` element containing links to different pages.

For accessibility, set `aria-current="page"` on the active item. This indicates to users that they are on the page of the current nav item.

```tsx
() => {
  const [activeIndex, setActiveIndex] = React.useState(0);
  const navItems = [
    { label: "Home", href: "https://www.sky.com" },
    { label: "Watch", href: "https://www.sky.com/watch" },
    { label: "TV", href: "https://www.sky.com/tv" },
    { label: "Glass", href: "https://www.sky.com/glass" },
    { label: "Broadband", href: "https://www.sky.com/broadband" },
    { label: "Mobile", href: "https://www.sky.com/shop/mobile" },
  ];

  return (
    <NavBar activeIndex={activeIndex} onItemChange={setActiveIndex}>
      {navItems.map((item, idx) => (
        <NavBar.Item
          key={item.label}
          href={item.href}
          target="_blank"
          rel="noopener"
          aria-current={activeIndex === idx ? "page" : undefined}
        >
          {item.label}
        </NavBar.Item>
      ))}
    </NavBar>
  );
}
```

---

## Router Components

Use the `component` prop when you want `NavBar.Item` to render a framework-agnostic routing component such as React Router `Link` or Next.js `Link`.

This keeps the NavBar behavior the same while letting the routing component control navigation.

```jsx
import { Link } from "react-router-dom";
import NextLink from "next/link";

<NavBar>
  <NavBar.Item component={Link} to="/dashboard">
    Dashboard
  </NavBar.Item>
  <NavBar.Item component={NextLink} href="/account">
    Account
  </NavBar.Item>
  <NavBar.Item href="/support">Support</NavBar.Item>
</NavBar>;
```

---

## In-Page Navigation

NavBar can be used for in-page anchor navigation by setting the `as` prop to `"anchor"` and providing `href` props that correspond to section IDs on the page. Clicking an item will smoothly scroll to the associated section. The active item updates based on the currently visible section as you scroll.

For accessibility, set `aria-current="location"` on the active item. This indicates that the active nav item is in view.

```full-screen-react-live
() => {
  const sections = [
    { id: "overview", label: "Overview"},
    { id: "features", label: "Features"},
    { id: "pricing", label: "Pricing"},
    { id: "testimonials", label: "Testimonials"},
    { id: "faq", label: "FAQ"},
    { id: "contact", label: "Contact"},
  ];
  const [activeSection, setActiveSection] = React.useState(sections[0].id);
  const sectionRefs = React.useRef(new Map());
  const isScrollingRef = React.useRef(false);
  const scrollTimeoutRef = React.useRef();

  React.useEffect(() => {
    const observers = [];
    sections.forEach((section) => {
      const el = sectionRefs.current.get(section.id);
      if (!el) return;
      const observer = new window.IntersectionObserver(
        ([entry]) => {
          if (entry.isIntersecting && !isScrollingRef.current) {
            setActiveSection(section.id);
          }
        },
        { threshold: 0.5 }
      );
      observer.observe(el);
      observers.push(observer);
    });
    return () => observers.forEach((o) => o.disconnect());
  }, []);

  const handleNavClick = (sectionId) => {
    isScrollingRef.current = true;
    setActiveSection(sectionId);
    clearTimeout(scrollTimeoutRef.current);
    scrollTimeoutRef.current = setTimeout(() => {
      isScrollingRef.current = false;
    }, 1000);
    sectionRefs.current.get(sectionId)?.scrollIntoView({ behavior: "smooth" });
  };

  const activeIndex = sections.findIndex((s) => s.id === activeSection);

  return (
    <>
      <NavBar
        as="anchor"
        $fill={{ xs: true, lg: false }}
        $position="sticky"
        $top={0}
        $zIndex={10}
        $bgColor="white"
        activeIndex={activeIndex}
      >
        {sections.map((section) => (
          <NavBar.Item
            key={section.id}
            href={`#${section.id}`}
            onClick={() => handleNavClick(section.id)}
            aria-current={activeSection === section.id ? "location" : undefined}
          >
            {section.label}
          </NavBar.Item>
        ))}
      </NavBar>
      {sections.map((section) => (
        <Box
          as="section"
          key={section.id}
          id={section.id}
          ref={(el) => {
            if (el) sectionRefs.current.set(section.id, el);
          }}
          $alignItems="center"
          $display="flex"
          $height="600px"
          $justifyContent="center"
        >
          <Text $fontSize="display-3">{section.label}</Text>
        </Box>
      ))}
    </>
  );
}
```

---

## Tab Style Navigation

NavBar can be used as a tabs bar by setting the `as` prop to `"tabs"`. In this mode, the component's semantics change: it renders as a `div` with `role="tablist"`, and each `NavBar.Item` receives `role=tab`. In this example, clicking on a tab updates the active state and displays corresponding content below the NavBar.

For accessibility:

- Set `aria-selected="true"` on the active tab item to indicate it is selected.
- Each tab (`NavBar.Item`) should have an `aria-controls` attribute pointing to the ID of its associated tab panel.
- Each tab panel (the content area with `role="tabpanel"`) should have an `aria-labelledby` attribute pointing to the ID of its corresponding tab.

```tsx
() => {
  const sections = [
    { id: "overview", label: "Overview", color: "grey10" },
    { id: "features", label: "Features", color: "grey20" },
    { id: "pricing", label: "Pricing", color: "grey40" },
    { id: "testimonials", label: "Testimonials", color: "grey50" },
    { id: "faq", label: "FAQ", color: "grey70" },
    { id: "contact", label: "Contact", color: "grey80" },
  ];
  const [activeItem, setActiveItem] = React.useState(sections[0].label);

  return (
    <>
      <NavBar as="tabs">
        {sections.map((section) => (
          <NavBar.Item
            key={section.id}
            onClick={() => setActiveItem(section.label)}
            aria-selected={activeItem === section.label ? "true" : undefined}
            id={`tab-${section.id}`}
            aria-controls={`section-${section.id}`}
          >
            {section.label}
          </NavBar.Item>
        ))}
      </NavBar>
      {activeItem && (
        <Box
          $alignItems="center"
          $bgColor={sections.find((s) => s.label === activeItem)?.color || "transparent"}
          $display="flex"
          $height="400px"
          $justifyContent="center"
          role="tabpanel"
          id={`section-${sections.find((s) => s.label === activeItem)?.id}`}
          aria-labelledby={`tab-${sections.find((s) => s.label === activeItem)?.id}`}
        >
          <Text $fontSize="display-2" $color="white">
            {activeItem}
          </Text>
        </Box>
      )}
    </>
  );
}
```

---

## Callbacks

### onItemChange

The `onItemChange` callback is triggered when a item is changed. It receives the index of the changed item as a number. This allows you to determine which item was activated and perform additional actions or updates based on the selected item.

```tsx
function Example(){
  const [itemState, setItemState] = React.useState(0);

  const handleItemChange = index => {
    setItemState(index);
  };

  return (
    <Flex $flexDirection="column" $gap={5}>
      <Text>Current Tab index: {itemState}</Text>
      <NavBar onItemChange={handleItemChange}>
        <NavBar.Item $active>Video Doorbell</NavBar.Item>
        <NavBar.Item>Indoor Camera</NavBar.Item>
        <NavBar.Item>Motion & Contact Sensors</NavBar.Item>
      </NavBar>
    </Flex>
  );
}
```

### onItemTrigger

The `onItemTrigger` callback requires a promise to be resolved before proceeding with navigation. In this example, when an item is clicked, a modal appears asking if the item change should proceed. Clicking Allow continues, Block cancels.

```tsx
function Example(){
  const [modalOpen, setModalOpen] = React.useState(false);

    const sections = [
      { id: "broadband", label: "Broadband", icon: wifiHouseLinear, color: "grey50" },
      { id: "tv", label: "TV", icon: tvLinear, color: "grey70" },
      { id: "mobile", label: "Mobile", icon: mobileLinear, color: "grey80"},
    ];

    const resolveRef = React.useRef();

    const onItemTrigger = () => {
      setModalOpen(true);
      return new Promise((resolve) => {
        resolveRef.current = resolve;
      });
    };

    const handleModalResult = (result) => {
      if (resolveRef.current) {
        resolveRef.current(result);
        resolveRef.current = undefined;
      }
      setModalOpen(false);
    };

    const [activeItem, setActiveItem] = React.useState(sections[0].label);

    return (
      <>
        <NavBar as='tabs' onItemTrigger={onItemTrigger}>
          {sections.map((section) => (
            <NavBar.Item
              key={section.id}
              onClick={() => setActiveItem(section.label)}
              aria-selected={activeItem === section.label ? 'true' : undefined}
            >
              <Icon src={section.icon} $size={{ xs: 'medium', xl: 'large' }} />
              {section.label}
            </NavBar.Item>
          ))}
        </NavBar>
        {activeItem && (
          <Box
            $alignItems="center"
            $bgColor={sections.find((s) => s.label === activeItem)?.color || "transparent"}
            $display="flex"
            $height="400px"
            $justifyContent="center"
            role="tabpanel"
            id={`section-${sections.find((s) => s.label === activeItem)?.id}`}
            aria-labelledby={`tab-${sections.find((s) => s.label === activeItem)?.id}`}
          >
            <Text $fontSize='display-2' $color='white'>
              {activeItem}
            </Text>
          </Box>
        )}
        {modalOpen && (
          <Modal onClose={() => handleModalResult(false)}>
            <Modal.Content>
              <Flex
                $flexDirection='column'
                $gap={4}
                $justifyContent='center'
                $alignItems='center'
                $width='400px'
                $height='250px'
              >
                <Text $fontSize='display-6'>Allow nav?</Text>
                <Flex $gap={2} $justifyContent='center'>
                  <Button $variant='primary' onClick={() => handleModalResult(true)}>
                    Allow
                  </Button>
                  <Button $variant='secondary' onClick={() => handleModalResult(false)}>
                    Block
                  </Button>
                </Flex>
              </Flex>
            </Modal.Content>
          </Modal>
        )}
      </>
  );
}
```

## System Modifiers

The `NavBar` component supports the props applied using the following system modifiers:

- [background](/core/system/background/)
- [display](/core/system/display/)
- [position](/core/system/position/)
- [flex-child](/core/system/flex-child/)
- [grid-child](/core/system/grid-child/)
- [margin](/core/system/margin/)
- [width](/core/system/width/)
