DEV Community

Cover image for Build accessible and functional Sidenav with Chakra UI
Gaurav Soni
Gaurav Soni

Posted on • Updated on

Build accessible and functional Sidenav with Chakra UI

Overview

Every web app on the internet has some kind of navigation that allows users to quickly navigate through different pages. Some web apps have navigation on the top and some have navigation on the side. In this blog, we will cover how to build a responsive and accessible sidenav. Building a responsive and accessible sidenav for your web app is quite challenging. There are various things that we need to consider while developing it. For example, In mobile devices where space is limited, a sidenav should be replaced with a drawer.

From the Accessibility(a11y) point of view, if we are navigating via keyboard(Tab key) through nav items, then the order of items should be followed. If we are using icon buttons instead of text for the nav items, then each button should have a proper aria-label to define the purpose of the icon button.

In this blog, we will work on building functional and accessible sidenav. Along the sidenav, we will also focus on creating a reusable responsive layout with well-defined HTML regions to improve accessibility (a11y).

Prerequisite

In order to get most of this blog, I will assume you have some knowledge about React, chakra ui, and typescript. We will use typescript instead of javascript to build our sidenav with type safety. This will help us to find the bugs at the compile time. Nowadays, every framework support typescript. In addition to these, we will also use react-router-dom and react-icons. I am also assuming you already have a React project created. There are a bunch of options to create. You can choose your favorite framework for setup. If you are ready, then let’s start constructing our sidenav.

Understanding the Role of Sidenav Within the App Layout

A sidenav is a navigation panel in your web app that provides links to navigate through different routes. It is provided in the root level component along with the main content.

In the image below, a typical layout of a web app is shown. A web app can have a header, sidenav, main content, and footer.

To make them accessible, they should be defined with appropriate landmarks. With the right landmarks, a screen reader(assistive technologies that audibly convey the content of a computer screen to visually impaired users) can easily switch between different regions of the web app. By adhering to HTML semantics, we can effectively establish meaningful landmarks. In HTML 5, some elements have pre-defined landmarks for different regions. A header has a banner landmark, an aside element has a complimentary landmark, the main has a primary content landmark, and the footer has a content info landmark. You can read more about it on W3C patterns.

Layout

A sidenav is a complementary landmark. The purpose of the complementary landmarks is to provide a supporting section to the primary content. The navigation links are not the primary content of our web app but are important for the user to navigate through primary content. They should be on the same level as primary content. So, in order to provide the supporting content such as navigation links, we need sidenav.

Why is Accessibility (a11y) Important to Us?

A11y(Accessibility) or digital a11y is a way to make your web app available for everyone. With everyone, we mean people with disabilities as well. According to WHO, About 15% of the world's population lives with some form of disability, of whom 2-4% experience significant difficulties in functioning. By default, the web/internet is designed to make it accessible to all kinds of people. That’s why there are different HTML elements and aria attributes exist. An accessible web app can reach a wider range of people and may result in more visitors to your app. There are various types of disabilities that can be covered by an accessible web app. You can learn more about it here.

kind of disabilities

Screen readers are the most useful tools for quickly navigating through web apps for disabled people. A screen reader can understand the semantics of the HTML elements that are used. That’s why we should use the correct HTML elements instead of div or span everywhere. Not only the HTML elements, but the order of the elements also matters a lot. The order in which the elements are laid out will define which text will be read out first and which one after that. For example, in the DOM, we have two elements, and with CSS we change the order(by position absolute or fixed). The order is changed on the screen but for the screen readers, it’s not changed. So, it’s our responsibility to let the screen reader devices know about this.

The keyboard also plays an important role while handling a11y. As we know the mobility-impaired person might not be able to use the mouse or touch devices. In this case, a keyboard-accessible app makes it easy to use for such kinds of people.

A Brief Introduction to Chakra UI

As per docs, "Chakra UI is a simple, modular, and accessible component library that gives you the building blocks you need to build your React applications." It is built on top of the emotion package, which provides a different pattern of writing CSS in JavaScript. Let’s start with installing the chakra ui package,

npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion
Enter fullscreen mode Exit fullscreen mode

Additionally, we also need react-router-dom and react-icons. Let’s install it you haven’t installed it,

npm install react-icons react-router-dom --save
Enter fullscreen mode Exit fullscreen mode

Folder Structure

.
└── sidenav/
├── sidenav-container/
│   ├── sidenav-container.tsx
│   └── sidenav-container.spec.tsx
├── sidenav-items/
│   ├── sidenav-items.tsx
│   └── sidenav-items.spec.tsx
├── sidenav-context/
│   ├── sidenav-context.tsx
│   └── sidenav-context.spec.tsx
├── sidenav.tsx
└── sidenav.spec.tsx
Enter fullscreen mode Exit fullscreen mode

We have a sidenav folder within our components directory. The following components need to be created:-

  1. <Sidenav /> - Our sidenav which is visible by default on the large screen and convert into the drawer in mobile devices.
  2. <SidenavContainer /> - A container where our sidenav will be rendered along with the main content.
  3. <SidenavItems /> - List of all the nav items in the sidenav.
  4. <SidnavContext /> - Context to provide the props that can be shared in nested components. This will be used in case of the mobile devices where we have use drawer and the drawer can be opened from any place.

Create a <SidenavItems /> component

Let’s create our first component. Create a sidenav/sidenav-items/sidenav-items.tsx file. Inside the file, we will first create an interface for our props:

import { IconType } from "react-icons";

export interface SidenavItem {
  icon: IconType;
  label: string;
  to: string;
}

export interface SidenavItemsProps {
  navItems: SidenavItem[];
  mode?: "semi" | "over";
}
Enter fullscreen mode Exit fullscreen mode

Our SidenavItemsProps has two properties:

  1. navItems: List of items of type SidenavItem. SidenavItem defines the structure of the single nav item that can be passed. It has icon, labeland to(route URL).
  2. mode: Used to decide how nav items should be rendered in different devices. semi mode is set when opened on large screens and over is used when opened on mobile devices. This is an optional prop and the default value for this will be semi.

We have our props in place. Let's create our <SidenavItems /> component for semi mode.

import { List, ListItem, Icon, Flex, Text, Link, Tooltip, IconButton } from "@chakra-ui/react";
import { NavLink } from "react-router-dom";

export function SidenavItems({ navItems, mode = "semi" }: SidenavItemsProps) {
  const sidebarItemInSemiMode = (
    { icon: Icon, ...item }: SidenavItem,
    index: number
  ) => (
    <ListItem key={index}>
      <Tooltip label={item.label} placement="right">
        <IconButton
          key={index}
          as={NavLink}
          _focus={{ bg: "gray.100" }}
          _activeLink={{ boxShadow: "md", bg: "orange.500", color: "white" }}
          bg="transparent"
          aria-label={item.label}
          borderRadius="xl"
          icon={<Icon />}
          to={item.to}
        />
      </Tooltip>
    </ListItem>
  );
  return (
    <List spacing={3}>
        {navItems.map((item, index) => sidebarItemInSemiMode(item, index))}
    </List>
  );
}

export default SidenavItems;
Enter fullscreen mode Exit fullscreen mode

We have sidebarItemInSemiMode in our SidenavItems component. This will render a single nav item inside our SidenavItems component. Since our nav items are a kind of list, List component from chakra-ui would be appropriate for this. We need to wrap each item in ListItem component. Then we have Tooltip component wrapping the IconButton component. Sometimes understanding the purpose of the nav item is not clear by just looking at the icon. In such cases, giving a tooltip will make it easier for the users to understand by providing additional information.

