<ThemedLayout />
<ThemedLayoutV2>
component that uses the <Drawer>
from Chakra UI library to define the layout and structure of a web page. It includes customizable components for the header, sidebar, title, footer, and off-layout area, which can be replaced or customized as needed.
By using <ThemedLayoutV2>
, developers can create a consistent look and feel across multiple pages or sections of a website, while also improving code maintainability and reusability. The customizable sections of <ThemedLayoutV2>
include:
<ThemedHeader>
: displayed at the top of the page and can display the user's name and avatar.<ThemedSider>
: displayed on the left side of the page and can display menu items.<ThemedTitleV2>
: displayed at the top of<ThemedSider>
and includes an icon and text.<Footer>
: displayed at the bottom of the page.<OffLayoutArea>
: rendered outside of the main layout component and can be placed anywhere on the page while still being part of the overall layout.
Footer
and OffLayoutArea
do not have any default components.
Usage
Example above shows how to use <ThemedLayoutV2>
with React Router
. You can see these examples for other routers:
⚠️ Next.js and Remix examples are using
<ThemedLayoutV2
> from@refinedev/antd
package. But you can use<ThemedLayoutV2>
from@refinedev/chakra-ui
as same.
<ThemedLayoutV2>
is designed to be responsive. In the live-preview, it appears in tablet mode and toggle <Drawer>
. On larger screens, it will use fixed open <Drawer>
.
Props
Sider
In <ThemedLayoutV2>
, the sidebar section is rendered using the <ThemedSider>
component by default. This component is specifically designed to generate menu items based on the resources defined in the <Refine>
components, using the useMenu
hook. However, if desired, it's possible to replace the default <ThemedSider>
component by passing a custom component to the Sider
prop.
import { Refine } from "@refinedev/core";
import { ThemedLayoutV2 } from "@refinedev/chakra-ui";
import { CustomSider } from "./CustomSider";
const App: React.FC = () => {
return (
<Refine
// ...
>
<ThemedLayoutV2
Sider={() => <CustomSider />}
>
{/* ... */}
</ThemedLayoutV2>
</Refine>
);
};
Also, you can customize the default <ThemedSider>
component either by using its props or with the swizzle feature.
Here is an example of how to customize the default <ThemedSider>
component using the render
and Title
prop:
import { Refine } from "@refinedev/core";
import { ThemedLayoutV2, ThemedSider } from "@refinedev/chakra-ui";
import { CustomTitle } from "./CustomTitle";
const App: React.FC = () => {
return (
<Refine
// ...
>
<ThemedLayoutV2
Sider={() => (
<ThemedSider
Title={({ collapsed }) => <CustomTitle collapsed={collapsed} />}
render={({ items, logout, collapsed }) => {
return (
<>
<div>My Custom Element</div>
{items}
{logout}
</>
);
}}
/>
)}
>
{/* ... */}
</ThemedLayoutV2>
</Refine>
);
};
Sider Props
Prop | Type | Description |
---|---|---|
Title | React.FC | Component to render at the top |
render | SiderRenderFunction | Function to render the menu items and other elements inside the <ThemedSider> |
meta | Record<string,any> | Meta data to use when creating routes for the menu items |
activeItemDisabled | boolean | Whether clicking on an active sider item should reload the page |
type SiderRenderFunction = (props: {
items: JSX.Element[];
logout: React.ReactNode;
dashboard: React.ReactNode;
collapsed: boolean;
}) => React.ReactNode;
initialSiderCollapsed
This prop is used to set the initial collapsed state of the <ThemedSiderV2>
component.
true
: The<ThemedSiderV2>
component will be collapsed by default.false
: The<ThemedSiderV2>
component will be expanded by default.
<ThemedLayoutV2
initialSiderCollapsed={true}
>
{/* ... */}
</ThemedLayoutV2>
onSiderCollapsed
Will be triggered when the <ThemedSiderV2>
component's collapsed
state changes.
Can be used to persist collapsed state on the localstorage. Then you can use localStorage item to decide if sider should be collapsed initially or not.
Here's an example of how to use the onSiderCollapsed
prop:
const MyLayout = () => {
const onSiderCollapse = (collapsed: boolean) => {
localStorage.setItem("siderCollapsed", collapsed);
};
const initialSiderCollapsed = Boolean(localStorage.getItem("siderCollapsed"));
return (
<ThemedLayoutV2
initialSiderCollapsed={initialSiderCollapsed}
onSiderCollapsed={onSiderCollapse}
>
{/* ... */}
</ThemedLayoutV2>
);
};
Header
In <ThemedLayoutV2>
, the header section is rendered using the <ThemedHeader>
component by default. It uses the useGetIdentity
hook to display the user's name and avatar on the right side of the header. However, if desired, it's possible to replace the default <ThemedHeader>
component by passing a custom component to the Header
prop.
Here is an example of how to replace the default <ThemedHeader>
component:
import { Refine } from "@refinedev/core";
import { ThemedLayoutV2 } from "@refinedev/chakra-ui";
import { CustomHeader } from "./CustomHeader";
const App: React.FC = () => {
return (
<Refine
// ...
>
<ThemedLayoutV2
Header={() => <CustomHeader />}
>
{/* ... */}
</ThemedLayoutV2>
</Refine>
);
};
You can also make it sticky using the sticky
property, which is optional and defaults to false
:
import { Refine } from "@refinedev/core";
import {
ThemedLayoutV2,
ThemedHeaderV2,
} from "@refinedev/chakra-ui";
const App: React.FC = () => {
return (
<Refine
// ...
>
<ThemedLayoutV2
Header={() => <ThemedHeaderV2 sticky />}
>
{/* ... */}
</ThemedLayoutV2>
</Refine>
);
};
Title
In <ThemedLayoutV2>
, the title section is rendered using the <ThemedTitleV2>
component by default. However, if desired, it's possible to replace the default <ThemedTitleV2>
component by passing a custom component to the Title
prop.
Here is an example of how to replace the default <ThemedTitleV2>
component:
import { Refine } from "@refinedev/core";
import { ThemedLayoutV2, ThemedTitleV2 } from "@refinedev/chakra-ui";
import { MyLargeIcon, MySmallIcon } from "./MyIcon";
const App: React.FC = () => {
return (
<Refine
// ...
>
<ThemedLayoutV2
Title={({ collapsed }) => (
<ThemedTitleV2
// collapsed is a boolean value that indicates whether the <Sidebar> is collapsed or not
collapsed={collapsed}
icon={collapsed ? <MySmallIcon /> : <MyLargeIcon />}
text="My Project"
/>
)}
>
{/* ... */}
</ThemedLayoutV2>
</Refine>
);
};
Footer
The footer section of the layout is displayed at the bottom of the page. Refine doesn't provide a default footer component. However, you can pass a custom component to the Footer
prop to display a footer section.
Here is an example of how to display a footer section:
import { Refine } from "@refinedev/core";
import { ThemedLayoutV2 } from "@refinedev/chakra-ui";
import { Flex } from "@chakra-ui/react";
const App: React.FC = () => {
return (
<Refine
// ...
>
<ThemedLayoutV2
Footer={() => (
<Flex
justifyContent="center"
alignItems="center"
bg="teal.500"
h="64px"
>
My Custom Footer
</Flex>
)}
>
{/* ... */}
</ThemedLayoutV2>
</Refine>
);
};
OffLayoutArea
off-layout area component is rendered outside of the main layout component, allowing it to be placed anywhere on the page while still being part of the overall layout .Refine doesn't provide a default off-layout area component. However, you can pass a custom component to the OffLayoutArea
prop to display a custom off-layout area.
Here is an example of how to display a custom off-layout area:
import { Refine } from "@refinedev/core";
import { ThemedLayoutV2 } from "@refinedev/chakra-ui";
import { Button } from "@chakra-ui/react";
const App: React.FC = () => {
return (
<Refine
// ...
>
<ThemedLayoutV2
OffLayoutArea={() => (
<Button
onClick={() => alert("Off layout are clicked")}
colorScheme="brand"
size="sm"
sx={{
position: "fixed",
left: "8px",
bottom: "8px",
zIndex: 1000,
}}
>
Send us Feedback 👋
</Button>
)}
>
{/* ... */}
</ThemedLayoutV2>
</Refine>
);
};
Customizing with swizzle
This feature is available with
@refine/cli
. Please refer to CLI documentation for more information.
<ThemedLayoutV2>
component source code can be ejected using the swizzle
command. This will create a copy of the component in your project's src
directory, allowing you to customize as your needs.
Usage
Let's create a new component by swizzling the <ThemedLayoutV2>
components.
> npm run refine swizzle
? Which package do you want to swizzle? (Use arrow keys or type to search)
Data Provider
◯ @refinedev/simple-rest
UI Framework
◉ @refinedev/chakra-ui
First, you need to select the package you want to swizzle. In this example, we will swizzle the @refinedev/chakra-ui
package.
Refine CLI will only show the packages that are installed in your project.
? Which component do you want to swizzle?
◯ TagField
◯ TextField
◯ UrlField
Other
◯ Breadcrumb
❯◉ ThemedLayoutV2
Pages
◯ ErrorPage
◯ AuthPage
(Move up and down to reveal more choices)
Then, you need to select the component you want to swizzle. In this example, we will swizzle the ThemedLayoutV2
component.
Successfully swizzled Themed Layout
Files created:
- src/components/themedLayout/sider.tsx
- src/components/themedLayout/header.tsx
- src/components/themedLayout/title.tsx
- src/components/themedLayout/index.tsx
Warning:
If you want to change the default layout;
You should pass layout related components to the <ThemedLayoutV2/> component's props.
╭ App.tsx ───────────────────────────────────────────────────────────────────────────────────────╮
│ │
│ import { ThemedLayoutV2 } from "components/themedLayout"; │
│ import { ThemedHeaderV2 } from "components/themedLayout/header"; │
│ import { ThemedSiderV2 } from "components/themedLayout/sider"; │
│ import { ThemedTitleV2 } from "components/themedLayout/title"; │
│ │
│ const App = () => { │
│ return ( │
│ <Refine │
│ /* ... */ │
│ > │
│ <ThemedLayoutV2 │
│ Header={ThemedHeaderV2} │
│ Sider={ThemedSiderV2} │
│ Title={ThemedTitleV2} │
│ /> │
│ /* ... */ │
│ </ThemedLayout> │
│ </Refine> │
│ ); │
│ } │
│ │
╰────────────────────────────────────────────────────────────────────────────────────────────────╯
Finally, the swizzle command will create a new folder in the src/components/layout
directory and generate the layout components of the @refinedev/chakra-ui
package in it.
You can use these components in your project as you wish.
import { Refine } from "@refinedev/core";
import { ThemedLayoutV2 } from "components/themedLayout";
import { ThemedHeader } from "components/themedLayout/header";
import { ThemedSider } from "components/themedLayout/sider";
import { ThemedTitle } from "components/themedLayout/title";
const App = () => {
return (
<Refine
/* ... */
>
<ThemedLayoutV2
Header={ThemedHeader}
Sider={ThemedSider}
Title={ThemedTitle}
>
/* ... */
</ThemedLayoutV2>
</Refine>
);
};
Refine CLI determines the path to create a new folder according to the framework you are using. For example, if you are using the remix
, the path will be app/components/layout
.
If there is already a file with the same name in the directory, the swizzle command will not overwrite it.
Migrate ThemedLayout to ThemedLayoutV2
Fixed some UI problems with ThemedLayoutV2
. If you are still using ThemedLayout
you can update it by following these steps.
Only if you are using ThemedLayout
. If you are not customizing the Header
component, an update like the one below will suffice.
-import { ThemedLayout } from "@refinedev/chakra-ui";
+import { ThemedLayoutV2 } from "@refinedev/chakra-ui";
...
-<ThemedLayout>
+<ThemedLayoutV2>
<Outlet />
-</ThemedLayout>
+</ThemedLayoutV2>
...
But mostly we customize the Header
component. For this, an update like the one below will suffice. Here, a HamburgerMenu
should be added to the Header
component that we have customized for the collapse/uncollapse of the Sider
component.
-import { RefineThemedLayoutHeaderProps } from "@refinedev/chakra-ui";
+import { RefineThemedLayoutV2HeaderProps, HamburgerMenu } from "@refinedev/chakra-ui";
-export const Header: React.FC<RefineThemedLayoutHeaderProps> = ({
- isSiderOpen,
- onToggleSiderClick,
- toggleSiderIcon: toggleSiderIconFromProps,
-}) => {
+export const Header: React.FC<RefineThemedLayoutV2HeaderProps> = () => {
return (
<Box
py="2"
pr="4"
pl="2"
display="flex"
alignItems="center"
- justifyContent={
- hasSidebarToggle
- ? { base: "flex-end", md: "space-between" }
- : "flex-end"
- }
+ justifyContent="space-between"
w="full"
height="64px"
bg={bgColor}
borderBottom="1px"
borderBottomColor={useColorModeValue("gray.200", "gray.700")}
>
- {hasSidebarToggle && (
- <IconButton
- display={{ base: "none", md: "flex" }}
- backgroundColor="transparent"
- aria-label="sidebar-toggle"
- onClick={() => onToggleSiderClick?.()}
- >
- {toggleSiderIconFromProps?.(Boolean(isSiderOpen)) ??
- (isSiderOpen ? (
- <Icon as={IconLayoutSidebarLeftCollapse} boxSize={"24px"} />
- ) : (
- <Icon as={IconLayoutSidebarLeftExpand} boxSize={"24px"} />
- ))}
- </IconButton>
- )}
+ <HamburgerMenu />
<HStack>
<IconButton
variant="ghost"
aria-label="Toggle theme"
onClick={toggleColorMode}
>
<Icon
as={colorMode === "light" ? IconMoon : IconSun}
w="24px"
h="24px"
/>
</IconButton>
{(user?.avatar || user?.name) && (
<HStack>
{user?.name && (
<Text size="sm" fontWeight="bold">
{user.name}
</Text>
)}
<Avatar size="sm" name={user?.name} src={user?.avatar} />
</HStack>
)}
</HStack>
</Box>
);
};
Hamburger Menu
The HamburgerMenu
component is a component that is used to collapse/uncollapse the Sider
component. It is used by default in the Header
component. However, you can do this anywhere you want using the <HamburgerMenu />
component. Below you can see an example put on the dashboard page.
FAQ
How can I persist the collapsed state of the <ThemedSiderV2>
component?
You can use initialSiderCollapsed
prop to persist the collapsed state of the <ThemedSiderV2>
component.
For example, you can get initialSiderCollapsed
's value from localStorage
or cookie
for persistence between sessions.
- React Router
- Next.js
- Remix
import { useState } from "react";
import { Refine } from "@refinedev/core";
import { BrowserRouter, Routes, Route, Outlet } from "react-router";
import { ThemedLayoutV2 } from "@refinedev/chakra-ui";
const App: React.FC = () => {
// you can get this value from `localStorage` or `cookie`
// for persistence between sessions
const [initialSiderCollapsed, setInitialSiderCollapsed] = useState(true);
return (
<BrowserRouter>
<Refine
// ...
>
{/* ... */}
<Routes>
<Route
element={
<ThemedLayoutV2 initialSiderCollapsed={initialSiderCollapsed}>
<Outlet />
</ThemedLayoutV2>
}
>
{/* ... */}
</Route>
</Routes>
</Refine>
</BrowserRouter>
);
};
export default App;
import { useState } from "react";
import { Refine } from "@refinedev/core";
import { ThemedLayoutV2 } from "@refinedev/chakra-ui";
import type { AppProps } from "next/app";
import type { NextPage } from "next";
function MyApp({ Component, pageProps }: AppProps): JSX.Element {
// you can get this value from `localStorage` or `cookie`
// for persistence between sessions
const [initialSiderCollapsed, setInitialSiderCollapsed] = useState(true);
const renderComponent = () => {
if (Component.noLayout) {
return <Component {...pageProps} />;
}
return (
<ThemedLayoutV2 initialSiderCollapsed={initialSiderCollapsed}>
<Component {...pageProps} />
</ThemedLayoutV2>
);
};
return (
<Refine
// ...
>
{/* ... */}
{renderComponent()}
</Refine>
);
}
export default MyApp;
import { useState } from "react";
import { Outlet } from "@remix-run/react";
import { ThemedLayoutV2 } from "@refinedev/chakra-ui";
export default function BaseLayout() {
// you can get this value from `localStorage` or `cookie`
// for persistence between sessions
const [initialSiderCollapsed, setInitialSiderCollapsed] = useState(true);
return (
<ThemedLayoutV2 initialSiderCollapsed={initialSiderCollapsed}>
<Outlet />
</ThemedLayoutV2>
);
}