RainbowKit Frontend Integration
Writing a Connect Wallet button from scratch — lose several days on edge cases: supporting 15+ wallets, deep linking for mobile, QR codes, auto-reconnect, disconnect handling. RainbowKit solves all of this in 30 lines of configuration on top of wagmi.
Installation and Basic Configuration
npm install @rainbow-me/rainbowkit wagmi viem @tanstack/react-query
// app/providers.tsx
import "@rainbow-me/rainbowkit/styles.css";
import { RainbowKitProvider, getDefaultConfig } from "@rainbow-me/rainbowkit";
import { WagmiProvider } from "wagmi";
import { mainnet, polygon, arbitrum } from "wagmi/chains";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const config = getDefaultConfig({
appName: "My dApp",
projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID!, // required
chains: [mainnet, polygon, arbitrum],
});
const queryClient = new QueryClient();
export function Providers({ children }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>{children}</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
);
}
Get WalletConnect Project ID from cloud.walletconnect.com — free tier is sufficient for most projects.
ConnectButton: Customization
Standard <ConnectButton /> is good for prototypes. In production almost always need a custom look:
import { ConnectButton } from "@rainbow-me/rainbowkit";
export function CustomConnectButton() {
return (
<ConnectButton.Custom>
{({ account, chain, openAccountModal, openChainModal, openConnectModal, mounted }) => {
const ready = mounted;
const connected = ready && account && chain;
return (
<div {...(!ready && { "aria-hidden": true, style: { opacity: 0 } })}>
{!connected ? (
<button onClick={openConnectModal}>Connect Wallet</button>
) : chain.unsupported ? (
<button onClick={openChainModal}>Wrong Network</button>
) : (
<div>
<button onClick={openChainModal}>{chain.name}</button>
<button onClick={openAccountModal}>
{account.displayBalance} · {account.displayName}
</button>
</div>
)}
</div>
);
}}
</ConnectButton.Custom>
);
}
Theming
RainbowKit supports three built-in themes (lightTheme, darkTheme, midnightTheme) with customization via theme prop:
import { lightTheme } from "@rainbow-me/rainbowkit";
<RainbowKitProvider
theme={lightTheme({
accentColor: "#6366f1", // indigo-500
accentColorForeground: "white",
borderRadius: "medium",
fontStack: "system",
})}
>
For dynamic dark/light theme switching, pass computed value from context or next-themes.
Custom Wallets and Display Order
RainbowKit shows wallets in its order by default. For a custom list:
import {
connectorsForWallets,
metaMaskWallet,
coinbaseWallet,
walletConnectWallet,
injectedWallet,
} from "@rainbow-me/rainbowkit/wallets";
const connectors = connectorsForWallets(
[
{
groupName: "Recommended",
wallets: [metaMaskWallet, coinbaseWallet],
},
{
groupName: "Others",
wallets: [walletConnectWallet, injectedWallet],
},
],
{ appName: "My dApp", projectId: "..." }
);
You can add a custom wallet (e.g., Safe or Rabby) via the Wallet interface from the package.
Authentication: SIWE (Sign-In with Ethereum)
RainbowKit has built-in EIP-4361 (Sign-In with Ethereum) support — user signs message instead of password:
import { RainbowKitAuthenticationProvider, createAuthenticationAdapter } from "@rainbow-me/rainbowkit";
import { SiweMessage } from "siwe";
const authAdapter = createAuthenticationAdapter({
getNonce: async () => {
const res = await fetch("/api/auth/nonce");
return res.text();
},
createMessage: ({ nonce, address, chainId }) =>
new SiweMessage({
domain: window.location.host,
address,
statement: "Sign in to My dApp",
uri: window.location.origin,
version: "1",
chainId,
nonce,
}),
getMessageBody: ({ message }) => message.prepareMessage(),
verify: async ({ message, signature }) => {
const res = await fetch("/api/auth/verify", {
method: "POST",
body: JSON.stringify({ message, signature }),
});
return res.ok;
},
signOut: () => fetch("/api/auth/logout", { method: "POST" }),
});
Typical Problems
SSR / hydration — RainbowKit uses window, which breaks SSR. In Next.js App Router wrap Providers in client component ("use client"). For Pages Router — dynamic(() => import('./Providers'), { ssr: false }) if problem persists.
Mobile deep linking — works out of the box for MetaMask Mobile and Coinbase Wallet via WalletConnect v2. Check that projectId is correctly configured in WalletConnect Cloud with the app domain.
Multiple RainbowKit instances — if RainbowKitProvider renders multiple times (architecture error), we get conflicts. Provider should be exactly one, as high in component tree as possible.