The nav item should be a link that navigates to the provided route. But at the same time, it is also an icon. The IconButton component seems like the correct component for this. But from the a11y point of view, it should be a link(anchor tag). How can we combine both? Well, it’s not difficult. Charka-ui provides us as prop which can be used to specify the ReactElement that can be rendered but with the same styles. We will use the NavLink component from react-router-dom to render the IconButton as a link. We also need to highlight our nav item whenever the user either hovers or focuses on it. On click, it should be marked as active in order to make the current nav item highlighted. This will help us to convey the right state of the link. The _activeLink and _focus props are used for this. The hover state is already handled by IconButton. Note that the active state of the link is provided by NavLink. So in order to _activeLink to work, this is required.

Chakra ui provides us with a set of default color palettes. I am using orange here. But you can choose anyone.

aria-label is also provided to help screen readers to read the appropriate text for visually impaired users.

In the end, we need to iterate over the nav items and render them using the array map function.

Next, let’s create sidenav items for over mode.

import { List, ListItem, Icon, Flex, Text, Link, Tooltip, IconButton } from "@chakra-ui/react";
import { NavLink } from "react-router-dom";

export function SidenavItems({ navItems, mode = "semi" }: SidenavItemsProps) {
  const sidebarItemInOverMode = (item: SidenavItem, index: number) => (
    <ListItem key={index}>
      <Link
        display="block"
        as={NavLink}
        to={item.to}
        _focus={{ bg: "gray.100" }}
        _hover={{
          bg: "gray.200"
        }}
        _activeLink={{ bg: "orange.500", color: "white" }}
        w="full"
        borderRadius="md"
      >
        <Flex alignItems="center" p={2}>
          <Icon boxSize="5" as={item.icon} />
          <Text ml={2}>{item.label}</Text>
        </Flex>
      </Link>
    </ListItem>
  );
  return (
    <List spacing={3}>
        {navItems.map((item, index) => sidebarItemInSemiMode(item, index))}
    </List>
  );
}

export default SidenavItems;
Enter fullscreen mode Exit fullscreen mode

sidebarItemInOverMode is similar to sidebarItemInSemiMode . The only difference is instead of IconButton we are using Link component. In over mode, we are rendering the icon along with the text. So it is easy to understand the purpose of the icon. Hence, we don’t need to wrap it inside the tooltip. So the full component will look like this,

import {
  List,
  ListItem,
  Icon,
  Flex,
  Text,
  Link,
  Tooltip,
  IconButton
} from "@chakra-ui/react";
import { IconType } from "react-icons";
import { NavLink } from "react-router-dom";

export interface SidenavItem {
  icon: IconType;
  label: string;
  to: string;
}

export interface SidenavItemsProps {
  navItems: SidenavItem[];
  mode?: "semi" | "over";
}

export function SidenavItems({ navItems, mode = "semi" }: SidenavItemsProps) {
  const sidebarItemInOverMode = (item: SidenavItem, index: number) => (
    <ListItem key={index}>
      <Link
        display="block"
        as={NavLink}
        to={item.to}
        _focus={{ bg: "gray.100" }}
        _hover={{
          bg: "gray.200"
        }}
        _activeLink={{ bg: "orange.500", color: "white" }}
        w="full"
        borderRadius="md"
      >
        <Flex alignItems="center" p={2}>
          <Icon boxSize="5" as={item.icon} />
          <Text ml={2}>{item.label}</Text>
        </Flex>
      </Link>
    </ListItem>
  );
  const sidebarItemInSemiMode = (
    { icon: Icon, ...item }: SidenavItem,
    index: number
  ) => (
    <ListItem key={index}>
      <Tooltip label={item.label} placement="right">
        <IconButton
          key={index}
          as={NavLink}
          _focus={{ bg: "gray.100" }}
          _activeLink={{ boxShadow: "md", bg: "orange.500", color: "white" }}
          bg="transparent"
          aria-label={item.label}
          borderRadius="xl"
          icon={<Icon />}
          to={item.to}
        />
      </Tooltip>
    </ListItem>
  );
  return (
    <List spacing={3}>
      {mode === "semi"
        ? navItems.map((item, index) => sidebarItemInSemiMode(item, index))
        : navItems.map((item, index) => sidebarItemInOverMode(item, index))}
    </List>
  );
}

export default SidenavItems;
Enter fullscreen mode Exit fullscreen mode

While iterating over the nav items, we can check the mode provided to the and render them accordingly. The preview of this component will looks like below,

Sidenav items demo

Sidenav context

Why do we need sidenav context?

We are creating a responsive sidenav. As we mentioned earlier, for mobile devices we will use the <Drawer /> component. The drawer will be hidden by default and can be opened by some action(button click etc). On some action, we need to convey this message to our drawer. This can be done by passing a callback as a prop to the sidenav. This is fine if you have one level of nesting for your component that will trigger the action. But if you have a deeply nested component that will trigger the action, this might be a tedious task. As per react docs,

Context provides a way to pass data through the component tree without having to pass props down manually at every level.”

Our sidenav component is a layout component and will be added at the root level. Other components may be placed within routes or deeply nested. To enable communication between these components, we utilize sidenav context. This type of scenario commonly arises during application development. Alternatively, you could use simple props, but that would lead to prop drilling.

Create a SidenavContext

Create a sidenav/sidenav-context.tsx file. We will add context inside the file:

import { useDisclosure } from "@chakra-ui/react";
import { createContext } from "react";
const SidenavContext = createContext<ReturnType<typeof useDisclosure> | null>(
  null
);
Enter fullscreen mode Exit fullscreen mode

Our sidenav requires specific props like open, close, toggle, etc. to manage the behavior of the drawer. Chakra ui provides a custom hook useDisclosure, designed for such components. Additionally, it is important to have a strongly typed context. To achieve this, we will utilize the ReturnType utility from TypeScript to obtain the functions and properties exposed by useDisclosure. Initially, the context will have no value, so we will initialize it with null.

Now, let’s create a custom hook for our sidenav context.

import { useContext } from "react";

export function useSidenav() {
  const sidebar = useContext(SidenavContext);
  if (!sidebar) {
    throw new Error("Cannot use `sidebar context` outside SidebarProvider");
  }
  return { ...(sidebar as ReturnType<typeof useDisclosure>) };
}
Enter fullscreen mode Exit fullscreen mode

Wrapping our context in the custom hook makes it easy to use in other places. Our sidenav context initially has a null value. So, it’s good to check if the context value is provided or not. If not, then simply throw an error.

Let’s create a provider,

import { useDisclosure } from "@chakra-ui/react";

export function SidenavProvider({
  children,
  ...props
}: {
  children: React.ReactNode;
}) {
  const disclosure = useDisclosure();
  return (
    <SidenavContext.Provider value={{ ...disclosure }} {...props}>
      {children}
    </SidenavContext.Provider>
  );
}

export default SidenavProvider;
Enter fullscreen mode Exit fullscreen mode

This code is wrapping the SidenavContext.Provider with disclosure value and exports it. This is a common practice in React and also makes the code easy to test. The whole code for sidenav context will look like this,

import { useDisclosure } from "@chakra-ui/react";
import { createContext, useContext } from "react";

const SidenavContext = createContext<ReturnType<typeof useDisclosure> | null>(
  null
);

export function useSidenav() {
  const sidebar = useContext(SidenavContext);
  if (!sidebar) {
    throw new Error("Cannot use `sidebar context` outside SidebarProvider");
  }
  return { ...(sidebar as ReturnType<typeof useDisclosure>) };
}

export function SidenavProvider({
  children,
  ...props
}: {
  children: React.ReactNode;
}) {
  const disclosure = useDisclosure();
  return (
    <SidenavContext.Provider value={{ ...disclosure }} {...props}>
      {children}
    </SidenavContext.Provider>
  );
}

export default SidenavProvider;
Enter fullscreen mode Exit fullscreen mode

Create a <Sidenav /> Component

Create a sidenav/sidenav.tsx file. Within the file, we will create components and interfaces for our props.

