Chain Selector

Header popover to switch through various blockchain networks

BlockchainsNavigation
loading

Technologies

Installation

Install Web3 dependencies

npm install @reown/appkit @reown/appkit-adapter-wagmi wagmi viem @tanstack/react-query

Add Shadcn components

npx shadcn@latest add popover

Add utils file

Create a file named lib/utils.ts and add the following code:
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
  
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

Copy and paste source code

"use client";

import React, { useState, Dispatch, SetStateAction, useEffect } from "react";
import Image from "next/image";
import {
  mainnet,
  avalanche,
  bsc,
  arbitrum,
  polygon,
} from "@reown/appkit/networks";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";
import { ChevronDown, Check } from "lucide-react";
import { useChainId, useSwitchChain, useAccount } from "wagmi";
import { cn } from "@/lib/utils";
import { useAppKit } from "@reown/appkit/react";

interface Props {
  chainName: string;
  chainId: number;
  activeChainId: number;
  setOpen: Dispatch<SetStateAction<boolean>>;
}

const chains = [
  {
    ...mainnet,
    imageUrl:
      "https://raw.githubusercontent.com/coinwink/cryptocurrency-logos/master/coins/16x16/1027.png",
    name: "Ethereum",
  },
  {
    ...bsc,
    imageUrl:
      "https://raw.githubusercontent.com/coinwink/cryptocurrency-logos/master/coins/16x16/1839.png",
    name: "Binance Smart Chain",
  },
  {
    ...avalanche,
    imageUrl:
      "https://raw.githubusercontent.com/coinwink/cryptocurrency-logos/master/coins/16x16/5805.png",
    name: "Avalanche",
  },
  {
    ...polygon,
    imageUrl: "https://cryptologos.cc/logos/thumbs/polygon.png?v=034",
    name: "Polygon",
  },
  {
    ...arbitrum,
    imageUrl: "https://cryptologos.cc/logos/thumbs/arbitrum.png?v=034",
    name: "Arbitrum",
  },
  // add more chains here
];

