Kanso UI LogoKanso UI
Components/ray card
cards

Ray Card

A premium, draft-grid style card featuring an animated border-tracer dot and a soft, glowing diagonal light ray.

cardtracingglowdraftgridminimalist

Preview

Glow / Tracer Color
Card Variant
Surface Finish
Tracer speed6s
750k
Total Views

🖲Usage

example-usage.tsx
import { RayCard } from "@/components/kanso/ray-card"

export default function RayCardDemo() {
  return (
    <RayCard 
      variant="metric" 
      value="750k" 
      label="Views" 
      glowColor="blue" 
    />
  )
}

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 ray-card.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-ray-dot: kanso-ray-dot 6s linear infinite;

  @keyframes kanso-ray-dot {
    0%, 100% { top: 4px; left: 4px; }
    25% { top: 4px; left: calc(100% - 12px); }
    50% { top: calc(100% - 12px); left: calc(100% - 12px); }
    75% { top: calc(100% - 12px); left: 4px; }
  }
}
3

Required helper files

Ensure your project has the following helper files configured:

  • lib/utils

<>Source Code

ray-card.tsx
'use client';

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

export interface RayCardProps extends React.HTMLAttributes<HTMLDivElement> {
  /**
   * The visual layout variant of the card content.
   * @default 'metric'
   */
  variant?: 'metric' | 'feature' | 'custom';
  /**
   * Main stat or headline text for the 'metric' variant (e.g. "750k").
   */
  value?: string;
  /**
   * Descriptive label text underneath the value.
   */
  label?: string;
  /**
   * Icon element rendered at the top for the 'feature' variant.
   */
  icon?: React.ReactNode;
  /**
   * Title text for the 'feature' variant.
   */
  title?: string;
  /**
   * The color theme of the card.
   * @default 'silver'
   */
  themeColor?: 'silver' | 'cyan' | 'gold' | 'violet';
  /**
   * The surface finish styling of the card.
   * @default 'glossy'
   */
  styleVariant?: 'glossy' | 'matte';
  /**
   * Whether to display the blueprint grid drafting lines.
   * @default true
   */
  showGridLines?: boolean;
  /**
   * Rotation duration of the border tracing dot in seconds.
   * @default 6
   */
  speed?: number;
  /**
   * Custom child elements (rendered directly in the 'custom' variant).
   */
  children?: React.ReactNode;
}

const themeGlows: Record<
  string,
  {
    dotColor: string;
    rayShadow: string;
    dark: {
      outerBg: string;
      cardBg: string;
      outerBgMatte: string;
      cardBgMatte: string;
      textGradient: string;
      lineGradientTop: string;
      lineGradientLeft: string;
      lineBorder: string;
      lineBorderMatte: string;
    };
    light: {
      outerBg: string;
      cardBg: string;
      outerBgMatte: string;
      cardBgMatte: string;
      textGradient: string;
      lineGradientTop: string;
      lineGradientLeft: string;
      lineBorder: string;
      lineBorderMatte: string;
    };
  }
