# How to Make the Layout Responsive Across Different Screen Sizes in refine-shadcn

> Learn how to make your refine-shadcn layout responsive across all screen sizes. Discover CSS media queries, dynamic panels, and persistent sidebar collapse for a seamless user experience.

- Repository: [Ferdi ÜNAL/refine-shadcn](https://github.com/ferdiunal/refine-shadcn)
- Tags: how-to-guide
- Published: 2026-03-01

---

**refine-shadcn implements a fully responsive admin layout by combining CSS media query detection, dynamic panel sizing from react-resizable-panels, and breakpoint-driven sidebar collapse behavior that persists user preferences in cookies.**

The `refine-shadcn` theme package provides a production-ready responsive layout system for React admin applications. By leveraging the `DefaultLayout` component in [`packages/theme/src/layouts/default.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/layouts/default.tsx), you can make the layout responsive across different screen sizes in refine-shadcn without writing custom CSS, thanks to its built-in breakpoint detection and intelligent panel management.

## Detecting Viewport Breakpoints with useMediaQuery

The foundation of the responsive system relies on the `useMediaQuery` hook to detect four distinct viewport sizes: **xs** (extra small), **sm** (small), **md** (medium), and **lg** (large). These boolean flags drive all layout decisions in the `DefaultLayout` component.

In [`packages/theme/src/layouts/default.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/layouts/default.tsx) at lines 38-46, the breakpoints are defined as follows:

```tsx
const xs = useMediaQuery("only screen and (max-width: 579.999px)");
const sm = useMediaQuery(
  "only screen and (min-width: 640px) and (max-width: 767.999px)",
);
const md = useMediaQuery(
  "only screen and (min-width: 768px) and (max-width: 1023.999px)",
);
const lg = useMediaQuery("only screen and (min-width: 1024px)");

```

These queries are re-evaluated on every resize event, providing reactive boolean values that determine panel dimensions and sidebar visibility throughout the component lifecycle.

## Calculating Dynamic Panel Sizes

The layout uses `react-resizable-panels` to create a flexible sidebar and content area. To make the layout responsive across different screen sizes in refine-shadcn, the component calculates default panel ratios based on the active breakpoint.

### Responsive Layout Arrays

The default layout is computed as a tuple `[sidebarSize, contentSize]` in lines 55-63 of [`default.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/default.tsx):

```tsx
const layout = useMemo(() => {
  if (defaultLayout) return defaultLayout;
  if (xs) return [15, 85];
  if (sm) return [20, 80];
  if (md) return [25, 75];
  return [15, 85]; // fallback (lg and up)
}, [defaultLayout, xs, sm, md]);

```

If the user has previously adjusted the panels, the `defaultLayout` prop (read from cookies) takes precedence. Otherwise, the sidebar occupies 15% on mobile, 20% on small tablets, 25% on medium screens, and falls back to 15% for large desktops.

### Sidebar Size Constraints

To maintain usability, the sidebar enforces different minimum and maximum sizes per breakpoint (lines 66-84):

```tsx
const SidebarSizes = useMemo(() => {
  if (lg) return { minSize: 11, maxSize: 15 };
  if (md) return { minSize: 15, maxSize: 25 };
  if (sm) return { minSize: 20, maxSize: 30 };
  return { minSize: 15, maxSize: 15 };
}, [sm, md, lg]);

```

This ensures the navigation panel remains proportional and draggable within sensible bounds across all devices.

## Controlling Sidebar Collapse Behavior

The **collapsible sidebar** automatically adapts to screen real estate while respecting user preferences.

### Automatic Mobile Collapse

On extra-small, small, and medium screens, the sidebar collapses automatically to maximize content space. This logic is implemented at lines 47-52 using a derived state:

```tsx
const [isCollapsed, setIsCollapsed] = useState<boolean>(xs ?? defaultCollapsed);
const hasCollapsed = useMemo(() => isCollapsed || xs || sm || md, [isCollapsed, md, sm, xs]);

```

The `hasCollapsed` boolean drives conditional rendering classes that hide navigation text and icons when space is constrained.

### User Toggle Persistence

When users manually expand or collapse the sidebar on larger screens, the `onExpand` and `onCollapse` callbacks serialize the state to cookies (lines 148-160). The `onLayout` callback similarly persists panel resize operations:

```tsx
onLayout={(sizes) => {
  document.cookie = `react-resizable-panels:layout=${JSON.stringify(sizes)}`;
}}
onExpand={() => {
  const collapsed = xs;
  setIsCollapsed(collapsed);
  document.cookie = `react-resizable-panels:collapsed=${JSON.stringify(collapsed)}`;
}}
onCollapse={() => {
  const collapsed = true;
  setIsCollapsed(collapsed);
  document.cookie = `react-resizable-panels:collapsed=${JSON.stringify(collapsed)}`;
}}

```

This cookie-based approach ensures the layout remains consistent across page reloads and browser sessions.

## Integrating with Vite and Next.js

The `DefaultLayout` component consumes these cookies through props, with different implementations for client-side and server-side rendering.

### Client-Side Vite-React Setup

In [`templates/vite-react/src/App.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/templates/vite-react/src/App.tsx), the application reads cookies using a utility like `js-cookie` and passes them to the layout component:

```tsx
import Cookie from "js-cookie";

const layout = Cookie.get("react-resizable-panels:layout");
const collapsed = Cookie.get("react-resizable-panels:collapsed");

<DefaultLayout
  defaultLayout={layout ? JSON.parse(layout) : undefined}
  defaultCollapsed={collapsed ? JSON.parse(collapsed) : false}
  navCollapsedSize={4}
>
  <Outlet />
</DefaultLayout>

```

### Server-Side Next.js Setup

For Next.js applications in [`templates/nextjs/app/layout.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/templates/nextjs/app/layout.tsx), the root layout reads cookies server-side using the `cookies` function from `next/headers`:

```tsx
import { cookies } from "next/headers";

const cookie = cookies();
const layout = cookie.get("react-resizable-panels:layout");
const collapsed = cookie.get("react-resizable-panels:collapsed");

<AppLayout
  defaultLayout={layout ? JSON.parse(layout.value) : undefined}
  defaultCollapsed={collapsed ? JSON.parse(collapsed.value) : false}
>
  {children}
</AppLayout>

```

This prevents layout shift during hydration by sending the correct initial state from the server.

## Customizing Responsive Behavior

You can extend the default responsive logic to support additional breakpoints or custom persistence mechanisms.

### Adding Custom Breakpoints

To add an **xl** breakpoint for screens wider than 1440px, extend the media queries and layout calculation in [`default.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/default.tsx):

```tsx
const xl = useMediaQuery("only screen and (min-width: 1440px)");

const layout = useMemo(() => {
  if (defaultLayout) return defaultLayout;
  if (xs) return [15, 85];
  if (sm) return [20, 80];
  if (md) return [25, 75];
  if (lg) return [30, 70];
  if (xl) return [35, 65];
  return [15, 85];
}, [defaultLayout, xs, sm, md, lg, xl]);

```

### Adjusting Collapsed Width

The collapsed sidebar width is controlled via the `navCollapsedSize` prop. Pass a different pixel value (represented as a percentage of the panel group) when instantiating the layout:

```tsx
<DefaultLayout
  defaultLayout={defaultLayout}
  defaultCollapsed={defaultCollapsed}
  navCollapsedSize={6}  // Wider collapsed sidebar (default is 4)
>
  {children}
</DefaultLayout>

```

### Using LocalStorage Instead of Cookies

To persist layout state in `localStorage` rather than cookies, replace the server-side cookie logic with client-side effects. This example from a Next.js root layout initializes state after hydration:

```tsx
import { useEffect, useState } from "react";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  const [defaultLayout, setDefaultLayout] = useState<number[]>();
  const [defaultCollapsed, setDefaultCollapsed] = useState<boolean>(false);

  useEffect(() => {
    const layout = localStorage.getItem("react-resizable-panels:layout");
    const collapsed = localStorage.getItem("react-resizable-panels:collapsed");
    setDefaultLayout(layout ? JSON.parse(layout) : undefined);
    setDefaultCollapsed(collapsed ? JSON.parse(collapsed) : false);
  }, []);

  return (
    <html lang="en">
      <body>
        <Suspense fallback={<div>Loading…</div>}>
          <AppLayout
            defaultLayout={defaultLayout}
            defaultCollapsed={defaultCollapsed}
          >
            {children}
          </AppLayout>
        </Suspense>
      </body>
    </html>
  );
}

```

## Summary

To make the layout responsive across different screen sizes in refine-shadcn, the system relies on three coordinated mechanisms:

- **Breakpoint detection** via `useMediaQuery` at lines 38-46 of [`default.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/default.tsx) provides reactive viewport flags
- **Dynamic panel sizing** using `react-resizable-panels` calculates responsive `[sidebar, content]` ratios and enforces min/max constraints per breakpoint
- **Persistent collapse state** stores user preferences in cookies, with automatic collapse on mobile devices (`xs`, `sm`, `md`)

The implementation spans the core theme package ([`packages/theme/src/layouts/default.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/layouts/default.tsx)), the UI primitives ([`packages/theme/src/ui/resizable.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/ui/resizable.tsx)), and framework-specific templates for Vite and Next.js.

## Frequently Asked Questions

### How does refine-shadcn detect screen size changes?

The `DefaultLayout` component uses the `useMediaQuery` hook to evaluate four CSS media queries corresponding to `xs`, `sm`, `md`, and `lg` breakpoints. These boolean values update automatically when the viewport crosses threshold boundaries, triggering recalculations of panel sizes and collapse states.

### Where is the responsive layout logic located in the source code?

All responsive logic resides in [`packages/theme/src/layouts/default.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/layouts/default.tsx). This file contains the breakpoint detection (lines 38-46), layout size calculations (lines 55-63), sidebar constraint definitions (lines 66-84), and collapse state management (lines 47-52). The underlying resizable panel wrapper is defined in [`packages/theme/src/ui/resizable.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/ui/resizable.tsx).

### Can I use localStorage instead of cookies for layout persistence?

Yes, though it requires client-side initialization. Replace the cookie reading logic in your application entry point (e.g., [`App.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/App.tsx) or [`layout.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/layout.tsx)) with `useEffect` hooks that read from `localStorage.getItem()`, then pass the deserialized values to `DefaultLayout` via the `defaultLayout` and `defaultCollapsed` props.

### How do I add a new breakpoint for extra-large screens?

Define a new media query using `useMediaQuery` (e.g., `const xl = useMediaQuery("only screen and (min-width: 1440px)")`), then include the `xl` variable in the dependency arrays of the `layout` and `SidebarSizes` `useMemo` hooks. Add conditional returns for the new breakpoint to specify custom panel percentages and size constraints.