import React from "react";
import {
  Drawer,
  DrawerContent,
  DrawerCloseButton,
  DrawerHeader,
  DrawerOverlay,
  VStack,
  DrawerBody,
  Icon
} from "@chakra-ui/react";
import { SiWwise } from "react-icons/si";
import { useSidenav } from "./sidenav-context/sidenav-context";
import SidenavItems, { SidenavItem } from "./sidenav-items/sidenav-items";

export interface SidenavProps {
  navItems: SidenavItem[];
}

export function Sidenav({ navItems }: SidenavProps) {
  const { isOpen, onClose } = useSidenav();
  return (
    <React.Fragment>
      <VStack spacing="5" as="nav" display={{ base: "none", md: "flex" }}>
        <Icon as={SiWwise} boxSize={8} /> {/*OR PUT YOUR LOGO HERE */}
        <SidenavItems navItems={navItems} />
      </VStack>
      <Drawer placement="left" onClose={onClose} isOpen={isOpen}>
        <DrawerOverlay />
        <DrawerContent>
          <DrawerCloseButton />
          <DrawerHeader>Weather Wise</DrawerHeader>
          <DrawerBody>
            <SidenavItems navItems={navItems} mode="over" />
          </DrawerBody>
        </DrawerContent>
      </Drawer>
    </React.Fragment>
  );
}

export default Sidenav;
Enter fullscreen mode Exit fullscreen mode

The SidenavProps interface is straightforward. It has a single property navItems. Let’s look at the Sidenav component. We are using our custom hook useSidenav coming from our sidenav context. After that, we have our JSX which contains two siblings that wrap our component in different components. The first one is VStack and the other one is Drawer. Let’s look at the first one,

<VStack spacing="5" as="nav" display={{ base: "none", md: "flex" }}>
  <Icon as={SiWwise} boxSize={8} /> {/*OR PUT YOUR LOGO HERE */}
  <SidenavItems navItems={navItems} />
</VStack>
Enter fullscreen mode Exit fullscreen mode

VStack will render the items vertically. Internally, it’s a flexbox with a flex-direction column. As we discussed earlier, HTML semantics are crucial for accessibility (a11y). Therefore, we will once again use the as prop to render the VStack as a nav element instead of a div. The VStack is visible on large screens but hidden on mobile devices. To achieve this, we use display prop with the value of {{ base: "none", md: "flex" }}. In Chakra UI, the breakpoints are designed with a mobile-first approach, so base refers to mobile devices. After the VStack, we have the logo (or in its absence, an Icon can be placed) and the SidenavItems.The SidenavItems component is set to semi mode by default, eliminating the need to provide it explicitly.

Now let's look at the other code snippet,

<Drawer placement="left" onClose={onClose} isOpen={isOpen}>
    <DrawerOverlay />
    <DrawerContent>
      <DrawerCloseButton />
      <DrawerHeader>Sidenav Header</DrawerHeader>
      <DrawerBody>
        <SidenavItems navItems={navItems} mode="over" />
      </DrawerBody>
    </DrawerContent>
  </Drawer>
Enter fullscreen mode Exit fullscreen mode

We are utilizing the Drawer component from Chakra UI, which represents a panel that slides out from the screen edge. The Drawer component requires an onClose callback, obtained from the Sidenav context. The isOpen prop determines whether the drawer is open or closed. By default, our useSidenav hook provides a false value for isOpen, resulting in the drawer being closed. Our sidenav will always be rendered on the left side, we need to set the placement to left.

Next, we have the DrawerOverlay component, responsible for rendering the backdrop with a gray background. The DrawerCloseButton is an icon placed at the top to allow the closing of the drawer. The DrawerHeader contains the header text, wrapped inside a header tag.

Inside the DrawerBody, we have our SidenavItems component. This code snippet does not include breakpoints because our drawer is always closed by default. To show the drawer, we need to set isOpen to true through an external button that is visible only on mobile devices. It is worth noting that we are providing the over mode to the SidenavItems component, which renders the navigation items differently from the default semi mode.

The whole code for sidenav component will look like this,