> = {
  silver: {
    dotColor: '#ffffff',
    rayShadow: '#ffffff',
    dark: {
      outerBg:
        'radial-gradient(circle 180px at 0% 0%, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.02) 60%, transparent 100%)',
      cardBg: 'linear-gradient(180deg, #09090b 0%, #030303 100%)',
      outerBgMatte:
        'radial-gradient(circle 200px at 0% 0%, rgba(255, 255, 255, 0.04) 0%, transparent 100%)',
      cardBgMatte: 'linear-gradient(180deg, #09090b, #030303)',
      textGradient: 'linear-gradient(180deg, #ffffff 0%, #a1a1aa 100%)',
      lineGradientTop:
        'linear-gradient(90deg, rgba(255, 255, 255, 0.12) 30%, rgba(255, 255, 255, 0.04) 70%)',
      lineGradientLeft:
        'linear-gradient(180deg, rgba(255, 255, 255, 0.1) 30%, rgba(255, 255, 255, 0.04) 70%)',
      lineBorder: 'rgba(255, 255, 255, 0.04)',
      lineBorderMatte: 'rgba(255, 255, 255, 0.03)',
    },
    light: {
      outerBg:
        'radial-gradient(circle 180px at 0% 0%, rgba(9, 9, 11, 0.1) 0%, rgba(9, 9, 11, 0.02) 60%, transparent 100%)',
      cardBg: 'linear-gradient(180deg, #ffffff 0%, #fafafa 100%)',
      outerBgMatte:
        'radial-gradient(circle 200px at 0% 0%, rgba(9, 9, 11, 0.03) 0%, transparent 100%)',
      cardBgMatte: 'linear-gradient(180deg, #fafafa, #ffffff)',
      textGradient: 'linear-gradient(180deg, #09090b 0%, #52525b 100%)',
      lineGradientTop:
        'linear-gradient(90deg, rgba(9, 9, 11, 0.1) 30%, rgba(9, 9, 11, 0.03) 70%)',
      lineGradientLeft:
        'linear-gradient(180deg, rgba(9, 9, 11, 0.08) 30%, rgba(9, 9, 11, 0.03) 70%)',
      lineBorder: 'rgba(9, 9, 11, 0.03)',
      lineBorderMatte: 'rgba(9, 9, 11, 0.02)',
    },
  },
  cyan: {
    dotColor: '#00ccff',
    rayShadow: '#00ccff',
    dark: {
      outerBg:
        'radial-gradient(circle 180px at 0% 0%, rgba(0, 204, 255, 0.15) 0%, rgba(0, 204, 255, 0.02) 60%, transparent 100%)',
      cardBg: 'linear-gradient(180deg, #02080d 0%, #010406 100%)',
      outerBgMatte:
        'radial-gradient(circle 200px at 0% 0%, rgba(0, 204, 255, 0.04) 0%, transparent 100%)',
      cardBgMatte: 'linear-gradient(180deg, #010a10, #000000)',
      textGradient: 'linear-gradient(180deg, #ffffff 0%, #00ccff 100%)',
      lineGradientTop:
        'linear-gradient(90deg, rgba(0, 204, 255, 0.15) 30%, rgba(0, 204, 255, 0.04) 70%)',
      lineGradientLeft:
        'linear-gradient(180deg, rgba(0, 204, 255, 0.12) 30%, rgba(0, 204, 255, 0.04) 70%)',
      lineBorder: 'rgba(0, 204, 255, 0.04)',
      lineBorderMatte: 'rgba(0, 204, 255, 0.03)',
    },
    light: {
      outerBg:
        'radial-gradient(circle 180px at 0% 0%, rgba(2, 132, 199, 0.12) 0%, rgba(2, 132, 199, 0.02) 60%, transparent 100%)',
      cardBg: 'linear-gradient(180deg, #ffffff 0%, #f4fbfe 100%)',
      outerBgMatte:
        'radial-gradient(circle 200px at 0% 0%, rgba(2, 132, 199, 0.03) 0%, transparent 100%)',
      cardBgMatte: 'linear-gradient(180deg, #f0f9ff, #ffffff)',
      textGradient: 'linear-gradient(180deg, #0369a1 0%, #0284c7 100%)',
      lineGradientTop:
        'linear-gradient(90deg, rgba(2, 132, 199, 0.1) 30%, rgba(2, 132, 199, 0.03) 70%)',
      lineGradientLeft:
        'linear-gradient(180deg, rgba(2, 132, 199, 0.08) 30%, rgba(2, 132, 199, 0.03) 70%)',
      lineBorder: 'rgba(2, 132, 199, 0.03)',
      lineBorderMatte: 'rgba(2, 132, 199, 0.02)',
    },
  },
  gold: {
    dotColor: '#ffd700',
    rayShadow: '#ffd700',
    dark: {
      outerBg:
        'radial-gradient(circle 180px at 0% 0%, rgba(255, 215, 0, 0.15) 0%, rgba(255, 215, 0, 0.02) 60%, transparent 100%)',
      cardBg: 'linear-gradient(180deg, #080602 0%, #020201 100%)',
      outerBgMatte:
        'radial-gradient(circle 200px at 0% 0%, rgba(255, 215, 0, 0.04) 0%, transparent 100%)',
      cardBgMatte: 'linear-gradient(180deg, #070501, #000000)',
      textGradient: 'linear-gradient(180deg, #ffffff 0%, #ffd700 100%)',
      lineGradientTop:
        'linear-gradient(90deg, rgba(255, 215, 0, 0.15) 30%, rgba(255, 215, 0, 0.04) 70%)',
      lineGradientLeft:
        'linear-gradient(180deg, rgba(255, 215, 0, 0.12) 30%, rgba(255, 215, 0, 0.04) 70%)',
      lineBorder: 'rgba(255, 215, 0, 0.04)',
      lineBorderMatte: 'rgba(255, 215, 0, 0.03)',
    },
    light: {
      outerBg:
        'radial-gradient(circle 180px at 0% 0%, rgba(217, 119, 6, 0.12) 0%, rgba(217, 119, 6, 0.02) 60%, transparent 100%)',
      cardBg: 'linear-gradient(180deg, #ffffff 0%, #fffdf4 100%)',
      outerBgMatte:
        'radial-gradient(circle 200px at 0% 0%, rgba(217, 119, 6, 0.03) 0%, transparent 100%)',
      cardBgMatte: 'linear-gradient(180deg, #fffbeb, #ffffff)',
      textGradient: 'linear-gradient(180deg, #b45309 0%, #d97706 100%)',
      lineGradientTop:
        'linear-gradient(90deg, rgba(217, 119, 6, 0.1) 30%, rgba(217, 119, 6, 0.03) 70%)',
      lineGradientLeft:
        'linear-gradient(180deg, rgba(217, 119, 6, 0.08) 30%, rgba(217, 119, 6, 0.03) 70%)',
      lineBorder: 'rgba(217, 119, 6, 0.03)',
      lineBorderMatte: 'rgba(217, 119, 6, 0.02)',
    },
  },
  violet: {
    dotColor: '#8b5cf6',
    rayShadow: '#8b5cf6',
    dark: {
      outerBg:
        'radial-gradient(circle 180px at 0% 0%, rgba(139, 92, 246, 0.15) 0%, rgba(139, 92, 246, 0.02) 60%, transparent 100%)',
      cardBg: 'linear-gradient(180deg, #06030c 0%, #020104 100%)',
      outerBgMatte:
        'radial-gradient(circle 200px at 0% 0%, rgba(139, 92, 246, 0.04) 0%, transparent 100%)',
      cardBgMatte: 'linear-gradient(180deg, #05020a, #000000)',
      textGradient: 'linear-gradient(180deg, #ffffff 0%, #8b5cf6 100%)',
      lineGradientTop:
        'linear-gradient(90deg, rgba(139, 92, 246, 0.15) 30%, rgba(139, 92, 246, 0.04) 70%)',
      lineGradientLeft:
        'linear-gradient(180deg, rgba(139, 92, 246, 0.12) 30%, rgba(139, 92, 246, 0.04) 70%)',
      lineBorder: 'rgba(139, 92, 246, 0.04)',
      lineBorderMatte: 'rgba(139, 92, 246, 0.03)',
    },
    light: {
      outerBg:
        'radial-gradient(circle 180px at 0% 0%, rgba(124, 58, 237, 0.12) 0%, rgba(124, 58, 237, 0.02) 60%, transparent 100%)',
      cardBg: 'linear-gradient(180deg, #ffffff 0%, #faf8ff 100%)',
      outerBgMatte:
        'radial-gradient(circle 200px at 0% 0%, rgba(124, 58, 237, 0.03) 0%, transparent 100%)',
      cardBgMatte: 'linear-gradient(180deg, #f5f3ff, #ffffff)',
      textGradient: 'linear-gradient(180deg, #6d28d9 0%, #7c3aed 100%)',
      lineGradientTop:
        'linear-gradient(90deg, rgba(124, 58, 237, 0.1) 30%, rgba(124, 58, 237, 0.03) 70%)',
      lineGradientLeft:
        'linear-gradient(180deg, rgba(124, 58, 237, 0.08) 30%, rgba(124, 58, 237, 0.03) 70%)',
      lineBorder: 'rgba(124, 58, 237, 0.03)',
      lineBorderMatte: 'rgba(124, 58, 237, 0.02)',
    },
  },
};

