Nav
import Nav from "@intility/bifrost-react/Nav";
Responsive navigation
By default, the <Nav>
provides a responsive layout for your app with a logo,
sidebar, top bar, mobile nav and hamburger menu button.
- Mobile -
0px
-599px
- Sidebar is hidden
- Top bar is visible, with
logo
,top
contents, and menu toggle button - Top bar hides when scrolling down, and re-appears when scrolling upwards
- Menu button in top bar toggles fullscreen mobile menu
- Mobile navigation menu displays content from
side
prop. If you want completely separate navigation from sidebar you can usemobile
prop instead.
- Tablet -
600px
-1599px
- Same as mobile, but the menu is placed on the left hand side with an overlay covering the rest of the page
- Desktop -
1600px
and wider- No hamburger button
- Sidebar is displayed only if
side
prop has content - Top bar is displayed only if
top
prop has content
<Nav
// if you only supply a string to `logo` you'll get an "it" logo graphic
logo="Demo App"
side={
<>
<a href="/">
<Nav.Item icon={faHome}>Home</Nav.Item>
</a>
<a href="/profile">
<Nav.Item icon={faUser}>Profile</Nav.Item>
</a>
</>
}
top={
<>
<a href="/profile" title="User profile">
<Nav.Item icon={faUser} />
</a>
<button type="button">
<Nav.Item>Button</Nav.Item>
</button>
</>
}
>
Your app content goes here
</Nav>
<Nav
// if you only supply a string to `logo` you'll get an "it" logo graphic
logo="Demo App"
side={
<>
<a href="/">
<Nav.Item icon={faHome}>Home</Nav.Item>
</a>
<a href="/profile">
<Nav.Item icon={faUser}>Profile</Nav.Item>
</a>
</>
}
top={
<>
<a href="/profile" title="User profile">
<Nav.Item icon={faUser} />
</a>
<button type="button">
<Nav.Item>Button</Nav.Item>
</button>
</>
}
>
Your app content goes here
</Nav>
Logo
<Nav.Logo>
is a helper component to make it easier to style the logo and app
name uniformly across applications.
<Nav.Logo logo={<img src="myLogo.svg" alt="" />}>App Name</Nav.Logo>
<Nav.Logo logo={<img src="myLogo.svg" alt="" />}>App Name</Nav.Logo>
<Nav.Logo logo>
also accepts a string URL which is equivalent to the above.
<Nav.Logo logo="myLogo.svg">App Name</Nav.Logo>
<Nav.Logo logo="myLogo.svg">App Name</Nav.Logo>
The logo
prop on <Nav>
lets you supply any JSX content which will be placed
in the top bar.
We recommend wrapping <Nav.Logo>
in a.bf-neutral-link
linking to the home
page (if you're using a routing lib, you probably want to use <Link>
instead
of <a>
)
<Nav
logo={
<a href="/" className="bf-neutral-link">
<Nav.Logo logo="myLogo.svg">App Name</Nav.Logo>
</a>
}
top={...}
/>
<Nav
logo={
<a href="/" className="bf-neutral-link">
<Nav.Logo logo="myLogo.svg">App Name</Nav.Logo>
</a>
}
top={...}
/>
If you only have a standalone sidebar, the logo will be placed in the top of the sidebar. Mobile devices always have a top bar.
<Nav
logo={
<a href="/" className="bf-neutral-link">
<Nav.Logo logo="myLogo.svg">App Name</Nav.Logo>
</a>
}
side={...}
/>
<Nav
logo={
<a href="/" className="bf-neutral-link">
<Nav.Logo logo="myLogo.svg">App Name</Nav.Logo>
</a>
}
side={...}
/>
Collapsible sidebar
If you want the sidebar to be collapsible, first make sure that each menu item
is a <Nav.Item>
or <Nav.Group>
with an icon.
Pass a boolean state to the sideProps.collapsed
prop, and a function to the
sideProps.onCollapsedChange
event.
Consider using
use-local-storage-state
if you want to persist the state between page reloads.
import { faEnvelopeOpenText, faHome } from "@fortawesome/free-solid-svg-icons";
import Nav from "@intility/bifrost-react/Nav";
import { useState } from "react";
export default function NavSideCollapseDemo() {
const [sideCollapsed, setSideCollapsed] = useState(false);
return (
<Nav
logo="Demo App"
sideProps={{
collapsed: sideCollapsed,
onCollapsedChange: (collapsed) => setSideCollapsed(collapsed),
}}
side={
<>
<a href="#path">
<Nav.Item icon={faHome}>Home</Nav.Item>
</a>
<a href="#path">
<Nav.Item icon={faEnvelopeOpenText}>Tickets</Nav.Item>
</a>
</>
}
>
Your app content goes here
</Nav>
);
}
import { faEnvelopeOpenText, faHome } from "@fortawesome/free-solid-svg-icons";
import Nav from "@intility/bifrost-react/Nav";
import { useState } from "react";
export default function NavSideCollapseDemo() {
const [sideCollapsed, setSideCollapsed] = useState(false);
return (
<Nav
logo="Demo App"
sideProps={{
collapsed: sideCollapsed,
onCollapsedChange: (collapsed) => setSideCollapsed(collapsed),
}}
side={
<>
<a href="#path">
<Nav.Item icon={faHome}>Home</Nav.Item>
</a>
<a href="#path">
<Nav.Item icon={faEnvelopeOpenText}>Tickets</Nav.Item>
</a>
</>
}
>
Your app content goes here
</Nav>
);
}
Standalone sidebar
If you only supply content to side
prop, no top bar will be rendered for
desktop screens.
For mobile screens the sidebar will be hidden and you will get a top bar with logo, app name and mobile menu button.
<Nav
logo="Demo App"
side={
<>
<a href="#path">
<Nav.Item icon={faHome}>Home</Nav.Item>
</a>
<a href="#path">
<Nav.Item icon={faEnvelopeOpenText}>Tickets</Nav.Item>
</a>
</>
}
>
Your app content goes here
</Nav>
<Nav
logo="Demo App"
side={
<>
<a href="#path">
<Nav.Item icon={faHome}>Home</Nav.Item>
</a>
<a href="#path">
<Nav.Item icon={faEnvelopeOpenText}>Tickets</Nav.Item>
</a>
</>
}
>
Your app content goes here
</Nav>
Standalone top bar
<Nav
logo="Demo App"
top={
<>
<a href="#path">
<Nav.Item>Home</Nav.Item>
</a>
<a href="#path" title="User profile">
<Nav.Item icon={faUser} />
</a>
</>
}
>
Your app content goes here
</Nav>
<Nav
logo="Demo App"
top={
<>
<a href="#path">
<Nav.Item>Home</Nav.Item>
</a>
<a href="#path" title="User profile">
<Nav.Item icon={faUser} />
</a>
</>
}
>
Your app content goes here
</Nav>
Grouped items
You can have grouped sections of links by wrapping them (or other content) in a
<Nav.Group>
. Avoid using icons on items inside a group.
- Renders as a popup on hover (or enter/space for keyboard users) for top bar, and for sidebar when collapsed
- Renders as a expandable submenu for mobile nav and sidebar when expanded
- Will be marked as active if there are any active sublinks (
.active
class on a link or button inside the group). Can be overridden with theactive
prop.
<Nav.Group icon={faPencilRuler} name="Applications">
<a href="/overview">
<Nav.Item>Overview</Nav.Item>
</a>
<a href="/apps">
<Nav.Item>Applications</Nav.Item>
</a>
</Nav.Group>
<Nav.Group icon={faPencilRuler} name="Applications">
<a href="/overview">
<Nav.Item>Overview</Nav.Item>
</a>
<a href="/apps">
<Nav.Item>Applications</Nav.Item>
</a>
</Nav.Group>
Can be used inside <Nav side={...}>
and will be rendered as an expandable
section. When the sidebar is collapsed it will be rendered as a pop-out instead.
Or inside <Nav top={...}>
, will be rendered as a dropdown.
Profile picture
To create a profile picture dropdown, use the .bf-nav-profile
class on an
<img>
.
<Nav.Group
name={<img className="bf-nav-profile" src="..." alt="Profile menu" />}
>
[dropdown content...]
</Nav.Group>
<Nav.Group
name={<img className="bf-nav-profile" src="..." alt="Profile menu" />}
>
[dropdown content...]
</Nav.Group>
See full code and demo in the Profile Picture Example.
Search input
You can have a search input in either sidebar or top bar by using <Nav.Search>
.
It renders as an <input>
and accepts the same value
and onChange
props.
Use the optional onSubmit
for handling clicks on the search icon (or enter keypress).
<Nav.Search
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
onSubmit={() => alert("search for " + searchValue)}
/>
<Nav.Search
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
onSubmit={() => alert("search for " + searchValue)}
/>
When used in sidebar, we recommend placing it as the first menu item. It will be rendered inside a dropout when sidebar is collapsed, and a simple input for mobile/tablet menu.
useNav hook
Allows you to access internal state of the Nav, like sideCollapsed
and
mobileOpen
.
See details and usage at useNav hook docs
Highlight active page
You can add an .active
class to the <a>
link (or <button>
) to mark it as
active for the current page.
<a href="/home" className="active">
<Nav.Item icon={faHome}>Home</Nav.Item>
</a>
<a href="/home" className="active">
<Nav.Item icon={faHome}>Home</Nav.Item>
</a>
Next.js App Router
When using Next.JS 13's App Router you can get the current path using the
usePathname
hook,
then apply the .active
class when appropriate.
// app/layout.tsx
import { type PropsWithChildren } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import Nav from "@intility/bifrost-react/Nav";
import { faHome } from "@fortawesome/pro-regular-svg-icons";
export default function RootLayout({ children }: PropsWithChildren) {
const pathname = usePathname();
return (
<html lang="en" className="bf-scrollbar">
<body>
<Nav
logo={
<Link href="/">
<Nav.Logo>Example app</Nav.Logo>
</Link>
}
side={
<>
<Link className={pathname === "/" ? "active" : ""} href="/">
<Nav.Item icon={faHome}>Home</Nav.Item>
</Link>
<Link
className={pathname === "/profile" ? "active" : ""}
href="/profile"
>
<Nav.Item icon={faUser}>Profile</Nav.Item>
</Link>
</>
}
>
{children}
</Nav>
</body>
</html>
);
}
// app/layout.tsx
import { type PropsWithChildren } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import Nav from "@intility/bifrost-react/Nav";
import { faHome } from "@fortawesome/pro-regular-svg-icons";
export default function RootLayout({ children }: PropsWithChildren) {
const pathname = usePathname();
return (
<html lang="en" className="bf-scrollbar">
<body>
<Nav
logo={
<Link href="/">
<Nav.Logo>Example app</Nav.Logo>
</Link>
}
side={
<>
<Link className={pathname === "/" ? "active" : ""} href="/">
<Nav.Item icon={faHome}>Home</Nav.Item>
</Link>
<Link
className={pathname === "/profile" ? "active" : ""}
href="/profile"
>
<Nav.Item icon={faUser}>Profile</Nav.Item>
</Link>
</>
}
>
{children}
</Nav>
</body>
</html>
);
}
React Router
When using <NavLink>
from
react-router-dom
,
an .active
CSS class will automatically be added on the link, which is styled
by bifrost.
import {
Link,
NavLink,
BrowserRouter as Router,
Routes,
Route,
} from "react-router-dom"; // v6
import { faHome, faUser, faTools } from "@fortawesome/pro-regular-svg-icons";
import Nav from "@intility/bifrost-react/Nav";
export default function MyApp() {
return (
<Router>
<Nav
logo={
<Link to="/">
<Nav.Logo>Example app</Nav.Logo>
</Link>
}
side={
<>
<NavLink to="/">
<Nav.Item icon={faHome}>Home</Nav.Item>
</NavLink>
<NavLink to="/profile">
<Nav.Item icon={faUser}>Profile</Nav.Item>
</NavLink>
<NavLink to="/app">
<Nav.Item icon={faTools}>Applications</Nav.Item>
</NavLink>
</>
}
>
<Routes>
<Route path="/" element={<div>Home page</div>} />
<Route path="/profile" element={<div>profile page</div>} />
<Route path="/app" element={<div>Applications page</div>} />
</Routes>
</Nav>
</Router>
);
}
import {
Link,
NavLink,
BrowserRouter as Router,
Routes,
Route,
} from "react-router-dom"; // v6
import { faHome, faUser, faTools } from "@fortawesome/pro-regular-svg-icons";
import Nav from "@intility/bifrost-react/Nav";
export default function MyApp() {
return (
<Router>
<Nav
logo={
<Link to="/">
<Nav.Logo>Example app</Nav.Logo>
</Link>
}
side={
<>
<NavLink to="/">
<Nav.Item icon={faHome}>Home</Nav.Item>
</NavLink>
<NavLink to="/profile">
<Nav.Item icon={faUser}>Profile</Nav.Item>
</NavLink>
<NavLink to="/app">
<Nav.Item icon={faTools}>Applications</Nav.Item>
</NavLink>
</>
}
>
<Routes>
<Route path="/" element={<div>Home page</div>} />
<Route path="/profile" element={<div>profile page</div>} />
<Route path="/app" element={<div>Applications page</div>} />
</Routes>
</Nav>
</Router>
);
}
hideBranding
Since bifrost-react@4.2.0
we include "an intility service" at the bottom of
sidebar. If you need to remove it for some reason, you can use the
hideBranding
prop.
Separate sub-components
The <Nav>
component is built internally using
CSS breakpoints and sub-components: <Nav.Side>
,
<Nav.Top>
, and <Nav.Mobile>
.
These components are basic HTML elements with Bifrost classnames applied. For advanced usage (for instance, sidebar only appears on certain pages) you can use these components yourself to compose whatever responsive navigation layout you want.
The useNav
hook is tied to the <Nav>
component and will not do anything when
working with the sub-components separately.
Nav.Side
LocalStorage/cookie state persistance is implemented in <Nav>
component, when
using <Nav.Side>
directly you can persist its state yourself with collapsed
and onCollapseChange
props.
<Nav.Side
logo='Demo App'
>
<a href='#path'>
<Nav.Item icon={faHome}>Home</Nav.Item>
</a>
<a href='#path'>
<Nav.Item icon={faEnvelopeOpenText}>Tickets</Nav.Item>
</a>
</Nav.Side>
<main>App content goes here</main>
<Nav.Side
logo='Demo App'
>
<a href='#path'>
<Nav.Item icon={faHome}>Home</Nav.Item>
</a>
<a href='#path'>
<Nav.Item icon={faEnvelopeOpenText}>Tickets</Nav.Item>
</a>
</Nav.Side>
<main>App content goes here</main>
Nav.Top
<Nav.Top logo='Demo App'>
<a href='#path'>
<Nav.Item>Home</Nav.Item>
</a>
<a href='#path' title='User profile'>
<Nav.Item icon={faUser} />
</a>
</Nav.Top>
<main>App content goes here</main>
<Nav.Top logo='Demo App'>
<a href='#path'>
<Nav.Item>Home</Nav.Item>
</a>
<a href='#path' title='User profile'>
<Nav.Item icon={faUser} />
</a>
</Nav.Top>
<main>App content goes here</main>
Nav.Mobile
Basically a big, fixed element that takes up the whole screen (leaves space for
top bar), suitable for mobile navigation popup. You will need to toggle it
yourself when not using the full <Nav>
component. Add an onOverlayClick
callback to get an overlay over the page content.
<Nav.Mobile onOverlayClick={handleOverlayClick}>
<a href="#path">
<Nav.Item icon={faHome}>Home</Nav.Item>
</a>
<a href="#path">
<Nav.Item icon={faUser}>Profile</Nav.Item>
</a>
</Nav.Mobile>
<Nav.Mobile onOverlayClick={handleOverlayClick}>
<a href="#path">
<Nav.Item icon={faHome}>Home</Nav.Item>
</a>
<a href="#path">
<Nav.Item icon={faUser}>Profile</Nav.Item>
</a>
</Nav.Mobile>
In order to open and close, you have to toggle between .bf-nav-mobile-open
and
.bf-nav-mobile-close
classes on the <Nav.Mobile>
element.
<Nav.Mobile className='bf-nav-mobile-open' />
<Nav.Mobile className='bf-nav-mobile-close' />
<Nav.Mobile className='bf-nav-mobile-open' />
<Nav.Mobile className='bf-nav-mobile-close' />