import React from "react";
import {
  Drawer,
  DrawerContent,
  DrawerCloseButton,
  DrawerHeader,
  DrawerOverlay,
  VStack,
  DrawerBody,
  Icon
} from "@chakra-ui/react";
import { SiWwise } from "react-icons/si";
import { useSidenav } from "./sidenav-context/sidenav-context";
import SidenavItems, { SidenavItem } from "./sidenav-items/sidenav-items";

export interface SidenavProps {
  navItems: SidenavItem[];
}

export function Sidenav({ navItems }: SidenavProps) {
  const { isOpen, onClose } = useSidenav();
  return (
    <React.Fragment>
      <VStack spacing="5" as="nav" display={{ base: "none", md: "flex" }}>
        <Icon as={SiWwise} boxSize={8} /> {/*OR PUT YOUR LOGO HERE */}
        <SidenavItems navItems={navItems} />
      </VStack>
      <Drawer placement="left" onClose={onClose} isOpen={isOpen}>
        <DrawerOverlay />
        <DrawerContent>
          <DrawerCloseButton />
          <DrawerHeader>Sidenav Header</DrawerHeader>
          <DrawerBody>
            <SidenavItems navItems={navItems} mode="over" />
          </DrawerBody>
        </DrawerContent>
      </Drawer>
    </React.Fragment>
  );
}

export default Sidenav;
Enter fullscreen mode Exit fullscreen mode

SidenavContainer

Why do we need <SidenavContainer />?

With the above sidenav component, we have completed its implementation. You are free to use it as you see fit. However, from my perspective, it is only partially implemented. If I were to utilize it in an application, I would likely create a layout component and incorporate our sidenav within it. The sidenav is an integral part of our layout, and layout components bring together all the core components that remain unchanged regardless of the routes. If you wish to take it a step further, we can begin by creating our <SidenavContainer /> component.

Create <SidenavContainer /> component

Create a sidenav/sidenav-container.tsx file. Within the file, we will create an interface for our props.

import { ReactNode, ReactElement } from "react";

export interface SidenavContainerProps {
  children: ReactNode;
  sidenav: ReactElement;
}
Enter fullscreen mode Exit fullscreen mode

There are two props for our SidenavContainer component: children and sidenav. Children are the react nodes that are passed by React. Sidenav is the component that we created above.

import { Box, Grid, GridItem } from "@chakra-ui/react";

export function SidenavContainer({ children, sidenav }: SidenavContainerProps) {
  return (
    <Grid templateAreas={`'sidebar main'`} templateColumns="auto 1fr">
      <GridItem area="sidebar" as="aside" w="full" p={0}>
        <Box
          pos="sticky"
          top={0}
          w={{ base: 0, md: "72px" }}
          borderRight="1px solid"
          borderColor="gray.100"
          p={{ base: 0, md: 2 }}
          paddingTop={8}
          height="100vh"
          overflow="auto"
          css={{
            "&::-webkit-scrollbar": {
              height: "var(--chakra-sizes-1)",
              width: "var(--chakra-sizes-1)"
            },
            "&::-webkit-scrollbar-thumb": {
              backgroundColor: "var(--chakra-colors-gray-400)"
            }
          }}
        >
          {sidenav}
        </Box>
      </GridItem>
      <GridItem as="main" area="main" p={{ base: 6, md: 8 }}>
        {children}
      </GridItem>
    </Grid>
  );
}

export default SidenavContainer;
Enter fullscreen mode Exit fullscreen mode

CSS grid is an ideal choice for creating two-dimensional layouts, and our case is no exception. Chakra UI conveniently provides us with a grid component out of the box, so we have opted to utilize it. If you would like to learn more about grids, you can refer to the MDN docs

Tip: To conveniently organize and position elements on the grid, consider using the templateAreas property.

In our grid, we have two items. The first item is our sidenav, and there are a few important details to note. We utilize the as prop to instruct Chakra UI to render the grid item as an aside element, which is crucial for accessibility reasons as mentioned earlier. Additionally, we make the sidenav sticky, ensuring it remains fixed when the user scrolls through the main content.