const ChainSelector = () => {
  const [open, setOpen] = useState<boolean>(false);
  const [isMounted, setIsMounted] = useState<boolean>(false);
  const currentChainId = useChainId();
  const { isConnected } = useAccount();

  useEffect(() => {
    setIsMounted(true);
  }, []);

  const isChainPresentInList = (chainId: number) =>
    chains.some((chain) => chain.id === chainId);

  console.log(chains, currentChainId);

  if (!isMounted) {
    // Render a fallback during SSR to avoid hydration issues from Wagmi/ReownAppKit
    return (
      <div className="bg-[#0a0a0a] px-4">
        <div className="flex justify-between items-center max-w-7xl mx-auto">
          <DesktopLogo className="w-[160px] mr-10" />

          <div
            className="w-[250px] h-[50px] bg-[#171717] rounded-lg border-[0.5px] border-white/15 flex justify-between items-center gap-2 
          text-[#A1A1AA] px-4"
          >
            Loading
            <div className="w-3.5 h-3.5 border-2 border-gray-400 border-b-transparent rounded-full inline-block box-border animate-spin" />{" "}
          </div>
        </div>
      </div>
    );
  }

  return (
    <div className="bg-[#0a0a0a] px-4">
      <div className="max-w-7xl mx-auto flex justify-between items-center">
        <DesktopLogo className="w-[160px] mr-10" />

        <Popover open={open} onOpenChange={() => setOpen(!open)}>
          <PopoverTrigger
            asChild
            className={cn(
              "w-[250px] transition-all duration-150 cursor-pointer group border-[0.5px] border-white/15 rounded-[8px]",
              open
                ? "border-white/15 border-[0.5px]"
                : "hover:shadow-[0_0_8px_rgba(163,249,27,1)]"
            )}
          >
            <div
              className={cn(
                "flex items-center justify-between bg-[#171717] px-3 h-[50px]",
                currentChainId &&
                  !isChainPresentInList(currentChainId) &&
                  "bg-red-500 bg-opacity-15"
              )}
            >
              <div className="flex items-center gap-2">
                {isConnected &&
                  currentChainId &&
                  isChainPresentInList(currentChainId) && (
                    <Image
                      src={
                        currentChainId
                          ? chains.filter(
                              (item) => item.id === currentChainId
                            )[0].imageUrl
                          : chains[0].imageUrl
                      }
                      width={18}
                      height={18}
                      alt="chain"
                      unoptimized
                      className="rounded-full"
                    />
                  )}
                <p className="text-sm">
                  {currentChainId && !isChainPresentInList(currentChainId)
                    ? "Invalid chain"
                    : currentChainId && isConnected
                    ? chains.filter((item) => item.id === currentChainId)[0]
                        .name
                    : "Not connected"}
                </p>
              </div>
              <ChevronDown
                size={23}
                className={cn(
                  "transition-all duration-300",
                  open ? "rotate-180 " : "rotate-0"
                )}
              />
            </div>
          </PopoverTrigger>
          <PopoverContent
            align="end"
            className="border-white/15 border-[0.5px] bg-[#171717] flex flex-col p-0.5 w-[250px] text-white"
          >
            {chains.map((chain) => (
              <ChainCard
                key={chain.id}
                chainId={Number(chain.id)}
                chainName={chain.name}
                activeChainId={currentChainId}
                setOpen={setOpen}
              />
            ))}
          </PopoverContent>
        </Popover>
      </div>
    </div>
  );
};

export default ChainSelector;

interface Props {
  chainName: string;
  chainId: number;
  activeChainId: number;
  setOpen: Dispatch<SetStateAction<boolean>>;
}

const ChainCard = ({ chainName, chainId, activeChainId, setOpen }: Props) => {
  const { switchChain, status } = useSwitchChain();
  const { isConnected } = useAccount();
  const { open } = useAppKit();

  useEffect(() => {
    if (status === "success" || status === "error") setOpen(false);
  }, [status]);

  return (
    <button
      onClick={
        isConnected ? () => switchChain({ chainId: chainId }) : () => open()
      }
      className="flex flex-col bg-gray-300 bg-opacity-0 hover:bg-opacity-10 px-2 py-2.5  focus:ring-0 focus:outline-none"
    >
      <div className="flex justify-between items-center w-full">
        <div className="flex items-center gap-2  ">
          <Image
            src={
              activeChainId
                ? chains.filter((item) => item.id === chainId)[0].imageUrl
                : chains[0].imageUrl
            }
            width={20}
            height={20}
            alt="chain"
            unoptimized
            className="rounded-full"
          />
          <p className="text-sm">{chainName}</p>
        </div>

        {chainId === activeChainId && status !== "pending" && isConnected && (
          <Check size={20} className="text-[#A3F91B]" />
        )}

        {status === "pending" && (
          <div className="text-[#A3F91B] w-3.5 h-3.5 border-2 border-[#a3f91b] border-b-transparent rounded-full inline-block box-border animate-spin" />
        )}
      </div>

      {status === "pending" && (
        <p className="text-xs text-white/50 mt-1 ">Approve in wallet...</p>
      )}
    </button>
  );
};

const DesktopLogo: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
  <svg
    width="799"
    height="92"
    viewBox="0 0 799 92"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    {...props}
  >
    <rect y="0.0273438" width="65.9697" height="18.554" fill="#FFFAFA" />
    <rect y="72.4766" width="65.9697" height="18.554" fill="#FFFAFA" />
    <rect
      x="46.8271"
      y="91.0312"
      width="54.7784"
      height="19.143"
      transform="rotate(-90 46.8271 91.0312)"
      fill="#FFFAFA"
    />
    <rect
      x="90.1191"
      y="72.7695"
      width="54.7784"
      height="19.143"
      transform="rotate(-90 90.1191 72.7695)"
      fill="#FFFAFA"
    />
    <rect
      x="769.013"
      y="72.7422"
      width="54.7784"
      height="19.143"
      transform="rotate(-90 769.013 72.7422)"
      fill="#FFFAFA"
    />
    <rect
      x="208.512"
      y="91.0312"
      width="90.7083"
      height="19.143"
      transform="rotate(-90 208.512 91.0312)"
      fill="#FFFAFA"
    />
    <rect
      x="248.27"
      y="91.0312"
      width="90.7083"
      height="19.143"
      transform="rotate(-90 248.27 91.0312)"
      fill="#FFFAFA"
    />
    <rect
      x="321.603"
      y="72.1797"
      width="71.8598"
      height="19.143"
      transform="rotate(-90 321.603 72.1797)"
      fill="#FFFAFA"
    />
    <rect
      x="370.49"
      y="91.3242"
      width="71.8598"
      height="19.143"
      transform="rotate(-90 370.49 91.3242)"
      fill="#FFFAFA"
    />
    <rect
      x="403.77"
      y="72.1836"
      width="71.8598"
      height="19.143"
      transform="rotate(-90 403.77 72.1836)"
      fill="#FFFAFA"
    />
    <rect
      x="670"
      y="71.8594"
      width="71.8598"
      height="19.143"
      transform="rotate(-90 670 71.8594)"
      fill="#FFFAFA"
    />
    <rect
      x="728"
      y="71.8594"
      width="71.8598"
      height="19.143"
      transform="rotate(-90 728 71.8594)"
      fill="#FFFAFA"
    />
    <rect
      x="470.918"
      y="91.0312"
      width="90.7083"
      height="19.143"
      transform="rotate(-90 470.918 91.0312)"
      fill="#FFFAFA"
    />
    <rect
      x="518.922"
      y="91.0312"
      width="71.5653"
      height="19.143"
      transform="rotate(-90 518.922 91.0312)"
      fill="#FFFAFA"
    />
    <rect
      x="552.791"
      y="91.0312"
      width="90.7083"
      height="19.4375"
      transform="rotate(-90 552.791 91.0312)"
      fill="#FFFAFA"
    />
    <rect
      x="287.439"
      y="91.0312"
      width="71.5653"
      height="19.4375"
      transform="rotate(-90 287.439 91.0312)"
      fill="#FFFAFA"
    />
    <rect
      x="80.1064"
      y="0.0273438"
      width="39.464"
      height="19.143"
      fill="#FFFAFA"
    />
    <rect x="759" width="39.464" height="19.143" fill="#FFFAFA" />
    <rect
      x="148.727"
      y="32.1289"
      width="29.7453"
      height="20.0265"
      fill="#FFFAFA"
    />
    <rect
      x="148.727"
      y="72.4766"
      width="48.8883"
      height="18.554"
      fill="#FFFAFA"
    />
    <rect
      x="178.472"
      y="19.4648"
      width="19.143"
      height="12.6638"
      fill="#FFFAFA"
    />
    <rect
      x="128.994"
      y="52.1562"
      width="19.732"
      height="38.875"
      fill="#FFFAFA"
    />
    <rect
      x="128.994"
      y="0.320312"
      width="68.6203"
      height="19.143"
      fill="#FFFAFA"
    />
    <rect
      x="227.654"
      y="0.320312"
      width="68.6203"
      height="18.8485"
      fill="#FFFAFA"
    />
    <rect
      x="340.745"
      y="0.320312"
      width="29.7453"
      height="18.8485"
      fill="#FFFAFA"
    />
    <rect
      x="340.745"
      y="72.4766"
      width="29.7453"
      height="18.8485"
      fill="#FFFAFA"
    />
    <rect
      x="422.913"
      y="72.1836"
      width="38.875"
      height="18.8485"
      fill="#FFFAFA"
    />
    <rect
      x="689.144"
      y="71.8594"
      width="38.875"
      height="18.8485"
      fill="#FFFAFA"
    />
    <rect
      x="479.164"
      y="0.320312"
      width="39.1695"
      height="18.8485"
      fill="#FFFAFA"
    />
    <rect
      x="490.061"
      y="47.7383"
      width="28.8617"
      height="18.554"
      fill="#FFFAFA"
    />
    <rect
      x="572.229"
      y="33.8945"
      width="30.0398"
      height="18.8485"
      fill="#FFFAFA"
    />
    <rect
      x="572.229"
      y="72.4766"
      width="49.7718"
      height="18.554"
      fill="#FFFAFA"
    />
    <rect
      x="572.229"
      y="0.320312"
      width="48.5938"
      height="18.554"
      fill="#FFFAFA"
    />
    <rect
      x="602.268"
      y="18.875"
      width="18.554"
      height="15.0199"
      fill="#FFFAFA"
    />
    <rect
      x="602.268"
      y="52.7422"
      width="19.732"
      height="19.732"
      fill="#FFFAFA"
    />
    <rect
      x="80.1064"
      y="71.8867"
      width="39.464"
      height="19.143"
      fill="#FFFAFA"
    />
    <rect x="759" y="71.8594" width="39.464" height="19.143" fill="#FFFAFA" />
    <rect
      x="32.1016"
      y="36.25"
      width="20.0265"
      height="18.554"
      fill="#FFFAFA"
    />
    <rect
      y="91.0312"
      width="72.4489"
      height="19.143"
      transform="rotate(-90 0 91.0312)"
      fill="#FFFAFA"
    />
  </svg>
);

See something you like?

Take your project further with our advanced custom development.

"One of the only full-stack Web3 component libraries i've seen in the space so far. Ten out of ten recommended. Saved me a ton of time. Can't wait to see what templates they release next."

Samy

Side projects builder