const RayCard = React.forwardRef<HTMLDivElement, RayCardProps>(
  (
    {
      variant = 'metric',
      value,
      label,
      icon,
      title,
      themeColor = 'silver',
      styleVariant = 'glossy',
      showGridLines = true,
      speed = 6,
      children,
      className,
      style,
      ...props
    },
    ref
  ) => {
    // Resolve theme preset configuration
    const activeTheme = React.useMemo(() => {
      if (themeColor in themeGlows) {
        return themeGlows[themeColor];
      }
      return themeGlows.silver;
    }, [themeColor]);

    // Unique class ID for component-scoping inline styles
    const scopeId = React.useId().replace(/:/g, '');

    const cssVars = {
      '--outer-bg-dark': activeTheme.dark.outerBg,
      '--card-bg-dark': activeTheme.dark.cardBg,
      '--outer-bg-matte-dark': activeTheme.dark.outerBgMatte,
      '--card-bg-matte-dark': activeTheme.dark.cardBgMatte,
      '--text-gradient-dark': activeTheme.dark.textGradient,
      '--line-top-gradient-dark': activeTheme.dark.lineGradientTop,
      '--line-left-gradient-dark': activeTheme.dark.lineGradientLeft,
      '--line-border-dark': activeTheme.dark.lineBorder,
      '--line-border-matte-dark': activeTheme.dark.lineBorderMatte,

      '--outer-bg-light': activeTheme.light.outerBg,
      '--card-bg-light': activeTheme.light.cardBg,
      '--outer-bg-matte-light': activeTheme.light.outerBgMatte,
      '--card-bg-matte-light': activeTheme.light.cardBgMatte,
      '--text-gradient-light': activeTheme.light.textGradient,
      '--line-top-gradient-light': activeTheme.light.lineGradientTop,
      '--line-left-gradient-light': activeTheme.light.lineGradientLeft,
      '--line-border-light': activeTheme.light.lineBorder,
      '--line-border-matte-light': activeTheme.light.lineBorderMatte,

      '--dot-color': activeTheme.dotColor,
      '--ray-shadow': activeTheme.rayShadow,
      '--speed': `${speed}s`,
    } as React.CSSProperties;

    return (
      <div
        ref={ref}
        style={{ ...cssVars, ...style }}
        className={cn(
          `outer-card-${scopeId} outer-card-container`,
          styleVariant === 'glossy' ? 'style-glossy' : 'style-matte',
          className
        )}
        {...props}
      >
        <style>{`
          @keyframes moveDot-${scopeId} {
            0%, 100% {
              top: 10%;
              right: 10%;
            }
            25% {
              top: 10%;
              right: calc(100% - 35px);
            }
            50% {
              top: calc(100% - 30px);
              right: calc(100% - 35px);
            }
            75% {
              top: calc(100% - 30px);
              right: 10%;
            }
          }

          .outer-card-${scopeId} {
            width: 300px;
            height: 250px;
            border-radius: 10px;
            padding: 1px;
            position: relative;
            box-sizing: border-box;
          }

          /* Glossy Outer Bg */
          .outer-card-${scopeId}.style-glossy {
            background: var(--outer-bg-light);
          }
          .dark .outer-card-${scopeId}.style-glossy {
            background: var(--outer-bg-dark);
          }

          /* Matte Outer Bg */
          .outer-card-${scopeId}.style-matte {
            background: var(--outer-bg-matte-light);
          }
          .dark .outer-card-${scopeId}.style-matte {
            background: var(--outer-bg-matte-dark);
          }

          .outer-card-${scopeId} .kanso-ray-card-dot {
            width: 5px;
            aspect-ratio: 1;
            position: absolute;
            background-color: var(--dot-color);
            box-shadow: 0 0 10px var(--dot-color);
            border-radius: 100px;
            z-index: 2;
            animation: moveDot-${scopeId} var(--speed) linear infinite;
          }

          .outer-card-${scopeId} .kanso-ray-card-body {
            z-index: 1;
            width: 100%;
            height: 100%;
            border-radius: 9px;
            display: flex;
            align-items: center;
            justify-content: center;
            position: relative;
            flex-direction: column;
            box-sizing: border-box;
            overflow: hidden;
            transition: background 0.3s ease, border-color 0.3s ease;
          }

          /* Glossy Body Styling */
          .outer-card-${scopeId}.style-glossy .kanso-ray-card-body {
            border: solid 1px var(--line-border-light);
            background: var(--card-bg-light);
            color: #18181b;
          }
          .dark .outer-card-${scopeId}.style-glossy .kanso-ray-card-body {
            border: solid 1px var(--line-border-dark);
            background: var(--card-bg-dark);
            color: #ffffff;
          }

          /* Matte Body Styling */
          .outer-card-${scopeId}.style-matte .kanso-ray-card-body {
            border: solid 1px var(--line-border-matte-light);
            background: var(--card-bg-matte-light);
            color: #18181b;
          }
          .dark .outer-card-${scopeId}.style-matte .kanso-ray-card-body {
            border: solid 1px var(--line-border-matte-dark);
            background: var(--card-bg-matte-dark);
            color: #ffffff;
          }

          /* Light Sheen Reflection for Glossy Variant */
          .outer-card-${scopeId} .kanso-ray-card-sheen {
            display: none;
          }
          .outer-card-${scopeId}.style-glossy .kanso-ray-card-sheen {
            display: block;
            position: absolute;
            inset: 0;
            background: radial-gradient(circle 150px at 0% 0%, rgba(9, 9, 11, 0.02) 0%, transparent 100%);
            z-index: 1;
            pointer-events: none;
          }
          .dark .outer-card-${scopeId}.style-glossy .kanso-ray-card-sheen {
            background: radial-gradient(circle 150px at 0% 0%, rgba(255, 255, 255, 0.05) 0%, transparent 100%);
          }

          /* Light Ray Styling */
          .outer-card-${scopeId} .kanso-ray-card-ray {
            width: 220px;
            height: 45px;
            border-radius: 100px;
            position: absolute;
            transform-origin: 10%;
            top: 0%;
            left: 0;
            transform: rotate(40deg);
            pointer-events: none;
            transition: opacity 0.5s ease;
          }

          /* Glossy Ray */
          .outer-card-${scopeId}.style-glossy .kanso-ray-card-ray {
            background-color: transparent;
            opacity: 0.06;
            box-shadow: 0 0 45px 5px var(--ray-shadow);
            filter: blur(24px);
          }
          .dark .outer-card-${scopeId}.style-glossy .kanso-ray-card-ray {
            background-color: transparent;
            opacity: 0.08;
          }
          .outer-card-${scopeId}.style-glossy:hover .kanso-ray-card-ray {
            opacity: 0.12;
          }
          .dark .outer-card-${scopeId}.style-glossy:hover .kanso-ray-card-ray {
            opacity: 0.16;
          }

          /* Matte Ray */
          .outer-card-${scopeId}.style-matte .kanso-ray-card-ray {
            background-color: transparent;
            opacity: 0.03;
            box-shadow: 0 0 35px 2px var(--ray-shadow);
            filter: blur(32px);
          }
          .dark .outer-card-${scopeId}.style-matte .kanso-ray-card-ray {
            background-color: transparent;
            opacity: 0.05;
          }
          .outer-card-${scopeId}.style-matte:hover .kanso-ray-card-ray {
            opacity: 0.06;
          }
          .dark .outer-card-${scopeId}.style-matte:hover .kanso-ray-card-ray {
            opacity: 0.10;
          }

          .outer-card-${scopeId} .kanso-ray-card-text {
            font-weight: 900;
            font-size: 4rem;
            background: var(--text-gradient-light);
            background-clip: text;
            -webkit-background-clip: text;
            color: transparent;
            line-height: 1;
            user-select: none;
          }

          .dark .outer-card-${scopeId} .kanso-ray-card-text {
            background: var(--text-gradient-dark);
            background-clip: text;
            -webkit-background-clip: text;
          }

          .outer-card-${scopeId} .kanso-ray-card-line {
            width: 100%;
            height: 1px;
            position: absolute;
            pointer-events: none;
          }

          /* Glossy Lines color */
          .outer-card-${scopeId}.style-glossy .kanso-ray-card-line {
            background-color: var(--line-border-light);
          }
          .dark .outer-card-${scopeId}.style-glossy .kanso-ray-card-line {
            background-color: var(--line-border-dark);
          }

          /* Matte Lines color */
          .outer-card-${scopeId}.style-matte .kanso-ray-card-line {
            background-color: var(--line-border-matte-light);
          }
          .dark .outer-card-${scopeId}.style-matte .kanso-ray-card-line {
            background-color: var(--line-border-matte-dark);
          }

          .outer-card-${scopeId} .kanso-ray-card-topl {
            top: 10%;
            background: var(--line-top-gradient-light);
          }
          .dark .outer-card-${scopeId} .kanso-ray-card-topl {
            background: var(--line-top-gradient-dark);
          }

          .outer-card-${scopeId} .kanso-ray-card-bottoml {
            bottom: 10%;
          }

          .outer-card-${scopeId} .kanso-ray-card-leftl {
            left: 10%;
            width: 1px;
            height: 100%;
            background: var(--line-left-gradient-light);
          }
          .dark .outer-card-${scopeId} .kanso-ray-card-leftl {
            background: var(--line-left-gradient-dark);
          }

          .outer-card-${scopeId} .kanso-ray-card-rightl {
            right: 10%;
            width: 1px;
            height: 100%;
          }
        `}</style>

        {/* Tracing Dot */}
        <div className="kanso-ray-card-dot" />

        {/* Card Body */}
        <div className="kanso-ray-card-body">
          {/* Diagnostic Light Ray */}
          <div className="kanso-ray-card-ray" />

          {/* Glossy Sheen Reflection Overlay */}
          <div className="kanso-ray-card-sheen" />

          {/* Card Content Layout */}
          <div className="relative z-10 flex flex-col items-center justify-center text-center w-full h-full select-none pointer-events-none">
            {variant === 'metric' && (
              <>
                <div className="kanso-ray-card-text">{value || '750k'}</div>
                <div className="text-[10px] font-mono tracking-[0.25em] uppercase text-zinc-400 dark:text-zinc-500 font-bold mt-1.5">
                  {label || 'Views'}
                </div>
              </>
            )}

            {variant === 'feature' && (
              <div className="flex flex-col items-center justify-center p-6 text-center">
                {icon && (
                  <div className="mb-3 size-7 flex items-center justify-center text-zinc-800 dark:text-white">
                    {icon}
                  </div>
                )}
                <h3 className="text-sm font-semibold text-zinc-900 dark:text-white mb-1">
                  {title || 'Feature Item'}
                </h3>
                <p className="text-xs text-zinc-500 dark:text-zinc-400 max-w-[85%] leading-relaxed">
                  {label || 'Brief description details of this card.'}
                </p>
              </div>
            )}

            {variant === 'custom' && children}
          </div>

          {/* Blueprint Drafting lines */}
          {showGridLines && (
            <>
              <div className="kanso-ray-card-line kanso-ray-card-topl" />
              <div className="kanso-ray-card-line kanso-ray-card-leftl" />
              <div className="kanso-ray-card-line kanso-ray-card-bottoml" />
              <div className="kanso-ray-card-line kanso-ray-card-rightl" />
            </>
          )}
        </div>
      </div>
    );
  }
);

RayCard.displayName = 'RayCard';

export { RayCard };

Props

Command Palette

Search for a command to run...