The second aspect to notice is w={{ base: 0, md: "72px" }}. By default, our sidenav has a semi mode, so we assign a width of 72px to make it visible on the screen. For mobile devices, we transition from a two-dimensional layout to a one-dimensional layout, hiding the sidenav with {base: 0}. Similarly, we use the code p={{ base: 0, md: 2 }} to dynamically add or remove padding based on the device.

The third important detail is the inclusion of overflow="auto". This ensures that if we have an overflow of nav items, the sidenav won't break; instead, a scrollbar will be displayed, allowing users to access the additional items. We can apply custom styles to the scrollbar using the css prop, but for now, we won't delve into that since it can be modified according to your requirements.

The second item in our grid is the main content, which occupies the remaining space. Similar to the sidenav, we use the as prop to render it as a main element for accessibility reasons, as mentioned earlier. Furthermore, we adjust the padding based on different devices.

Let’s try it out

Now we are done with the implementation. Let’s try it out in our React app. In your App.tsx, add the below code,

import { BsBarChart } from "react-icons/bs";
import { BiMap, BiChalkboard } from "react-icons/bi";
import { FiSettings, FiMenu } from "react-icons/fi";
import { Outlet } from "react-router-dom";
import {
  SidenavProvider,
  SidenavContainer,
    SidenavItem,
  Sidenav,
    useSidenav,
} from "./components/sidenav";
import { Navbar } from "./components/navbar/navbar";

export default function App() {
  const navItems: SidenavItem[] = [
    { icon: BsBarChart, label: "Dashboard", to: "" },
    { icon: BiChalkboard, label: "Forecast", to: "forecast" },
    { icon: BiMap, label: "Location", to: "location" },
    { icon: FiSettings, label: "Settings", to: "settings" }
  ];
    const { onOpen } = useSidenav();
  return (
    <SidenavProvider>
      <SidenavContainer sidenav={<Sidenav navItems={navItems} />}>
        <main>
          <div className="App">
            <h1>Hello CodeSandbox!</h1>
            <h2>Start editing to see some magic happen!</h2>
            <Outlet />
          </div>
        </main>
    <IconButton
       aria-label="menu"
       display={{ base: "flex", md: "none" }}
       onClick={onOpen}
       icon={<FiMenu />}
    />
      </SidenavContainer>
    </SidenavProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

We have created an array of navigation items to define our navigation menu. To enable navigation between these items, you will need to set up the corresponding routes. Although I won't provide the code for creating routes here, I have prepared a codesandbox with the complete implementation, which is attached at the end.

Our entire app is wrapped in the SidenavProvider, allowing us to access the SidenavContext through the useSidenav hook. To demonstrate how to use the useSidenav hook, I have utilized an <IconButton /> component, which is visible on mobile devices. When the user clicks on this button, it triggers the onOpen function from our hook, which opens the sidenav as a drawer.

Full demo

Closing Remarks

Remember, the possibilities for customization and enhancement are endless. Feel free to explore and adapt the code according to your specific requirements and design preferences. With the tools and concepts discussed in this blog, you can create intuitive navigation solutions that elevate the usability and accessibility of your web applications.

Don't forget to check out the attached codesandbox for a full implementation example. If you have any questions or need further assistance, feel free to reach out. Happy coding and may your navigation journeys be smooth and delightful!.

Inspirations:

https://chakra-ui.com/

https://material.angular.io/components/sidenav/overviewhttps://web.dev/building-a-sidenav-component/

https://web.dev/building-a-sidenav-component/

https://www.w3.org/WAI/ARIA/apg/patterns/landmarks/examples/general-principles.html

Top comments (3)

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
gauravsoni119 profile image
Gaurav Soni

Can you tell me which component is doing this? Also, it would be great if you could provide an optimized example as well🙏.

Collapse
 
emmanuelmeric profile image
Emmanuel Meric

Nvm I read too fast, I thought sidebarItemInSemiMode was used as a component.