Connect Button
Wallet connect button with modal and dropdown. Base variant includes address, copy, and disconnect.
Preview
"use client";import { formatAddress } from "@solana/connector";import { useConnector } from "@solana/connector/react";import { ChevronDown } from "lucide-react";import { useState } from "react";import { Button } from "@/components/ui/button";import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger,} from "@/components/ui/dropdown-menu";import { Spinner } from "@/components/ui/spinner";import { cn } from "@/lib/utils";import { WalletDropdownContent, type WalletDropdownContentProps,} from "./wallet-dropdown-content";import { WalletIcon } from "./wallet-icon";import { WalletModal } from "./wallet-modal";interface ConnectButtonProps { className?: string; /** * Custom dropdown content component. * Defaults to base WalletDropdownContent (address + copy + disconnect). * Pass a custom component to add balance, network switcher, tokens, etc. * * @example * ```tsx * <ConnectButton dropdownContent={WalletDropdownWithBalance} /> * ``` */ dropdownContent?: React.ComponentType<WalletDropdownContentProps>;}export function ConnectButton({ className, dropdownContent: DropdownContent = WalletDropdownContent,}: ConnectButtonProps) { const [isModalOpen, setIsModalOpen] = useState(false); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const { isConnected, isConnecting, account, connector } = useConnector(); if (isConnected && account && connector) { const shortAddress = formatAddress(account); const walletIcon = connector.icon || undefined; return ( <DropdownMenu open={isDropdownOpen} onOpenChange={setIsDropdownOpen}> <DropdownMenuTrigger asChild> <Button variant="default" size="lg" className={cn("gap-2", className)} > <WalletIcon src={walletIcon} alt={connector.name} className="h-5 w-5 shrink-0" /> <span className="text-xs">{shortAddress}</span> <ChevronDown className={cn( "h-4 w-4 opacity-50 transition-transform duration-200 ease-in-out", isDropdownOpen && "-rotate-180", )} /> </Button> </DropdownMenuTrigger> <DropdownMenuContent align="end" side="bottom" className="w-auto rounded-2xl p-0 shadow-md" > <DropdownContent selectedAccount={account} walletIcon={walletIcon} walletName={connector.name} /> </DropdownMenuContent> </DropdownMenu> ); } return ( <> <Button size="lg" variant="default" onClick={() => setIsModalOpen(true)} className={className} disabled={isConnecting} > {isConnecting ? ( <> <Spinner className="h-4 w-4" /> <span className="text-xs">Connecting...</span> </> ) : ( "Connect Wallet" )} </Button> <WalletModal open={isModalOpen} onOpenChange={setIsModalOpen} /> </> );}Prerequisites
Complete the installation guide before using this component — it covers project setup, environment variables, and wiring up NitsoProvider.
Try the Demo
Install the demo to see it working immediately without any wiring:
npx shadcn@latest add https://nitso.fun/r/connect-button-demo.jsonpnpm dlx shadcn@latest add https://nitso.fun/r/connect-button-demo.jsonyarn dlx shadcn@latest add https://nitso.fun/r/connect-button-demo.jsonbunx --bun shadcn@latest add https://nitso.fun/r/connect-button-demo.jsonThis installs connect-button-demo.tsx into your components/ folder. Drop it anywhere:
import { ConnectButtonDemo } from '@/components/connect-button-demo';
export default function Home() {
return (
<nav>
<ConnectButtonDemo />
</nav>
);
}Delete it when you're ready to wire things up yourself.
Installation
npx shadcn@latest add https://nitso.fun/r/connect-button.jsonpnpm dlx shadcn@latest add https://nitso.fun/r/connect-button.jsonyarn dlx shadcn@latest add https://nitso.fun/r/connect-button.jsonbunx --bun shadcn@latest add https://nitso.fun/r/connect-button.jsonInstall dependencies
npm install @solana/connector @solana/web3.jspnpm add @solana/connector @solana/web3.jsyarn add @solana/connector @solana/web3.jsbun add @solana/connector @solana/web3.jsInstall shadcn UI components
npx shadcn@latest badge button dialog dropdown-menu accordion separator spinnerpnpm dlx shadcn@latest badge button dialog dropdown-menu accordion separator spinneryarn dlx shadcn@latest badge button dialog dropdown-menu accordion separator spinnerbunx --bun shadcn@latest badge button dialog dropdown-menu accordion separator spinnerCopy the source code
"use client";import { formatAddress } from "@solana/connector";import { useConnector } from "@solana/connector/react";import { ChevronDown } from "lucide-react";import { useState } from "react";import { Button } from "@/components/ui/button";import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger,} from "@/components/ui/dropdown-menu";import { Spinner } from "@/components/ui/spinner";import { cn } from "@/lib/utils";import { WalletDropdownContent, type WalletDropdownContentProps,} from "./wallet-dropdown-content";import { WalletIcon } from "./wallet-icon";import { WalletModal } from "./wallet-modal";interface ConnectButtonProps { className?: string; /** * Custom dropdown content component. * Defaults to base WalletDropdownContent (address + copy + disconnect). * Pass a custom component to add balance, network switcher, tokens, etc. * * @example * ```tsx * <ConnectButton dropdownContent={WalletDropdownWithBalance} /> * ``` */ dropdownContent?: React.ComponentType<WalletDropdownContentProps>;}export function ConnectButton({ className, dropdownContent: DropdownContent = WalletDropdownContent,}: ConnectButtonProps) { const [isModalOpen, setIsModalOpen] = useState(false); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const { isConnected, isConnecting, account, connector } = useConnector(); if (isConnected && account && connector) { const shortAddress = formatAddress(account); const walletIcon = connector.icon || undefined; return ( <DropdownMenu open={isDropdownOpen} onOpenChange={setIsDropdownOpen}> <DropdownMenuTrigger asChild> <Button variant="default" size="lg" className={cn("gap-2", className)} > <WalletIcon src={walletIcon} alt={connector.name} className="h-5 w-5 shrink-0" /> <span className="text-xs">{shortAddress}</span> <ChevronDown className={cn( "h-4 w-4 opacity-50 transition-transform duration-200 ease-in-out", isDropdownOpen && "-rotate-180", )} /> </Button> </DropdownMenuTrigger> <DropdownMenuContent align="end" side="bottom" className="w-auto rounded-2xl p-0 shadow-md" > <DropdownContent selectedAccount={account} walletIcon={walletIcon} walletName={connector.name} /> </DropdownMenuContent> </DropdownMenu> ); } return ( <> <Button size="lg" variant="default" onClick={() => setIsModalOpen(true)} className={className} disabled={isConnecting} > {isConnecting ? ( <> <Spinner className="h-4 w-4" /> <span className="text-xs">Connecting...</span> </> ) : ( "Connect Wallet" )} </Button> <WalletModal open={isModalOpen} onOpenChange={setIsModalOpen} /> </> );}Usage
import { ConnectButton } from "@/components/nitso/connect-button/";
export function Header() {
return (
<nav>
<ConnectButton />
</nav>
);
}Addons
The base dropdown shows address, copy, and disconnect. Install addons to extend it:
| Addon | Description |
|---|---|
wallet-balance | SOL balance display |
network-switcher | Mainnet / Devnet / Testnet switcher |
token-list | SPL token holdings |
transaction-list | Recent transaction history |
wallet-connect-modal | WalletConnect QR code modal |
See each addon's docs for wiring instructions.
Props
ConnectButton
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Additional CSS classes for the button |
dropdownContent | ComponentType<WalletDropdownContentProps> | WalletDropdownContent | Custom dropdown component rendered when wallet is connected |
WalletDropdownContent
| Prop | Type | Default | Description |
|---|---|---|---|
selectedAccount | string | — | Connected wallet address (required) |
walletIcon | string | — | Wallet icon URL |
walletName | string | — | Wallet name e.g. Phantom (required) |
actions | ReactNode | — | Extra action buttons next to copy, e.g. NetworkSwitcherButton |
children | ReactNode | — | Content between header and disconnect, e.g. WalletBalance |
WalletModal
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | — | Whether the modal is visible (required) |
onOpenChange | (open: boolean) => void | — | Callback when open state changes (required) |