Glyph
A simple right click component for your logo

Getting your company's logo shouldn't be hard.

You have a great logo but god help those who are trying to find it.

People need to 'inspect element' and dig through your code, download an ultra huge zip file that has your entire brandbook and yet most people end up using the 2016 version from Google Images that's probably a PNG.

This needs to stop, it's literally hurting your brand, that's why I made — Glyph a simple way to give people your logo, in one click. Originally first seen on Linear, recently on Perplexity and Supabase, but soon everywhere on the web.

Github
'use client';

import { useState, useRef, useEffect } from 'react';
import Image from 'next/image';

interface GlyphProps {
  logoSvg: string;
  brandmarkSvg: string;
  brandKitUrl: string;
  onCopy?: (type: 'logo' | 'brandmark') => void;
  isOpen?: boolean;
  onOpenChange?: (open: boolean) => void;
}

export default function Glyph({ 
  logoSvg, 
  brandmarkSvg, 
  brandKitUrl, 
  onCopy,
  isOpen: controlledIsOpen,
  onOpenChange 
}: GlyphProps) {
  const [internalIsOpen, setInternalIsOpen] = useState(false);
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [copiedItem, setCopiedItem] = useState<'logo' | 'brandmark' | null>(null);
  const previousCopiedItem = useRef<'logo' | 'brandmark' | null>(null);
  const hasPerformedCopy = useRef(false);
  const menuRef = useRef<HTMLDivElement>(null);

  // Use controlled or uncontrolled isOpen state
  const isOpen = controlledIsOpen !== undefined ? controlledIsOpen : internalIsOpen;
  const setIsOpen = (open: boolean) => {
    if (controlledIsOpen !== undefined) {
      onOpenChange?.(open);
    } else {
      setInternalIsOpen(open);
    }
  };

  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
        setIsOpen(false);
      }
    };

    if (isOpen) {
      document.addEventListener('mousedown', handleClickOutside);
    }

    return () => {
      document.removeEventListener('mousedown', handleClickOutside);
    };
  }, [isOpen]);

  const handleContextMenu = (e: React.MouseEvent) => {
    e.preventDefault();
    setPosition({ x: e.clientX, y: e.clientY });
    setIsOpen(true);
    // Reset animation state
    hasPerformedCopy.current = false;
    previousCopiedItem.current = null;
  };

  const copyToClipboard = async (svgUrl: string, type: 'logo' | 'brandmark') => {
    try {
      const response = await fetch(svgUrl);
      const svgText = await response.text();
      await navigator.clipboard.writeText(svgText);
      previousCopiedItem.current = type;
      hasPerformedCopy.current = true;
      setCopiedItem(type);
      onCopy?.(type);
      setTimeout(() => setCopiedItem(null), 2000);
    } catch (error) {
      console.error('Failed to copy SVG to clipboard:', error);
    }
  };

  const downloadBrandKit = () => {
    // Create an anchor element and trigger download
    const link = document.createElement('a');
    link.href = brandKitUrl;
    link.download = 'brand-kit.zip'; // Suggested filename
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  };

  return (
    <>
      <div 
        className="absolute inset-0" 
        onContextMenu={handleContextMenu}
      />

      {isOpen && (
        <div 
          ref={menuRef}
          className="fixed z-50"
          style={{ 
            left: `${position.x}px`, 
            top: `${position.y}px`,
          }}
        >
          <div className="w-[240px] bg-white rounded-lg shadow-xl overflow-hidden z-10 border border-gray-200/50">
            <div className="p-0.5">
              {/* Copy logo as SVG */}
              <button
                onClick={() => copyToClipboard(logoSvg, 'logo')}
                className="flex items-center w-full px-3 py-2 text-left hover:bg-gray-100 transition-colors whitespace-nowrap relative overflow-hidden group rounded-t-[calc(0.5rem-2px)]"
              >
                <div className="relative h-5 flex-1">
                  <div className={`flex items-center absolute inset-0 ${copiedItem === 'logo' ? 'animate-slide-up-out' : copiedItem === null && previousCopiedItem.current === 'logo' && hasPerformedCopy.current ? 'animate-slide-down-in' : ''}`}>
                    <div className="w-6 h-6 flex items-center justify-center mr-2.5">
                      <Image 
                        src="/code.svg" 
                        alt="Code icon" 
                        width={18} 
                        height={18}
                        className="text-gray-600 group-hover:text-gray-900 transition-colors"
                      />
                    </div>
                    <span className="text-gray-600 group-hover:text-gray-900 text-sm font-medium transition-colors">
                      Copy logo as SVG
                    </span>
                  </div>
                  {copiedItem === 'logo' && (
                    <div className={`flex items-center absolute inset-0 ${copiedItem === null ? 'animate-slide-up-out-exit' : 'animate-slide-up-in'}`}>
                      <div className="w-6 h-6 flex items-center justify-center mr-2.5">
                        <Image 
                          src="/check.svg" 
                          alt="Check icon" 
                          width={18} 
                          height={18}
                          className="text-gray-600 group-hover:text-gray-900 transition-colors"
                        />
                      </div>
                      <span className="text-gray-600 group-hover:text-gray-900 text-sm font-medium transition-colors">
                        Copied!
                      </span>
                    </div>
                  )}
                </div>
              </button>

              {/* Copy brandmark as SVG */}
              <button
                onClick={() => copyToClipboard(brandmarkSvg, 'brandmark')}
                className="flex items-center w-full px-3 py-2 text-left hover:bg-gray-100 transition-colors whitespace-nowrap relative overflow-hidden group"
              >
                <div className="relative h-5 flex-1">
                  <div className={`flex items-center absolute inset-0 ${copiedItem === 'brandmark' ? 'animate-slide-up-out' : copiedItem === null && previousCopiedItem.current === 'brandmark' && hasPerformedCopy.current ? 'animate-slide-down-in' : ''}`}>
                    <div className="w-6 h-6 flex items-center justify-center mr-2.5">
                      <Image 
                        src="/hexagon.svg" 
                        alt="Hexagon icon" 
                        width={18} 
                        height={18}
                        className="text-gray-600 group-hover:text-gray-900 transition-colors"
                      />
                    </div>
                    <span className="text-gray-600 group-hover:text-gray-900 text-sm font-medium transition-colors">
                      Copy brandmark as SVG
                    </span>
                  </div>
                  {copiedItem === 'brandmark' && (
                    <div className={`flex items-center absolute inset-0 ${copiedItem === null ? 'animate-slide-up-out-exit' : 'animate-slide-up-in'}`}>
                      <div className="w-6 h-6 flex items-center justify-center mr-2.5">
                        <Image 
                          src="/check.svg" 
                          alt="Check icon" 
                          width={18} 
                          height={18}
                          className="text-gray-600 group-hover:text-gray-900 transition-colors"
                        />
                      </div>
                      <span className="text-gray-600 group-hover:text-gray-900 text-sm font-medium transition-colors">
                        Copied!
                      </span>
                    </div>
                  )}
                </div>
              </button>

              <div className="h-px bg-gray-200 -mx-0.5" />

              {/* Download BrandKit */}
              <button
                onClick={downloadBrandKit}
                className="flex items-center w-full px-3 py-2 text-left hover:bg-gray-100 transition-colors whitespace-nowrap group rounded-b-[calc(0.5rem-2px)]"
              >
                <div className="w-6 h-6 flex items-center justify-center mr-2.5">
                  <Image 
                    src="/download.svg" 
                    alt="Download icon" 
                    width={18} 
                    height={18}
                    className="text-gray-600 group-hover:text-gray-900 transition-colors"
                  />
                </div>
                <span className="text-gray-600 group-hover:text-gray-900 text-sm font-medium transition-colors">Download BrandKit</span>
              </button>
            </div>
          </div>
        </div>
      )}
    </>
  );
}

Props

PropTypeRequiredDescription
logoSvgstringYesURL to the full logo SVG file
brandmarkSvgstringYesURL to the brandmark/icon SVG file
brandKitUrlstringYesURL to download the brand kit zip file
onCopy(type: 'logo' | 'brandmark') => voidNoCallback when an SVG is copied to clipboard
isOpenbooleanNoControl the open state of the menu
onOpenChange(open: boolean) => voidNoCallback when the menu open state changes