Skip to main content

Component Preview Tooltip

A hover tooltip that shows a live preview of any component.

Micro InteractionReactTailwind CSS
CSSTailwind

Manual

Create a file and paste the following code into it.

component-preview-tooltip.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
"use client";

import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { type FC, type ReactNode, useEffect, useState } from "react";
import { Tooltip, TooltipTrigger } from "@/components/ui/tooltip";
import { cn } from "@/lib/cn";

interface ComponentPreviewTooltipProps {
	componentName: string;
	children: ReactNode;
	width?: number;
	height?: number;
	scale?: number;
	side?: "top" | "right" | "bottom" | "left";
	className?: string;
}

export const ComponentPreviewTooltip: FC<ComponentPreviewTooltipProps> = ({
	componentName,
	children,
	width = 300,
	height = 200,
	scale = 0.8,
	side = "top",
	className,
}) => {
	const [PreviewComponent, setPreviewComponent] = useState<FC | null>(null);
	const [isLoading, setIsLoading] = useState(false);

	useEffect(() => {
		let mounted = true;

		async function loadComponent() {
			setIsLoading(true);
			try {
				const module = await import(
					`@/components/previews/${componentName}/default`
				);
				if (mounted) {
					setPreviewComponent(() => module.default);
				}
			} catch (error) {
				console.error(`Failed to load preview for ${componentName}`, error);
			} finally {
				if (mounted) {
					setIsLoading(false);
				}
			}
		}

		loadComponent();

		return () => {
			mounted = false;
		};
	}, [componentName]);

	return (
		<Tooltip delayDuration={0}>
			<TooltipTrigger asChild>
				<span className={cn("inline-block cursor-help", className)}>
					{children}
				</span>
			</TooltipTrigger>

			<TooltipPrimitive.Portal>
				<TooltipPrimitive.Content
					side={side}
					sideOffset={5}
					className={cn(
						"z-50 overflow-hidden rounded-3xl border shadow-xl",
						"bg-white dark:bg-zinc-900 border-zinc-200 dark:border-zinc-800",
						"text-zinc-950 dark:text-zinc-50",
						"animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
						"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
					)}
					style={{
						width,
						height,
					}}
				>
					<div className="flex h-full w-full flex-col p-3">
						<div className="flex-1 overflow-hidden relative bg-zinc-50 dark:bg-zinc-950/50 rounded-2xl">
							{isLoading ? (
								<div className="absolute inset-0 flex items-center justify-center">
									<div className="h-8 w-8 animate-spin rounded-full border-2 border-zinc-300 border-t-blue-500" />
								</div>
							) : PreviewComponent ? (
								<div
									className="origin-top-left absolute top-0 left-0"
									style={{
										width: `${100 / scale}%`,
										height: `${100 / scale}%`,
										transform: `scale(${scale})`,
									}}
								>
									<div className="flex w-full h-full items-center justify-center p-4">
										<PreviewComponent />
									</div>
								</div>
							) : (
								<div className="absolute inset-0 flex items-center justify-center text-sm text-zinc-500">
									Preview not found
								</div>
							)}
						</div>
					</div>
					<TooltipPrimitive.Arrow
						className="fill-white dark:fill-zinc-900"
						width={11}
						height={5}
					/>
				</TooltipPrimitive.Content>
			</TooltipPrimitive.Portal>
		</Tooltip>
	);
};

Update the import paths to match your project setup.

Similar components

Status Badge Table

Max

Morphing Dropdown

SVG Branch Connector

Tool Calls Section

Resource details

PublishedMarch 3, 2026
CategoryMicro Interaction
ReactTailwind CSS