Domain UI

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

0px0
0pxmax

Basic Preview

Current size:Base (< 256px)

"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 (&lt; 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

  • Leslie Alexander

    leslie.alexander@example.com

  • Michael Foster

    michael.foster@example.com

  • Dries Vincent

    dries.vincent@example.com

  • Lindsay Walton

    lindsay.walton@example.com

  • Courtney Henry

    courtney.henry@example.com

  • Tom Cook

    tom.cook@example.com

"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

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

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.

PropTypeDefaultDescription
childrenReactNode-The content to preview
configPreviewConfigdefaultConfigConfiguration options
breakpointsBreakpointConfig[]defaultBreakpointsCustom breakpoint definitions
classNamestring-Additional CSS classes

RRIFramePreview

Renders content in an iframe for isolated preview.

PropTypeDefaultDescription
srcUrlstring-Required. URL to load in iframe
heightnumber930Height of the iframe in pixels
configPreviewConfigdefaultConfigConfiguration options
breakpointsBreakpointConfig[]defaultBreakpointsCustom breakpoint definitions
classNamestring-Additional CSS classes

ResponsiveResizablePreview

The base preview component that others extend.

PropTypeDefaultDescription
childrenReactNode-The content to preview
configPreviewConfigdefaultConfigConfiguration options
breakpointsBreakpointConfig[]defaultBreakpointsCustom breakpoint definitions
classNamestring-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

  1. Use container queries when possible for true container-based responsive design
  2. Test with real content rather than placeholder text
  3. Include edge cases in your breakpoint testing
  4. Use IFrame preview for testing complete pages with their own CSS
  5. Configure minimal view when you just need the resizing functionality

Last updated on