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.
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.
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
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
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
We have a sidenav folder within our components directory. The following components need to be created:-
-
<Sidenav />
- Our sidenav which is visible by default on the large screen and convert into the drawer in mobile devices. -
<SidenavContainer />
- A container where our sidenav will be rendered along with the main content. -
<SidenavItems />
- List of all the nav items in the sidenav. -
<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";
}
Our SidenavItemsProps
has two properties:
-
navItems
: List of items of typeSidenavItem
.SidenavItem
defines the structure of the single nav item that can be passed. It hasicon
,label
andto
(route URL). -
mode
: Used to decide how nav items should be rendered in different devices.semi
mode is set when opened on large screens andover
is used when opened on mobile devices. This is an optional prop and the default value for this will besemi
.
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;
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;
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;
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 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
);
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>) };
}
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;
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;
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;
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>
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>
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;
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;
}
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;
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>
);
}
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
.
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://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)
Can you tell me which component is doing this? Also, it would be great if you could provide an optimized example as well🙏.
Nvm I read too fast, I thought sidebarItemInSemiMode was used as a component.