Responsive Resizable Preview
A responsive and resizable component for previewing content across different device breakpoints with drag-to-resize functionality.
A powerful component for previewing your responsive designs across different screen sizes with built-in resizable panels, breakpoint indicators, and testing tools.
Demo
Basic Usage
Width: 0px(NaN%)
0px0
0pxmax
"use client";
import { RRChildPreview } from "@/components/domain-ui/responsive-resizable-preview";
export default function BasicDemo() {
return (
<RRChildPreview>
<div className="@container">
<div className="flex min-h-[300px] flex-col items-center justify-center @2xl:bg-cyan-200 @2xs:bg-slate-100 @3xl:bg-blue-200 @3xs:bg-gray-100 @4xl:bg-indigo-200 @5xl:bg-purple-200 @6xl:bg-pink-200 @7xl:bg-rose-200 @lg:bg-green-200 @md:bg-yellow-200 @sm:bg-orange-200 @xl:bg-teal-200 @xs:bg-red-200 bg-gray-50 p-4 @2xl:dark:bg-cyan-300 @2xs:dark:bg-slate-700 @3xl:dark:bg-blue-300 @3xs:dark:bg-gray-700 @4xl:dark:bg-indigo-300 @5xl:dark:bg-purple-300 @6xl:dark:bg-pink-300 @7xl:dark:bg-rose-300 @lg:dark:bg-green-300 @md:dark:bg-yellow-300 @sm:dark:bg-orange-300 @xl:dark:bg-teal-300 @xs:dark:bg-red-300 dark:bg-gray-800">
<h3 className="font-bold @2xl:text-6xl @2xs:text-lg @3xl:text-7xl @3xs:text-base @4xl:text-8xl @5xl:text-9xl @lg:text-4xl @md:text-3xl @sm:text-2xl @xl:text-5xl @xs:text-xl text-sm">
Basic Preview
</h3>
<p className="mt-4 text-center text-sm @sm:text-base @md:text-lg font-mono">
Current size:
<span className="font-semibold ml-1">
<span className="@3xs:hidden">Base (< 256px)</span>
<span className="hidden @3xs:inline @2xs:hidden">3XS (256px+)</span>
<span className="hidden @2xs:inline @xs:hidden">2XS (288px+)</span>
<span className="hidden @xs:inline @sm:hidden">XS (320px+)</span>
<span className="hidden @sm:inline @md:hidden">SM (384px+)</span>
<span className="hidden @md:inline @lg:hidden">MD (448px+)</span>
<span className="hidden @lg:inline @xl:hidden">LG (512px+)</span>
<span className="hidden @xl:inline @2xl:hidden">XL (576px+)</span>
<span className="hidden @2xl:inline @3xl:hidden">2XL (672px+)</span>
<span className="hidden @3xl:inline @4xl:hidden">3XL (768px+)</span>
<span className="hidden @4xl:inline @5xl:hidden">4XL (896px+)</span>
<span className="hidden @5xl:inline @6xl:hidden">5XL (1024px+)</span>
<span className="hidden @6xl:inline @7xl:hidden">6XL (1152px+)</span>
<span className="hidden @7xl:inline">7XL (1280px+)</span>
</span>
</p>
</div>
</div>
</RRChildPreview>
);
}
Minimal Configuration
"use client";
import { RRChildPreview } from "@/components/domain-ui/responsive-resizable-preview";
const people = [
{
name: "Leslie Alexander",
email: "leslie.alexander@example.com",
role: "Co-Founder / CEO",
imageUrl:
"https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80",
lastSeen: "3h ago",
lastSeenDateTime: "2023-01-23T13:23Z",
},
{
name: "Michael Foster",
email: "michael.foster@example.com",
role: "Co-Founder / CTO",
imageUrl:
"https://images.unsplash.com/photo-1519244703995-f4e0f30006d5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80",
lastSeen: "3h ago",
lastSeenDateTime: "2023-01-23T13:23Z",
},
{
name: "Dries Vincent",
email: "dries.vincent@example.com",
role: "Business Relations",
imageUrl:
"https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80",
lastSeen: null,
},
{
name: "Lindsay Walton",
email: "lindsay.walton@example.com",
role: "Front-end Developer",
imageUrl:
"https://images.unsplash.com/photo-1517841905240-472988babdf9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80",
lastSeen: "3h ago",
lastSeenDateTime: "2023-01-23T13:23Z",
},
{
name: "Courtney Henry",
email: "courtney.henry@example.com",
role: "Designer",
imageUrl:
"https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80",
lastSeen: "3h ago",
lastSeenDateTime: "2023-01-23T13:23Z",
},
{
name: "Tom Cook",
email: "tom.cook@example.com",
role: "Director of Product",
imageUrl:
"https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80",
lastSeen: null,
},
];
const StackCard = () => {
return (
<div className="@container bg-white px-4 py-12 sm:px-6 lg:px-8 dark:bg-gray-900">
<div className="mx-auto max-w-4xl">
<ul
role="list"
className="divide-y divide-gray-100 dark:divide-gray-800"
>
{people.map((person) => (
<li
key={person.email}
className="flex justify-between gap-x-6 py-5"
>
<div className="flex min-w-0 gap-x-4">
<img
alt=""
src={person.imageUrl}
className="size-12 flex-none rounded-full bg-gray-50 dark:bg-gray-800"
/>
<div className="min-w-0 flex-auto">
<p className="font-semibold text-gray-900 text-sm/6 dark:text-white">
{person.name}
</p>
<p className="mt-1 truncate text-gray-500 text-xs/5 dark:text-gray-400">
{person.email}
</p>
</div>
</div>
<div className="@sm:flex hidden shrink-0 @sm:flex-col @sm:items-end">
<p className="text-gray-900 text-sm/6 dark:text-white">
{person.role}
</p>
{person.lastSeen ? (
<p className="mt-1 text-gray-500 text-xs/5 dark:text-gray-400">
Last seen{" "}
<time dateTime={person.lastSeenDateTime}>
{person.lastSeen}
</time>
</p>
) : (
<div className="mt-1 flex items-center gap-x-1.5">
<div className="flex-none rounded-full bg-emerald-500/20 p-1">
<div className="size-1.5 rounded-full bg-emerald-500" />
</div>
<p className="text-gray-500 text-xs/5 dark:text-gray-400">
Online
</p>
</div>
)}
</div>
</li>
))}
</ul>
</div>
</div>
);
};
export default function MinimalDemo() {
return (
<RRChildPreview
config={{
showToolbar: false,
showScale: false,
showLabels: false,
}}
>
<StackCard />
</RRChildPreview>
);
}
Container Queries
Width: 0px(NaN%)
0px0
0pxmax
"use client";
import { RRChildPreview } from "@/components/domain-ui/responsive-resizable-preview";
const ContainerQueryCard = () => {
return (
<div className="@container">
<div className="flex flex-col @3xs:bg-gray-100 @2xs:bg-slate-100 @xs:bg-red-200 @sm:bg-orange-200 @md:bg-yellow-200 @lg:bg-green-200 @xl:bg-teal-200 @2xl:bg-cyan-200 @3xl:bg-blue-200 @4xl:bg-indigo-200 @5xl:bg-purple-200 @6xl:bg-pink-200 @7xl:bg-rose-200 bg-gray-50 p-4 @3xs:dark:bg-gray-700 @2xs:dark:bg-slate-700 @xs:dark:bg-red-300 @sm:dark:bg-orange-300 @md:dark:bg-yellow-300 @lg:dark:bg-green-300 @xl:dark:bg-teal-300 @2xl:dark:bg-cyan-300 @3xl:dark:bg-blue-300 @4xl:dark:bg-indigo-300 @5xl:dark:bg-purple-300 @6xl:dark:bg-pink-300 @7xl:dark:bg-rose-300 dark:bg-gray-800">
<div className="flex @xs:flex-row flex-col @xs:items-center @xs:justify-between @2xl:gap-4 gap-2">
<div className="h-8 w-2/3 rounded bg-gray-500 font-bold @lg:text-3xl @md:text-2xl @sm:text-xl @xl:text-4xl @xs:text-lg text-base dark:bg-gray-600" />
<div className="h-6 w-1/4 rounded bg-gray-300 @lg:text-lg @md:text-base @sm:text-sm text-gray-600 text-xs dark:bg-gray-700 dark:text-gray-400" />
</div>
<div className="mt-4 grid @3xl:grid-cols-6 @5xl:grid-cols-8 @md:grid-cols-3 @xl:grid-cols-4 @xs:grid-cols-2 grid-cols-1 gap-4">
<StatCard />
<StatCard />
<StatCard />
<StatCard />
<StatCard />
<StatCard />
<StatCard />
<StatCard />
</div>
<div className="mt-6 @4xl:block hidden">
<div className="grid @4xl:grid-cols-2 @5xl:grid-cols-3 @6xl:grid-cols-4 gap-4">
{Array.from({ length: 4 }, (_, i) => (
<div
key={`card-${i + 1}`}
className="rounded bg-white/50 p-4 dark:bg-black/50"
>
<div className="mb-2 h-6 w-full rounded bg-gray-500 font-semibold dark:bg-gray-600" />
<div className="h-6 w-1/4 rounded bg-gray-300 @lg:text-lg @md:text-base @sm:text-sm text-gray-600 text-xs dark:bg-gray-700 dark:text-gray-400" />
</div>
))}
</div>
</div>
</div>
</div>
);
};
const StatCard = () => (
<div className="rounded bg-white/50 @4xl:p-6 @lg:p-5 @xs:p-4 p-3 transition-all duration-200 dark:bg-black/50">
<div className="flex items-center @2xl:gap-3 gap-2">
<div className="@sm:min-h-5 @xl:min-h-6 min-h-4 @sm:min-w-5 @xl:min-w-6 min-w-4 bg-gray-200 dark:bg-gray-800" />
<div className="h-3 w-48 rounded bg-gray-500 font-medium @sm:text-sm @xl:text-base text-xs dark:bg-gray-600" />
</div>
<div className="mt-2 h-12 w-full bg-gray-800 font-bold @4xl:text-3xl @sm:text-xl @xl:text-2xl text-lg dark:bg-gray-950" />
</div>
);
export default function ContainerQueryDemo() {
return (
<RRChildPreview>
<ContainerQueryCard />
</RRChildPreview>
);
}
IFrame Preview
Width: 0px(NaN%)
0px0
0pxmax
"use client";
import { RRIFramePreview } from "@/components/domain-ui/responsive-resizable-preview";
export default function IFrameDemo() {
return (
<RRIFramePreview
srcUrl="/demo/responsive-resizable-preview"
height={500}
config={{
darkMode: false,
showToolbar: true,
showScale: true,
showLabels: true,
}}
/>
);
}
Installation
CLI
pnpm dlx shadcn@latest add https://domain-ui.dev/r/responsive-resizable-preview.json
npx shadcn@latest add https://domain-ui.dev/r/responsive-resizable-preview.json
npx shadcn@latest add https://domain-ui.dev/r/responsive-resizable-preview.json
bunx --bun shadcn@latest add https://domain-ui.dev/r/responsive-resizable-preview.json
Manual
Copy and paste the following code into your project:
Loading registry files...
Usage
Basic Usage
import { RRChildPreview } from "@/components/ui/responsive-resizable-preview";
export default function MyComponent() {
return (
<RRChildPreview>
<div className="p-4">
<h2 className="text-xl font-bold">My Responsive Component</h2>
<p>This will be previewed across different breakpoints.</p>
</div>
</RRChildPreview>
);
}
With Configuration
import { RRChildPreview } from "@/components/ui/responsive-resizable-preview";
export default function ConfiguredPreview() {
return (
<RRChildPreview
config={{
darkMode: false,
showToolbar: true,
showScale: true,
showLabels: true,
}}
>
<YourComponent />
</RRChildPreview>
);
}
IFrame Preview
import { RRIFramePreview } from "@/components/ui/responsive-resizable-preview";
export default function IFrameExample() {
return (
<RRIFramePreview
srcUrl="/your-page"
height={600}
config={{
showToolbar: true,
showScale: true,
showLabels: true,
}}
/>
);
}
API Reference
RRChildPreview
Renders child components directly in the resizable preview.
Prop | Type | Default | Description |
---|---|---|---|
children | ReactNode | - | The content to preview |
config | PreviewConfig | defaultConfig | Configuration options |
breakpoints | BreakpointConfig[] | defaultBreakpoints | Custom breakpoint definitions |
className | string | - | Additional CSS classes |
RRIFramePreview
Renders content in an iframe for isolated preview.
Prop | Type | Default | Description |
---|---|---|---|
srcUrl | string | - | Required. URL to load in iframe |
height | number | 930 | Height of the iframe in pixels |
config | PreviewConfig | defaultConfig | Configuration options |
breakpoints | BreakpointConfig[] | defaultBreakpoints | Custom breakpoint definitions |
className | string | - | Additional CSS classes |
ResponsiveResizablePreview
The base preview component that others extend.
Prop | Type | Default | Description |
---|---|---|---|
children | ReactNode | - | The content to preview |
config | PreviewConfig | defaultConfig | Configuration options |
breakpoints | BreakpointConfig[] | defaultBreakpoints | Custom breakpoint definitions |
className | string | - | Additional CSS classes |
PreviewConfig
Configuration options for the preview component.
interface PreviewConfig {
darkMode?: boolean; // Enable dark mode
showToolbar?: boolean; // Show/hide the toolbar
showScale?: boolean; // Show/hide scaling information
showLabels?: boolean; // Show/hide breakpoint labels
}
Default configuration:
const defaultConfig = {
darkMode: false,
showToolbar: true,
showScale: true,
showLabels: true,
};
BreakpointConfig
Custom breakpoint definitions.
interface Breakpoint {
title: string; // Display name (e.g., "sm", "md", "lg")
minWidthRem: number; // Minimum width in rem units
minWidthPx: number; // Minimum width in pixels
icon: React.ComponentType; // Icon component for the breakpoint
}
Examples
Custom Breakpoints
Define your own breakpoints for testing:
import { RRChildPreview } from "@/components/ui/responsive-resizable-preview";
import { Smartphone, Tablet, Monitor } from "lucide-react";
const customBreakpoints = [
{
title: "mobile",
minWidthRem: 20,
minWidthPx: 320,
icon: Smartphone,
},
{
title: "tablet",
minWidthRem: 48,
minWidthPx: 768,
icon: Tablet,
},
{
title: "desktop",
minWidthRem: 80,
minWidthPx: 1280,
icon: Monitor,
},
];
export default function CustomBreakpointExample() {
return (
<RRChildPreview breakpoints={customBreakpoints}>
<YourComponent />
</RRChildPreview>
);
}
Minimal Preview
For a clean preview without controls:
<RRChildPreview
config={{
showToolbar: false,
showScale: false,
showLabels: false,
}}
>
<YourComponent />
</RRChildPreview>
Features
- 🎯 Drag-to-resize: Interactive resizing with smooth transitions
- 📱 Breakpoint indicators: Visual feedback for different screen sizes
- 🎮 Playback controls: Automatically cycle through breakpoints
- 📸 Screenshot tool: Capture preview at current size
- 🎨 Dark mode support: Built-in dark/light theme switching
- 📐 Scale indicators: Visual measurement tools
- 🖼️ IFrame support: Isolated preview for complete pages
- ⚡ Container queries: Perfect for testing
@container
CSS
Best Practices
- Use container queries when possible for true container-based responsive design
- Test with real content rather than placeholder text
- Include edge cases in your breakpoint testing
- Use IFrame preview for testing complete pages with their own CSS
- Configure minimal view when you just need the resizing functionality
Last updated on