Docs
Creative Error Boundaries

Creative Error Boundaries

A creative way to handle and display errors in your application with an artistic touch.

Select Artistic Style

Test Error Scenarios

Masterpiece in Progress

Your artwork is rendering beautifully in this creative space.

Installation

Copy and paste the following code into your project.

components/ui/creative-error-boundaries.tsx

"use client";
 
import React, { Component, ErrorInfo, ReactNode } from "react";
import { motion, AnimatePresence } from "framer-motion";
import {
  Paintbrush,
  Eraser,
  Bug,
  RefreshCw,
  Camera,
  Share2,
} from "lucide-react";
 
interface Props {
  children: ReactNode;
  theme?: "watercolor" | "sketch" | "oil-painting";
  onErrorCapture?: (error: Error, errorInfo: ErrorInfo) => void;
}
 
interface State {
  hasError: boolean;
  error: Error | null;
  errorInfo: ErrorInfo | null;
  showDebug: boolean;
  errorSnapshot: string | null;
}
 
export class CreativeErrorBoundaries extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = {
      hasError: false,
      error: null,
      errorInfo: null,
      showDebug: false,
      errorSnapshot: null,
    };
  }
 
  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error };
  }
 
  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    this.props.onErrorCapture?.(error, errorInfo);
    this.setState({
      error,
      errorInfo,
      errorSnapshot: new Date().toISOString(),
    });
  }
 
  private getThemeStyles() {
    switch (this.props.theme) {
      case "watercolor":
        return "bg-linear-to-br from-blue-50 to-purple-50 dark:from-blue-950 dark:to-purple-950 border-blue-200 dark:border-blue-800";
      case "sketch":
        return "bg-zinc-50 dark:bg-zinc-950 border-zinc-200 dark:border-zinc-800";
      case "oil-painting":
        return "bg-linear-to-br from-amber-50 to-rose-50 dark:from-amber-950 dark:to-rose-950 border-amber-200 dark:border-amber-800";
      default:
        return "bg-canvas-50 dark:bg-canvas-950 border-canvas-200 dark:border-canvas-800";
    }
  }
 
  private handleRetry = () => {
    this.setState({ hasError: false, error: null, errorInfo: null });
  };
 
  private handleShare = () => {
    const errorDetails = {
      message: this.state.error?.message,
      stack: this.state.error?.stack,
      timestamp: this.state.errorSnapshot,
    };
    navigator.clipboard.writeText(JSON.stringify(errorDetails, null, 2));
  };
 
  private captureErrorState = () => {
    const errorDetails = {
      message: this.state.error?.message,
      timestamp: this.state.errorSnapshot,
    };
    console.log("Error State Captured:", errorDetails);
  };
 
  render() {
    if (this.state.hasError) {
      return (
        <AnimatePresence>
          <motion.div
            initial={{ opacity: 0, y: 20 }}
            animate={{ opacity: 1, y: 0 }}
            exit={{ opacity: 0, y: -20 }}
            transition={{ duration: 0.3 }}
            className={`${this.getThemeStyles()} relative overflow-hidden rounded-lg border-2 border-dashed p-6`}
          >
            <motion.div
              className="absolute inset-0 opacity-10"
              animate={{
                backgroundPosition: ["0% 0%", "100% 100%"],
                backgroundSize: ["100% 100%", "150% 150%"],
              }}
              transition={{
                duration: 20,
                repeat: Infinity,
                repeatType: "reverse",
              }}
              style={{
                backgroundImage:
                  "url('data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%239C92AC' fill-opacity='0.4'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E')",
              }}
            />
 
            <div className="relative space-y-6">
              {/* Artistic Error Display */}
              <motion.div
                className="text-canvas-800 dark:text-canvas-200 flex items-center space-x-3"
                animate={{ x: [0, 5, 0] }}
                transition={{ duration: 2, repeat: Infinity }}
              >
                <Paintbrush className="h-6 w-6" />
                <h3 className="text-xl font-semibold">
                  Artistic Happy Accident
                </h3>
              </motion.div>
 
              {/* Error Message as Art Description */}
              <motion.div
                className={`rounded-lg bg-white/50 p-6 shadow-lg backdrop-blur-xs dark:bg-black/50`}
                whileHover={{ scale: 1.02 }}
                transition={{ type: "spring" }}
              >
                <p className="text-canvas-700 dark:text-canvas-300 font-serif text-lg italic">
                  &quot;{this.state.error?.message}&quot;
                </p>
                <div className="text-canvas-500 dark:text-canvas-400 mt-4 text-sm">
                  Captured at: {this.state.errorSnapshot}
                </div>
              </motion.div>
 
              {/* Action Buttons */}
              <div className="flex flex-wrap items-center gap-4">
                <motion.button
                  whileHover={{ scale: 1.05 }}
                  whileTap={{ scale: 0.95 }}
                  onClick={this.handleRetry}
                  className="bg-canvas-200 dark:bg-canvas-800 hover:bg-canvas-300 dark:hover:bg-canvas-700 flex items-center space-x-2 rounded-md px-4 py-2 transition-colors"
                >
                  <RefreshCw className="h-4 w-4" />
                  <span>Try Again</span>
                </motion.button>
 
                <motion.button
                  whileHover={{ scale: 1.05 }}
                  whileTap={{ scale: 0.95 }}
                  onClick={this.handleShare}
                  className="bg-canvas-200 dark:bg-canvas-800 hover:bg-canvas-300 dark:hover:bg-canvas-700 flex items-center space-x-2 rounded-md px-4 py-2 transition-colors"
                >
                  <Share2 className="h-4 w-4" />
                  <span>Share Error</span>
                </motion.button>
 
                <motion.button
                  whileHover={{ scale: 1.05 }}
                  whileTap={{ scale: 0.95 }}
                  onClick={this.captureErrorState}
                  className="bg-canvas-200 dark:bg-canvas-800 hover:bg-canvas-300 dark:hover:bg-canvas-700 flex items-center space-x-2 rounded-md px-4 py-2 transition-colors"
                >
                  <Camera className="h-4 w-4" />
                  <span>Capture State</span>
                </motion.button>
              </div>
 
              {/* Debug Info */}
              <motion.div
                initial={false}
                animate={{ height: this.state.showDebug ? "auto" : 0 }}
                className="overflow-hidden"
              >
                <div className="rounded-lg bg-white/30 p-6 backdrop-blur-xs dark:bg-black/30">
                  <div className="mb-4 flex items-center justify-between">
                    <div className="flex items-center space-x-2">
                      <Bug className="h-4 w-4" />
                      <h4 className="font-medium">Conservation Notes</h4>
                    </div>
                  </div>
                  <pre className="overflow-auto rounded-lg bg-black/5 p-4 text-sm dark:bg-white/5">
                    {this.state.errorInfo?.componentStack}
                  </pre>
                </div>
              </motion.div>
 
              {/* Toggle Debug Button */}
              <motion.button
                whileHover={{ scale: 1.05 }}
                whileTap={{ scale: 0.95 }}
                onClick={() =>
                  this.setState((state) => ({ showDebug: !state.showDebug }))
                }
                className="text-canvas-600 dark:text-canvas-400 text-sm underline decoration-dashed underline-offset-4"
              >
                {this.state.showDebug ? "Hide" : "Show"} Technical Details
              </motion.button>
            </div>
          </motion.div>
        </AnimatePresence>
      );
    }
 
    return this.props.children;
  }
}

