How to Create Custom Layouts by Extending BaseLayout in refine-shadcn
Extend the BaseLayout component from @ferdiunal/refine-shadcn to create custom layouts that inherit global theme support, tooltips, and toast notifications while wrapping your own headers, sidebars, or navigation drawers.
The refine-shadcn library provides a minimal, composable BaseLayout component located at packages/theme/src/layouts/base.tsx. This component deliberately handles only essential global providers—ThemeProvider from next-themes, TooltipProvider, and the Toaster notification system—freeing you to build any UI structure around the {children} content without reimplementing theme logic.
BaseLayout Architecture and Type System
The Core Component
In packages/theme/src/layouts/base.tsx, the BaseLayout component accepts props extending ThemeProvider options plus a children slot. It renders the global UI infrastructure that every page needs:
export const BaseLayout = ({
attribute,
defaultTheme,
enableSystem,
// … other ThemeProvider props
children,
}: Props) => (
<ThemeProvider …>
<TooltipProvider …>
{children}
<Toaster />
</TooltipProvider>
</ThemeProvider>
);
Because BaseLayout injects {children} directly inside the TooltipProvider, any custom layout you build will automatically support tooltips and theme switching while maintaining access to toast notifications via the <Toaster /> element rendered as a sibling.
LayoutProps Type Definitions
The contract for valid layout props is defined in packages/theme/src/types/layout.d.ts. The LayoutProps interface extends ThemeProvider properties and adds UI-specific fields such as logo, navbar, and footer, ensuring your custom layouts remain type-safe when accepting theme or branding configurations.
DefaultLayout Reference Implementation
For a complete example of composition, examine packages/theme/src/layouts/default.tsx. This file demonstrates how to wrap BaseLayout with a responsive sidebar, header bar, and content area while forwarding all theme props unchanged.
Creating a Minimal Header Layout
To create a simple layout that adds a fixed header above all page content, import BaseLayout and the LayoutProps type, then compose your UI inside the component’s children:
// src/layouts/MyHeaderLayout.tsx
import { BaseLayout } from "@ferdiunal/refine-shadcn";
import { LayoutProps } from "@ferdiunal/refine-shadcn/dist/types/layout";
import { Header } from "@/components/header";
export const MyHeaderLayout = ({
attribute,
defaultTheme,
enableSystem,
children,
}: LayoutProps) => {
return (
<BaseLayout
attribute={attribute}
defaultTheme={defaultTheme}
enableSystem={enableSystem}
>
<Header />
{children}
</BaseLayout>
);
};
MyHeaderLayout.displayName = "MyHeaderLayout";
All theme-related props are passed through to BaseLayout, ensuring the ThemeProvider and Toaster remain active. The <Header /> component renders above the page content while the toast container persists globally.
Building a Full-Featured Drawer Layout
For complex layouts requiring responsive navigation, you can replicate the logic from DefaultLayout but substitute components. The following example replaces the sidebar with a mobile drawer while keeping the desktop header:
// src/layouts/CustomDrawerLayout.tsx
import { BaseLayout } from "@ferdiunal/refine-shadcn";
import type { LayoutProps, LogoType } from "@ferdiunal/refine-shadcn";
import { useState, useMemo, cloneElement, isValidElement } from "react";
import { Drawer, DrawerTrigger, DrawerContent } from "@/components/drawer";
import { Link } from "@/components/link";
import { ModeToggle } from "@/components/mode-toggle";
import { useResource } from "@refinedev/core";
import { useMediaQuery } from "@react-hook/media-query";
export const CustomDrawerLayout = ({
children,
logo,
navbar,
footer,
attribute,
defaultTheme,
enableSystem,
}: LayoutProps) => {
const { resources } = useResource();
const firstDashboard = resources?.[0];
const isMobile = useMediaQuery("only screen and (max-width: 639px)");
const [drawerOpen, setDrawerOpen] = useState(false);
const Logo: LogoType | undefined = useMemo(() => {
if (!logo) return null;
const component = isMobile ? logo.collapsed : logo.default;
return isValidElement(component)
? cloneElement(component, { className: "w-auto h-8" })
: null;
}, [logo, isMobile]);
return (
<BaseLayout
attribute={attribute}
defaultTheme={defaultTheme}
enableSystem={enableSystem}
>
{isMobile && (
<Drawer open={drawerOpen} onOpenChange={setDrawerOpen}>
<DrawerTrigger asChild>
<button className="p-2">☰</button>
</DrawerTrigger>
<DrawerContent>
<nav className="flex flex-col space-y-2 p-4">
<Link href={firstDashboard?.list?.toString() ?? "/"}>{Logo}</Link>
</nav>
</DrawerContent>
</Drawer>
)}
{!isMobile && (
<header className="flex h-14 items-center justify-between border-b p-4">
<Link href={firstDashboard?.list?.toString() ?? "/"}>{Logo}</Link>
<div className="flex items-center space-x-2">
<ModeToggle />
{navbar?.rightSide}
</div>
</header>
)}
<main className="px-6 py-4">{children}</main>
{footer && <footer className="border-t p-4">{footer}</footer>}
</BaseLayout>
);
};
CustomDrawerLayout.displayName = "CustomDrawerLayout";
This implementation reuses the logo handling logic from DefaultLayout via the useMemo pattern, conditionally renders a Drawer for mobile viewports, and preserves all theme forwarding to BaseLayout.
Using Your Custom Layout in an Application
Once defined, import your layout component into your application entry point and supply the theme configuration:
// src/App.tsx
import { CustomDrawerLayout } from "@/layouts/CustomDrawerLayout";
function App() {
return (
<CustomDrawerLayout
attribute="data-theme"
defaultTheme="system"
enableSystem
>
<MyDashboard />
</CustomDrawerLayout>
);
}
Passing attribute, defaultTheme, and enableSystem ensures the next-themes provider inside BaseLayout initializes correctly for your application.
Summary
- BaseLayout in
packages/theme/src/layouts/base.tsxprovides the essentialThemeProvider,TooltipProvider, andToasterwrappers required by every refine-shadcn application. - LayoutProps in
packages/theme/src/types/layout.d.tsdefines the complete type contract, extendingThemeProviderprops with UI-specific fields likelogoandnavbar. - To create custom layouts, import
BaseLayout, acceptLayoutProps(or a subset), wrap your custom UI around{children}, and forward theme props unchanged. - Reference
packages/theme/src/layouts/default.tsxfor a production-ready example combining sidebars, headers, and responsive behavior.
Frequently Asked Questions
What props does BaseLayout accept?
BaseLayout accepts all props defined in LayoutProps, which extend ThemeProvider options from next-themes (such as attribute, defaultTheme, and enableSystem) plus React children. According to the source in packages/theme/src/layouts/base.tsx, any additional ThemeProvider props are destructured and passed directly to the provider.
Can I use BaseLayout without a sidebar?
Yes. BaseLayout is intentionally minimal and does not enforce any navigation structure. You can wrap it around a simple header, a blank canvas, or a complex dashboard grid. The DefaultLayout in packages/theme/src/layouts/default.tsx provides one example with a sidebar, but you are not required to include one.
How do I access theme settings inside my custom layout?
Because BaseLayout renders ThemeProvider from next-themes, you can use the useTheme hook from that library anywhere inside your layout’s component tree. Your custom layout component receives theme props (like attribute and defaultTheme) via LayoutProps, which you must forward to BaseLayout to initialize the provider.
Where is the DefaultLayout defined?
The reference implementation is located at packages/theme/src/layouts/default.tsx in the ferdiunal/refine-shadcn repository. This file demonstrates how to compose BaseLayout with a responsive sidebar, header, and content area while handling logo states and resource navigation.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →