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
This component uses the Reown Appkit for web3 wallet connectivity which requires additional configurations to your project. To ensure it works properly follow the Install Reown Appkit setup guide.
Add Shadcn components
npx shadcn@latest add popover
This component uses the Shadcn component library which requires additional configurations to your project. To ensure it works properly follow the Install Shadcn UI setup guide.
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>
);
"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