Update the import paths to match your project setup.

import { CreativeErrorBoundaries } from "@/components/ui/creative-error-boundaries";

Usage

Basic Implementation

import { CreativeErrorBoundaries } from "@/components/ui/creative-error-boundaries";
 
export default function YourComponent() {
  return (
    <CreativeErrorBoundaries>
      {/* Your component content */}
    </CreativeErrorBoundaries>
  );
}

With Theme and Error Capture

import { CreativeErrorBoundaries } from "@/components/ui/creative-error-boundaries";
 
export default function ThemedErrorHandler() {
  const handleErrorCapture = (error: Error, errorInfo: ErrorInfo) => {
    // Log error to your analytics or monitoring service
    console.log("Error captured:", error);
    console.log("Error Info:", errorInfo);
  };
 
  return (
    <CreativeErrorBoundaries
      theme="watercolor"
      onErrorCapture={handleErrorCapture}
    >
      {/* Your component content */}
    </CreativeErrorBoundaries>
  );
}

Props

CreativeErrorBoundaries

PropTypeDefaultDescription
childrenReact.ReactNode-The content to be wrapped by the error boundary
theme"watercolor" | "sketch" | "oil-painting""watercolor"The visual theme to apply to the error display
onErrorCapture(error: Error, errorInfo: ErrorInfo) => void-Optional callback function triggered when an error is caught

Examples

Different Themes

// Watercolor theme
<CreativeErrorBoundaries theme="watercolor">
  <YourComponent />
</CreativeErrorBoundaries>
 
// Sketch theme
<CreativeErrorBoundaries theme="sketch">
  <YourComponent />
</CreativeErrorBoundaries>
 
// Oil painting theme
<CreativeErrorBoundaries theme="oil-painting">
  <YourComponent />
</CreativeErrorBoundaries>

Error Tracking Integration

import { CreativeErrorBoundaries } from "@/components/ui/creative-error-boundaries";
import { trackError } from "@/lib/analytics";
 
export default function TrackedComponent() {
  const handleError = (error: Error, errorInfo: ErrorInfo) => {
    // Track error in your analytics system
    trackError({
      error: error.message,
      stack: errorInfo.componentStack,
      timestamp: new Date().toISOString(),
    });
  };
 
  return (
    <CreativeErrorBoundaries onErrorCapture={handleError}>
      <YourComponent />
    </CreativeErrorBoundaries>
  );
}