Kanso UI LogoKanso UI
Components/browser loader
effects

Browser Loader

A premium, interactive SVG mock browser loader with glowing trace flows and pulsing content skeleton overlays.

loaderbrowsersvganimationglowskeletonvercel

Preview

Select Theme Color
Layout Variant
Flow duration5s
Deploying project...

🖲Usage

example-usage.tsx
import { BrowserLoader } from "@/components/kanso/browser-loader"

export default function BrowserLoaderDemo() {
  return (
    <div className="w-full max-w-2xl aspect-video">
      <BrowserLoader themeColor="cyan" loadingText="Deploying..." />
    </div>
  )
}

Installation

1

Create folder & copy source

Create a folder named kanso inside your project's components directory (i.e. components/kanso/). Copy the source code shown in the next section, and paste it into a file named browser-loader.tsx inside it.

2

Add global styling

Add the custom keyframe animations and styles to your global stylesheet (e.g. app/globals.css):

globals.css
/* Add to app/globals.css */
@theme inline {
  --animate-kanso-loader-flow: kanso-loader-flow 5s linear infinite;

  @keyframes kanso-loader-flow {
    from {
      stroke-dashoffset: 720;
    }
    to {
      stroke-dashoffset: 0;
    }
  }
}
3

Required helper files

Ensure your project has the following helper files configured:

  • lib/utils

<>Source Code

browser-loader.tsx
'use client';

import * as React from 'react';
import { cn } from '@/lib/utils';

export interface BrowserLoaderProps extends React.HTMLAttributes<HTMLDivElement> {
  /**
   * The visual layout variant of the mock browser contents.
   * @default 'browser'
   */
  variant?: 'browser' | 'sidebar' | 'dashboard';
  /**
   * The color theme of the loading traces.
   * Can be one of the presets or any valid CSS color (hex, rgb, hsl).
   * @default 'cyan'
   */
  themeColor?:
    | 'indigo'
    | 'cyan'
    | 'emerald'
    | 'rose'
    | 'amber'
    | 'monochrome'
    | string;
  /**
   * Custom loading text displayed in the browser window header.
   * @default 'Loading...'
   */
  loadingText?: string;
  /**
   * Whether to show the background grid lines.
   * @default true
   */
  showGrid?: boolean;
  /**
   * Animation duration for the flow lines in seconds.
   * @default 5
   */
  flowDuration?: number;
  /**
   * Accent glow intensity multiplier (0 to 1).
   * @default 0.6
   */
  glowIntensity?: number;
}

const themeColorMap: Record<string, string> = {
  indigo: '#6366f1',
  cyan: '#00ccff',
  emerald: '#10b981',
  rose: '#f43f5e',
  amber: '#f59e0b',
  monochrome: 'currentColor',
};

