A component that renders JSX strings with access to Tailwind CSS, shadcn components, and Lucide icons.
Flight Reservation
Departure
June 15, 2024
Time
10:30 AM
San Francisco (SFO)
to New York (JFK)
$349
Economy
"use client";
import { Calendar, Clock, Plane } from "lucide-react";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Slider } from "@/components/ui/slider";
import { highlightCode } from "@/lib/highlight-code";
import { JsxRenderer } from "@/components/ui/jsx-renderer";
const fullJsx = `<div className="max-w-xl rounded-lg border bg-card p-6">
<div className="flex items-center gap-2 mb-6">
<Plane className="size-5 text-primary" />
<h2 className="text-xl font-semibold">Flight Reservation</h2>
</div>
<div className="space-y-4 mb-6">
<div className="flex items-center justify-between p-3 bg-muted rounded-md">
<div className="flex items-center gap-2">
<Calendar className="size-4 text-muted-foreground" />
<span className="text-sm font-medium">Departure</span>
</div>
<span className="text-sm">June 15, 2024</span>
</div>
<div className="flex items-center justify-between p-3 bg-muted rounded-md">
<div className="flex items-center gap-2">
<Clock className="size-4 text-muted-foreground" />
<span className="text-sm font-medium">Time</span>
</div>
<span className="text-sm">10:30 AM</span>
</div>
<div className="flex items-center justify-between p-3 bg-muted rounded-md">
<div>
<div className="text-sm font-medium mb-1">San Francisco (SFO)</div>
<div className="text-xs text-muted-foreground">to New York (JFK)</div>
</div>
<div className="text-right">
<div className="text-sm font-semibold">$349</div>
<div className="text-xs text-muted-foreground">Economy</div>
</div>
</div>
</div>
<div className="flex gap-2">
<Button className="flex-1" onClick={() => onClickHandler("Select Flight")}>Select Flight</Button>
<Button variant="outline" className="flex-1" onClick={() => onClickHandler("View Details")}>View Details</Button>
</div>
</div>`;
export function JsxRendererDemo() {
const [percentage, setPercentage] = useState([100]);
const [highlightedCode, setHighlightedCode] = useState<string>("");
const partialJsx = fullJsx.slice(
0,
Math.floor((fullJsx.length * percentage[0]) / 100),
);
useEffect(() => {
highlightCode(partialJsx, "tsx").then(setHighlightedCode);
}, [partialJsx]);
const onClickHandler = (value: string) => {
toast.success(`${value} clicked`);
};
return (
<div className="max-h-[450px] space-y-4 w-full overflow-y-auto py-4">
<Slider
value={percentage}
onValueChange={setPercentage}
max={100}
step={1}
className="w-full"
/>
<div className="border rounded-lg">
<JsxRenderer
blacklistedAttrs={[]}
jsx={partialJsx}
components={{ Plane, Calendar, Clock, Button }}
className="w-full"
bindings={{
onClickHandler: onClickHandler,
}}
/>
</div>
<div className="space-y-2">
<div className="relative">
<div
className="bg-code text-sm rounded-lg overflow-x-auto overflow-y-auto"
dangerouslySetInnerHTML={{ __html: highlightedCode }}
/>
</div>
</div>
</div>
);
}
Installation
pnpm dlx shadcn@latest add jsx-renderer
About
The JSX Renderer component allows you to render JSX strings dynamically with built-in error handling, state management, and component injection. It's particularly useful for AI-generated UI components or when you need to render JSX from external sources.
Use Cases
- AI-Generated UI: Render user interfaces generated by AI models
- Code Preview: Display rendered JSX in documentation or playgrounds
States
The JSX Renderer supports different states that affect its appearance and behavior:
- interactive: Default state with full interactivity
- disabled: Reduces opacity and disables pointer events
- read-only: Disables pointer events but maintains visibility
- streaming: Shows a pulsing animation with reduced opacity
- error: Adds a red border for error indication
Usage
import { JsxRenderer } from "@/components/ui/jsx-renderer"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
export function MyComponent() {
const jsxString = `
<Card className="p-4">
<h2 className="text-lg font-semibold">Hello World</h2>
<Button>Click me</Button>
</Card>
`
return (
<JsxRenderer
jsx={jsxString}
components={{
Button,
Card,
}}
/>
)
}Component Requirements
When using the JSX Renderer, you need to provide all components that are referenced in your JSX string. For example:
const components = {
Button,
Card,
Input,
// ... any other components you want to use
}
const jsxString = `
<Card className="p-4">
<h2 className="text-lg font-semibold">Hello World</h2>
<Button>Click me</Button>
</Card>
`
<JsxRenderer jsx={jsxString} components={components} />Examples
Streaming State
"use client";
import { Cloud, CloudRain, Sun, Sunset } from "lucide-react";
import { useEffect, useState } from "react";
import { JsxRenderer } from "@/components/ui/jsx-renderer";
const fullJsx = `<div className="max-w-xl rounded-xl bg-gradient-to-br from-blue-400 to-blue-500 p-6 text-white shadow-lg">
<div className="flex justify-between items-center mb-4">
<h2 className="text-2xl font-medium">Mexico City</h2>
<Sun className="text-yellow-300 text-4xl" />
</div>
<div className="flex justify-between items-end mb-4">
<div className="text-6xl font-light">
<span>47°</span>
</div>
<div className="text-right">
<p className="text-sm">Winter storm warning</p>
</div>
</div>
<div className="grid grid-cols-6 gap-8">
<div className="text-center flex flex-col items-center">
<p className="text-sm mb-1">4PM</p>
<Sun className="text-yellow-300 mb-1" />
<p className="text-sm font-semibold">46°</p>
</div>
<div className="text-center flex flex-col items-center">
<p className="text-sm mb-1">5PM</p>
<Sun className="text-yellow-300 mb-1" />
<p className="text-sm font-semibold">44°</p>
</div>
<div className="text-center flex flex-col items-center">
<p className="text-sm mb-1">6PM</p>
<CloudRain className="text-yellow-300 mb-1" />
<p className="text-sm font-semibold">41°</p>
</div>
<div className="text-center flex flex-col items-center">
<p className="text-sm mb-1">7PM</p>
<Sunset className="text-yellow-500 mb-1" />
<p className="text-sm font-semibold">41°</p>
</div>
<div className="text-center flex flex-col items-center">
<p className="text-sm mb-1">8PM</p>
<Cloud className="text-gray-300 mb-1" />
<p className="text-sm font-semibold">37°</p>
</div>
<div className="text-center flex flex-col items-center">
<p className="text-sm mb-1">9PM</p>
<Cloud className="text-gray-300 mb-1" />
<p className="text-sm font-semibold">35°</p>
</div>
</div>
</div>`;
const jsxLines = fullJsx.split("\n");
export function JsxRendererStreamingDemo() {
const [currentLines, setCurrentLines] = useState(jsxLines.slice(0, 1)); // Start with header and current temp
useEffect(() => {
const interval = setInterval(() => {
if (currentLines.length >= jsxLines.length) {
setCurrentLines(jsxLines.slice(0, 1));
return;
}
setCurrentLines((prev) => [...prev, jsxLines[prev.length]]);
}, 100);
return () => clearInterval(interval);
}, [currentLines.length]);
const currentJsx = currentLines.join("\n");
return (
<div className="w-full">
<JsxRenderer
jsx={currentJsx}
components={{ Cloud, Sun, CloudRain, Sunset }}
state="streaming"
className="w-full h-[260px]"
/>
</div>
);
}
Disabled State
Restaurant Reservation
Date
October 20, 2024
Time
7:00 PM
The Golden Spoon
Italian Cuisine • Downtown
4 Guests
Window Seat
"use client";
import { Calendar, ChefHat, Clock } from "lucide-react";
import { JsxRenderer } from "@/components/ui/jsx-renderer";
export function JsxRendererDisabledDemo() {
const jsx = `<div className="max-w-xl rounded-lg border bg-card p-6">
<div className="flex items-center gap-2 mb-6">
<ChefHat className="size-5 text-primary" />
<h2 className="text-xl font-semibold">Restaurant Reservation</h2>
</div>
<div className="space-y-4 mb-6">
<div className="flex items-center justify-between p-3 bg-muted rounded-md">
<div className="flex items-center gap-2">
<Calendar className="size-4 text-muted-foreground" />
<span className="text-sm font-medium">Date</span>
</div>
<span className="text-sm">October 20, 2024</span>
</div>
<div className="flex items-center justify-between p-3 bg-muted rounded-md">
<div className="flex items-center gap-2">
<Clock className="size-4 text-muted-foreground" />
<span className="text-sm font-medium">Time</span>
</div>
<span className="text-sm">7:00 PM</span>
</div>
<div className="flex items-center justify-between p-3 bg-muted rounded-md">
<div>
<div className="text-sm font-medium mb-1">The Golden Spoon</div>
<div className="text-xs text-muted-foreground">Italian Cuisine • Downtown</div>
</div>
<div className="text-right">
<div className="text-sm font-semibold">4 Guests</div>
<div className="text-xs text-muted-foreground">Window Seat</div>
</div>
</div>
</div>
<div className="flex gap-2">
<button className="flex-1 bg-primary text-primary-foreground hover:bg-primary/90 px-4 py-2 rounded-md font-medium">
Confirm Reservation
</button>
<button className="flex-1 border border-input bg-background hover:bg-accent hover:text-accent-foreground px-4 py-2 rounded-md font-medium">
Modify Details
</button>
</div>
</div>`;
return (
<div className="w-full">
<JsxRenderer
jsx={jsx}
components={{ Calendar, Clock, ChefHat }}
state="disabled"
className="w-full"
/>
</div>
);
}
Error Handling
The component includes an error boundary to catch rendering errors:
<JsxRenderer
jsx={jsxString}
onError={(error, errorInfo) => console.error(error)}
fallback={CustomFallback}
/>API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
jsx | string | - | The JSX string to render |
fixIncompleteJsx | boolean | true | Whether to automatically complete unclosed JSX tags |
components | Record<string, React.ComponentType> | - | Map of component names to their React components |
state | "disabled" | "read-only" | "interactive" | "streaming" | "error" | "interactive" | The state of the renderer affecting styling and behavior |
fallback | React.ComponentType | ErrorFallback | Custom fallback component for error boundary |
onError | (error: Error, errorInfo: React.ErrorInfo) => void | - | Callback for error handling |