function BrowserLoader({
  variant = 'browser',
  themeColor = 'cyan',
  loadingText = 'Loading...',
  showGrid = true,
  flowDuration = 5,
  glowIntensity = 0.6,
  className,
  ...props
}: BrowserLoaderProps) {
  // Resolve theme color to hex or CSS variable
  const resolvedColor = React.useMemo(() => {
    if (themeColor in themeColorMap) {
      return themeColorMap[themeColor];
    }
    return themeColor;
  }, [themeColor]);

  // Unique ID prefix for gradients to prevent collision if multiple loaders are rendered
  const idPrefix = React.useId().replace(/:/g, '');

  return (
    <div
      className={cn(
        'relative flex items-center justify-center w-full h-full min-h-[300px] overflow-hidden bg-zinc-50 dark:bg-zinc-950 text-zinc-900 dark:text-white rounded-xl border border-zinc-200 dark:border-zinc-900 transition-colors duration-300',
        className
      )}
      {...props}
    >
      {/* SVG Loader Content */}
      <svg
        viewBox="0 0 900 900"
        xmlns="http://www.w3.org/2000/svg"
        preserveAspectRatio="xMidYMid slice"
        className="w-full h-full opacity-90 transition-opacity duration-300"
      >
        <defs>
          <linearGradient
            id={`${idPrefix}-traceGradient1`}
            x1={250}
            y1={120}
            x2={100}
            y2={200}
            gradientUnits="userSpaceOnUse"
          >
            <stop
              offset="0%"
              stopColor={
                resolvedColor === 'currentColor'
                  ? 'currentColor'
                  : resolvedColor
              }
              stopOpacity={1}
            />
            <stop
              offset="100%"
              stopColor={
                resolvedColor === 'currentColor'
                  ? 'currentColor'
                  : resolvedColor
              }
              stopOpacity={0.2}
            />
          </linearGradient>
          <linearGradient
            id={`${idPrefix}-traceGradient2`}
            x1={650}
            y1={120}
            x2={800}
            y2={300}
            gradientUnits="userSpaceOnUse"
          >
            <stop
              offset="0%"
              stopColor={
                resolvedColor === 'currentColor'
                  ? 'currentColor'
                  : resolvedColor
              }
              stopOpacity={1}
            />
            <stop
              offset="100%"
              stopColor={
                resolvedColor === 'currentColor'
                  ? 'currentColor'
                  : resolvedColor
              }
              stopOpacity={0.2}
            />
          </linearGradient>
          <linearGradient
            id={`${idPrefix}-traceGradient3`}
            x1={250}
            y1={380}
            x2={400}
            y2={400}
            gradientUnits="userSpaceOnUse"
          >
            <stop
              offset="0%"
              stopColor={
                resolvedColor === 'currentColor'
                  ? 'currentColor'
                  : resolvedColor
              }
              stopOpacity={1}
            />
            <stop
              offset="100%"
              stopColor={
                resolvedColor === 'currentColor'
                  ? 'currentColor'
                  : resolvedColor
              }
              stopOpacity={0.2}
            />
          </linearGradient>
          <linearGradient
            id={`${idPrefix}-traceGradient4`}
            x1={650}
            y1={120}
            x2={500}
            y2={100}
            gradientUnits="userSpaceOnUse"
          >
            <stop
              offset="0%"
              stopColor={
                resolvedColor === 'currentColor'
                  ? 'currentColor'
                  : resolvedColor
              }
              stopOpacity={1}
            />
            <stop
              offset="100%"
              stopColor={
                resolvedColor === 'currentColor'
                  ? 'currentColor'
                  : resolvedColor
              }
              stopOpacity={0.2}
            />
          </linearGradient>
        </defs>

        {/* Grid Background */}
        {showGrid && (
          <g
            id="grid"
            className="opacity-30 dark:opacity-40 transition-opacity duration-300"
          >
            {/* Vertical grid lines */}
            <line
              x1={0}
              y1={0}
              x2={0}
              y2="100%"
              className="stroke-zinc-200 dark:stroke-zinc-900"
              strokeWidth={0.75}
            />
            <line
              x1={100}
              y1={0}
              x2={100}
              y2="100%"
              className="stroke-zinc-200 dark:stroke-zinc-900"
              strokeWidth={0.75}
            />
            <line
              x1={200}
              y1={0}
              x2={200}
              y2="100%"
              className="stroke-zinc-200 dark:stroke-zinc-900"
              strokeWidth={0.75}
            />
            <line
              x1={300}
              y1={0}
              x2={300}
              y2="100%"
              className="stroke-zinc-200 dark:stroke-zinc-900"
              strokeWidth={0.75}
            />
            <line
              x1={400}
              y1={0}
              x2={400}
              y2="100%"
              className="stroke-zinc-200 dark:stroke-zinc-900"
              strokeWidth={0.75}
            />
            <line
              x1={500}
              y1={0}
              x2={500}
              y2="100%"
              className="stroke-zinc-200 dark:stroke-zinc-900"
              strokeWidth={0.75}
            />
            <line
              x1={600}
              y1={0}
              x2={600}
              y2="100%"
              className="stroke-zinc-200 dark:stroke-zinc-900"
              strokeWidth={0.75}
            />
            <line
              x1={700}
              y1={0}
              x2={700}
              y2="100%"
              className="stroke-zinc-200 dark:stroke-zinc-900"
              strokeWidth={0.75}
            />
            <line
              x1={800}
              y1={0}
              x2={800}
              y2="100%"
              className="stroke-zinc-200 dark:stroke-zinc-900"
              strokeWidth={0.75}
            />
            <line
              x1={900}
              y1={0}
              x2={900}
              y2="100%"
              className="stroke-zinc-200 dark:stroke-zinc-900"
              strokeWidth={0.75}
            />

            {/* Horizontal grid lines */}
            <line
              x1={0}
              y1={100}
              x2="100%"
              y2={100}
              className="stroke-zinc-200 dark:stroke-zinc-900"
              strokeWidth={0.75}
            />
            <line
              x1={0}
              y1={200}
              x2="100%"
              y2={200}
              className="stroke-zinc-200 dark:stroke-zinc-900"
              strokeWidth={0.75}
            />
            <line
              x1={0}
              y1={300}
              x2="100%"
              y2={300}
              className="stroke-zinc-200 dark:stroke-zinc-900"
              strokeWidth={0.75}
            />
            <line
              x1={0}
              y1={400}
              x2="100%"
              y2={400}
              className="stroke-zinc-200 dark:stroke-zinc-900"
              strokeWidth={0.75}
            />
            <line
              x1={0}
              y1={500}
              x2="100%"
              y2={500}
              className="stroke-zinc-200 dark:stroke-zinc-900"
              strokeWidth={0.75}
            />
            <line
              x1={0}
              y1={600}
              x2="100%"
              y2={600}
              className="stroke-zinc-200 dark:stroke-zinc-900"
              strokeWidth={0.75}
            />
            <line
              x1={0}
              y1={700}
              x2="100%"
              y2={700}
              className="stroke-zinc-200 dark:stroke-zinc-900"
              strokeWidth={0.75}
            />
            <line
              x1={0}
              y1={800}
              x2="100%"
              y2={800}
              className="stroke-zinc-200 dark:stroke-zinc-900"
              strokeWidth={0.75}
            />
          </g>
        )}

        {/* Traces Group (Translates same as Browser, with Bezier bends at corners) */}
        <g id="traces" transform="translate(0, 200)">
          {/* Path 1: Enters left, bends up along left edge of browser */}
          <path
            d="M100 300 H240 Q250 300 250 290 V120"
            className="animate-kanso-loader-flow fill-none stroke-linejoin-round [stroke-dasharray:120_600] [stroke-dashoffset:720] transition-colors duration-300"
            style={{
              stroke: `url(#${idPrefix}-traceGradient1)`,
              strokeWidth: 1.5,
              animationDuration: `${flowDuration}s`,
              filter:
                resolvedColor === 'currentColor'
                  ? 'none'
                  : `drop-shadow(0 0 ${8 * glowIntensity}px ${resolvedColor})`,
            }}
          />
          {/* Path 2: Enters right, bends down along right edge of browser */}
          <path
            d="M800 200 H660 Q650 200 650 210 V380"
            className="animate-kanso-loader-flow fill-none stroke-linejoin-round [stroke-dasharray:120_600] [stroke-dashoffset:720] transition-colors duration-300"
            style={{
              stroke: `url(#${idPrefix}-traceGradient2)`,
              strokeWidth: 1.5,
              animationDuration: `${flowDuration}s`,
              filter:
                resolvedColor === 'currentColor'
                  ? 'none'
                  : `drop-shadow(0 0 ${8 * glowIntensity}px ${resolvedColor})`,
            }}
          />
          {/* Path 3: Enters bottom, bends left along bottom edge of browser */}
          <path
            d="M400 520 V390 Q400 380 390 380 H250"
            className="animate-kanso-loader-flow fill-none stroke-linejoin-round [stroke-dasharray:120_600] [stroke-dashoffset:720] transition-colors duration-300"
            style={{
              stroke: `url(#${idPrefix}-traceGradient3)`,
              strokeWidth: 1.5,
              animationDuration: `${flowDuration}s`,
              filter:
                resolvedColor === 'currentColor'
                  ? 'none'
                  : `drop-shadow(0 0 ${8 * glowIntensity}px ${resolvedColor})`,
            }}
          />
          {/* Path 4: Enters top, bends right along top edge of browser */}
          <path
            d="M500 50 V110 Q500 120 510 120 H650"
            className="animate-kanso-loader-flow fill-none stroke-linejoin-round [stroke-dasharray:120_600] [stroke-dashoffset:720] transition-colors duration-300"
            style={{
              stroke: `url(#${idPrefix}-traceGradient4)`,
              strokeWidth: 1.5,
              animationDuration: `${flowDuration}s`,
              filter:
                resolvedColor === 'currentColor'
                  ? 'none'
                  : `drop-shadow(0 0 ${8 * glowIntensity}px ${resolvedColor})`,
            }}
          />
        </g>

        {/* Browser Mockup Window */}
        <g id="browser" transform="translate(0, 200)" className="select-none">
          {/* Main Frame */}
          <rect
            x={250}
            y={120}
            width={400}
            height={260}
            rx={10}
            ry={10}
            className="fill-white dark:fill-zinc-950/80 stroke-zinc-200 dark:stroke-zinc-800/80 [filter:drop-shadow(0_10px_30px_rgba(0,0,0,0.03))] dark:[filter:drop-shadow(0_20px_50px_rgba(0,0,0,0.8))] transition-colors duration-300"
            strokeWidth={1.5}
          />
          {/* Header Bar */}
          <rect
            x={250}
            y={120}
            width={400}
            height={34}
            rx={10}
            ry={10}
            className="fill-zinc-50 dark:fill-zinc-900/90 stroke-zinc-200 dark:stroke-zinc-800/60 transition-colors duration-300"
            strokeWidth={1}
          />
          {/* Cover rounded corners of top bar bottom so only top is rounded */}
          <rect
            x={251}
            y={144}
            width={398}
            height={10}
            className="fill-white dark:fill-zinc-950 transition-colors duration-300"
          />

          {/* Browser Window Window Control Dots */}
          <circle
            cx={272}
            cy={137}
            r={3.5}
            className="fill-zinc-200 dark:fill-zinc-800 transition-colors duration-300"
          />
          <circle
            cx={282}
            cy={137}
            r={3.5}
            className="fill-zinc-200 dark:fill-zinc-800 transition-colors duration-300"
          />
          <circle
            cx={292}
            cy={137}
            r={3.5}
            className="fill-zinc-200 dark:fill-zinc-800 transition-colors duration-300"
          />

          {/* Loading Header Text */}
          <text
            x={450}
            y={141}
            textAnchor="middle"
            className="font-mono text-[9px] font-semibold tracking-[0.25em] uppercase fill-zinc-400 dark:fill-zinc-500 transition-colors duration-300"
          >
            {loadingText}
          </text>

          {/* Page Loading Skeletons - VARIANT 1: browser */}
          {variant === 'browser' && (
            <g className="opacity-95">
              {/* Title block */}
              <rect
                x={275}
                y={175}
                width={180}
                height={12}
                rx={3}
                ry={3}
                className="fill-zinc-100 dark:fill-zinc-800/70 animate-pulse transition-colors duration-300"
              />

              {/* Paragraph lines */}
              <rect
                x={275}
                y={205}
                width={350}
                height={8}
                rx={2}
                ry={2}
                className="fill-zinc-50 dark:fill-zinc-900/60 animate-pulse [animation-delay:200ms] transition-colors duration-300"
              />
              <rect
                x={275}
                y={222}
                width={320}
                height={8}
                rx={2}
                ry={2}
                className="fill-zinc-50 dark:fill-zinc-900/60 animate-pulse [animation-delay:400ms] transition-colors duration-300"
              />
              <rect
                x={275}
                y={239}
                width={220}
                height={8}
                rx={2}
                ry={2}
                className="fill-zinc-50 dark:fill-zinc-900/60 animate-pulse [animation-delay:600ms] transition-colors duration-300"
              />

              {/* Visual canvas block */}
              <rect
                x={275}
                y={265}
                width={350}
                height={75}
                rx={6}
                ry={6}
                className="fill-zinc-50/50 dark:fill-zinc-900/40 stroke-zinc-200 dark:stroke-zinc-900/60 animate-pulse [animation-delay:800ms] transition-colors duration-300"
                strokeWidth={1}
              />

              {/* Footer indicator line */}
              <rect
                x={275}
                y={355}
                width={100}
                height={8}
                rx={2}
                ry={2}
                className="fill-zinc-50 dark:fill-zinc-900/50 animate-pulse [animation-delay:1000ms] transition-colors duration-300"
              />
            </g>
          )}

          {/* Page Loading Skeletons - VARIANT 2: sidebar */}
          {variant === 'sidebar' && (
            <g className="opacity-95">
              {/* Sidebar border */}
              <line
                x1={330}
                y1={154}
                x2={330}
                y2={380}
                className="stroke-zinc-200 dark:stroke-zinc-800/80 transition-colors duration-300"
                strokeWidth={1}
              />

              {/* Sidebar items */}
              <rect
                x={265}
                y={175}
                width={50}
                height={10}
                rx={2.5}
                ry={2.5}
                className="fill-zinc-100 dark:fill-zinc-800/70 animate-pulse transition-colors duration-300"
              />
              <rect
                x={265}
                y={195}
                width={50}
                height={10}
                rx={2.5}
                ry={2.5}
                className="fill-zinc-50 dark:fill-zinc-900/60 animate-pulse [animation-delay:150ms] transition-colors duration-300"
              />
              <rect
                x={265}
                y={215}
                width={50}
                height={10}
                rx={2.5}
                ry={2.5}
                className="fill-zinc-50 dark:fill-zinc-900/60 animate-pulse [animation-delay:300ms] transition-colors duration-300"
              />
              <rect
                x={265}
                y={235}
                width={50}
                height={10}
                rx={2.5}
                ry={2.5}
                className="fill-zinc-50 dark:fill-zinc-900/60 animate-pulse [animation-delay:450ms] transition-colors duration-300"
              />

              {/* Main content right side */}
              <rect
                x={350}
                y={175}
                width={120}
                height={12}
                rx={3}
                ry={3}
                className="fill-zinc-100 dark:fill-zinc-800/70 animate-pulse [animation-delay:100ms] transition-colors duration-300"
              />
              <rect
                x={350}
                y={202}
                width={275}
                height={8}
                rx={2}
                ry={2}
                className="fill-zinc-50 dark:fill-zinc-900/60 animate-pulse [animation-delay:250ms] transition-colors duration-300"
              />
              <rect
                x={350}
                y={218}
                width={250}
                height={8}
                rx={2}
                ry={2}
                className="fill-zinc-50 dark:fill-zinc-900/60 animate-pulse [animation-delay:400ms] transition-colors duration-300"
              />

              <rect
                x={350}
                y={242}
                width={275}
                height={95}
                rx={6}
                ry={6}
                className="fill-zinc-50/50 dark:fill-zinc-900/40 stroke-zinc-200 dark:stroke-zinc-900/60 animate-pulse [animation-delay:600ms] transition-colors duration-300"
                strokeWidth={1}
              />

              <rect
                x={350}
                y={355}
                width={80}
                height={8}
                rx={2}
                ry={2}
                className="fill-zinc-50 dark:fill-zinc-900/50 animate-pulse [animation-delay:800ms] transition-colors duration-300"
              />
            </g>
          )}

          {/* Page Loading Skeletons - VARIANT 3: dashboard */}
          {variant === 'dashboard' && (
            <g className="opacity-95">
              {/* Card 1 */}
              <rect
                x={270}
                y={172}
                width={170}
                height={80}
                rx={6}
                ry={6}
                className="fill-zinc-50/30 dark:fill-zinc-900/40 stroke-zinc-200 dark:stroke-zinc-900/60 animate-pulse transition-colors duration-300"
                strokeWidth={1}
              />
              <rect
                x={285}
                y={187}
                width={60}
                height={8}
                rx={2}
                ry={2}
                className="fill-zinc-100 dark:fill-zinc-800/70 animate-pulse transition-colors duration-300"
              />
              <rect
                x={285}
                y={207}
                width={120}
                height={18}
                rx={3}
                ry={3}
                className="fill-zinc-50 dark:fill-zinc-900/50 animate-pulse [animation-delay:200ms] transition-colors duration-300"
              />
              <rect
                x={285}
                y={233}
                width={80}
                height={6}
                rx={1.5}
                ry={1.5}
                className="fill-zinc-50 dark:fill-zinc-900/40 animate-pulse [animation-delay:350ms] transition-colors duration-300"
              />

              {/* Card 2 */}
              <rect
                x={460}
                y={172}
                width={170}
                height={80}
                rx={6}
                ry={6}
                className="fill-zinc-50/30 dark:fill-zinc-900/40 stroke-zinc-200 dark:stroke-zinc-900/60 animate-pulse [animation-delay:150ms] transition-colors duration-300"
                strokeWidth={1}
              />
              <rect
                x={475}
                y={187}
                width={60}
                height={8}
                rx={2}
                ry={2}
                className="fill-zinc-100 dark:fill-zinc-800/70 animate-pulse [animation-delay:150ms] transition-colors duration-300"
              />
              <rect
                x={475}
                y={207}
                width={120}
                height={18}
                rx={3}
                ry={3}
                className="fill-zinc-50 dark:fill-zinc-900/50 animate-pulse [animation-delay:350ms] transition-colors duration-300"
              />
              <rect
                x={475}
                y={233}
                width={80}
                height={6}
                rx={1.5}
                ry={1.5}
                className="fill-zinc-50 dark:fill-zinc-900/40 animate-pulse [animation-delay:500ms] transition-colors duration-300"
              />

              {/* Card 3 (Bottom) */}
              <rect
                x={270}
                y={268}
                width={360}
                height={88}
                rx={6}
                ry={6}
                className="fill-zinc-50/30 dark:fill-zinc-900/40 stroke-zinc-200 dark:stroke-zinc-900/60 animate-pulse [animation-delay:300ms] transition-colors duration-300"
                strokeWidth={1}
              />
              {/* Bar Chart Skeletons inside Bottom Card */}
              <rect
                x={290}
                y={295}
                width={12}
                height={45}
                rx={2}
                ry={2}
                className="fill-zinc-100 dark:fill-zinc-800/50 animate-pulse [animation-delay:400ms] transition-colors duration-300"
              />
              <rect
                x={312}
                y={310}
                width={12}
                height={30}
                rx={2}
                ry={2}
                className="fill-zinc-100 dark:fill-zinc-800/50 animate-pulse [animation-delay:500ms] transition-colors duration-300"
              />
              <rect
                x={334}
                y={285}
                width={12}
                height={55}
                rx={2}
                ry={2}
                className="fill-zinc-100 dark:fill-zinc-800/50 animate-pulse [animation-delay:600ms] transition-colors duration-300"
              />
              <rect
                x={356}
                y={305}
                width={12}
                height={35}
                rx={2}
                ry={2}
                className="fill-zinc-100 dark:fill-zinc-800/50 animate-pulse [animation-delay:700ms] transition-colors duration-300"
              />

              {/* Lines text info inside Bottom Card */}
              <rect
                x={395}
                y={290}
                width={120}
                height={8}
                rx={2}
                ry={2}
                className="fill-zinc-100 dark:fill-zinc-800/70 animate-pulse [animation-delay:450ms] transition-colors duration-300"
              />
              <rect
                x={395}
                y={306}
                width={210}
                height={8}
                rx={2}
                ry={2}
                className="fill-zinc-50 dark:fill-zinc-900/60 animate-pulse [animation-delay:650ms] transition-colors duration-300"
              />
              <rect
                x={395}
                y={322}
                width={160}
                height={8}
                rx={2}
                ry={2}
                className="fill-zinc-50 dark:fill-zinc-900/60 animate-pulse [animation-delay:850ms] transition-colors duration-300"
              />

              {/* Footer indicator line */}
              <rect
                x={275}
                y={363}
                width={100}
                height={6}
                rx={1.5}
                ry={1.5}
                className="fill-zinc-50 dark:fill-zinc-900/50 animate-pulse [animation-delay:1000ms] transition-colors duration-300"
              />
            </g>
          )}
        </g>
      </svg>
    </div>
  );
}

export { BrowserLoader };

Props

Command Palette

Search for a command to run...