Migrate from RainbowKit
Map RainbowKit concepts to Wyllet and switch a dApp over step by step.
RainbowKit and Wyllet solve the same problem on the same foundation: both sit on top of wagmi, viem and react-query, and both give you a provider, a connect button and a config helper. Most of a migration is renaming. This guide maps RainbowKit's concepts to Wyllet's, walks through the switch step by step, and lists what works differently. For a side-by-side of features, licenses and dependencies, see How Wyllet compares.
Concept map
| RainbowKit | Wyllet |
|---|---|
RainbowKitProvider | WylletProvider |
ConnectButton | WylletButton |
ConnectButton.Custom | useWylletButton() |
getDefaultConfig | getDefaultConfig (same idea, walletConnectProjectId instead of projectId) |
darkTheme() / lightTheme() | obsidianTheme() / porcelainTheme() (also auroraTheme(), terminalTheme()) |
useConnectModal().openConnectModal | useWyllet().openConnect |
useAccountModal().openAccountModal | useWyllet().openAccount |
useChainModal().openChainModal | useWyllet().openTab('networks') |
RainbowKitAuthenticationProvider + adapter | auth prop on WylletProvider |
| Custom wallet objects | Wallet objects |
Wallet groups (groupName) | A flat wallets array |
useAddRecentTransaction | useWylletActivity().addTransaction |
import '@rainbow-me/rainbowkit/styles.css' | Not needed (styles are injected) |
Step by step
1. Swap the packages
npm uninstall @rainbow-me/rainbowkit
npm install wyllet
# Only if you use WalletConnect (QR codes and mobile wallets):
npm install @walletconnect/ethereum-providerWyllet's peers are react, react-dom, wagmi, viem and @tanstack/react-query, which your app already has. @walletconnect/ethereum-provider is an optional peer you install and pin yourself.
2. Update the config
Wyllet's getDefaultConfig takes the same kind of input and returns a wagmi config. Rename projectId to walletConnectProjectId. Other wagmi createConfig options, such as ssr, pass straight through.
import { getDefaultConfig } from '@rainbow-me/rainbowkit'
import { base, mainnet } from 'wagmi/chains'
export const config = getDefaultConfig({
appName: 'My dApp',
projectId: 'YOUR_PROJECT_ID',
chains: [mainnet, base],
ssr: true,
})import { getDefaultConfig } from 'wyllet'
import { base, mainnet } from 'viem/chains'
export const config = getDefaultConfig({
appName: 'My dApp',
walletConnectProjectId: 'YOUR_PROJECT_ID',
chains: [mainnet, base],
ssr: true,
})Wyllet's getDefaultConfig also accepts appDescription, appUrl, appIcon, transports, wallets and extra connectors. See Configuration.
3. Replace the provider
Keep WagmiProvider and QueryClientProvider as they are. Replace RainbowKitProvider with WylletProvider and delete the RainbowKit stylesheet import.
'use client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { WagmiProvider } from 'wagmi'
import { WylletProvider } from 'wyllet'
import { config } from './wagmi'
const queryClient = new QueryClient()
export function Providers({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<WylletProvider>{children}</WylletProvider>
</QueryClientProvider>
</WagmiProvider>
)
}A few RainbowKit provider props have direct counterparts:
| RainbowKit prop | Wyllet |
|---|---|
theme | theme (a theme, or a { light, dark } pair) |
appInfo.appName | appName in getDefaultConfig, or appInfo.name on the provider |
appInfo.learnMoreUrl | appInfo.learnMoreUrl |
appInfo.disclaimer | disclaimer |
avatar | avatar (receives address, ensImage and size) |
locale | messages (override any string; see Translations) |
initialChain | The first chain in your config is the default |
4. Replace the button
// Before
<ConnectButton label="Sign in" />
// After
<WylletButton label="Sign in" />WylletButton has a display prop instead of accountStatus and showBalance: 'full' shows the avatar, name and balance, 'compact' the avatar and name, and 'avatar' the avatar only. The network appears as a badge on the avatar, so there is no separate chain button. See WylletButton.
5. Replace custom buttons
ConnectButton.Custom is a render prop. Wyllet uses a hook instead, so you can call it in any component.
import { useWylletButton } from 'wyllet'
function CustomButton() {
const { mounted, account, chain, openConnect, openAccount } = useWylletButton()
if (!mounted) return null
if (!account) return <button onClick={(e) => openConnect(e.currentTarget)}>Connect</button>
if (chain?.unsupported) return <button onClick={(e) => openAccount(e.currentTarget, 'networks')}>Wrong network</button>
return <button onClick={(e) => openAccount(e.currentTarget)}>{account.displayName}</button>
}ConnectButton.Custom | useWylletButton() |
|---|---|
mounted | mounted |
account.displayName | account.displayName |
account.displayBalance | account.balance.formatted and account.balance.symbol |
chain.unsupported | chain.unsupported |
openConnectModal() | openConnect(anchor?) |
openAccountModal() | openAccount(anchor?) |
openChainModal() | openAccount(anchor, 'networks') |
authenticationStatus | authStatus |
Passing the clicked element as anchor lets the popover presentation position itself next to your button.
6. Replace the modal hooks
const { openConnect, openAccount, openTab, close } = useWyllet()
openConnect() // was openConnectModal()
openAccount() // was openAccountModal()
openTab('networks') // was openChainModal()7. Move the theme
// Before
<RainbowKitProvider theme={darkTheme({ accentColor: '#7b3fe4', accentColorForeground: 'white' })} />
// After
<WylletProvider theme={obsidianTheme({ accentColor: '#7b3fe4', accentColorForeground: 'white' })} />accentColor and accentColorForeground keep their names. The other knobs differ: radius takes 'sharp' | 'soft' | 'round' | 'pill', overlayBlur takes 'none' | 'subtle' | 'heavy', and fontFamily takes any font stack. To follow the OS color scheme, pass a pair:
<WylletProvider theme={{ light: porcelainTheme(), dark: obsidianTheme() }} colorMode="auto" />Every token is also a CSS variable (such as --wy-colors-accent), and the classNames prop attaches your own classes to individual elements. See Theming and Class names.
8. Move authentication
In RainbowKit you wrap the app in RainbowKitAuthenticationProvider, pass an adapter, and supply the authentication status yourself. In Wyllet you pass an adapter to the auth prop and Wyllet tracks the status.
import type { WylletAuthAdapter } from 'wyllet'
const auth: WylletAuthAdapter = {
getNonce: () => fetch('/api/nonce').then((r) => r.text()),
verify: ({ message, signature }) =>
fetch('/api/verify', { method: 'POST', body: JSON.stringify({ message, signature }) }).then((r) => r.ok),
signOut: () => fetch('/api/logout', { method: 'POST' }).then(() => {}),
getSession: () => fetch('/api/me').then((r) => (r.ok ? r.json() : null)),
}
<WylletProvider auth={auth}>{children}</WylletProvider>createMessageis optional. By default Wyllet builds an EIP-4361 message withviem/siwe.getSessionrestores an existing session on page load. Return{ address }ornull.- Disconnecting or switching accounts calls
signOut. - Read the status with
useWylletAuth().
Your backend nonce and verify endpoints do not need to change. See Sign-In With Ethereum.
9. Move the wallet list
RainbowKit groups wallets under a groupName. Wyllet takes one ordered array of called presets, and groups wallets in the UI on its own: detected and recently used first, then the rest.
wallets: [{ groupName: 'Recommended', wallets: [metaMaskWallet, rainbowWallet, walletConnectWallet] }]import { metaMaskWallet, rainbowWallet, walletConnectWallet } from 'wyllet'
wallets: [metaMaskWallet(), rainbowWallet(), walletConnectWallet()]Wallets with featured: false are listed in a lower group. Installed browser wallets you did not list are still detected through EIP-6963. Wyllet bundles official artwork only for its default wallets (MetaMask, Phantom, Rabby, OKX and WalletConnect). coinbaseWallet(), rainbowWallet() and trustWallet() show a neutral monogram unless the extension is installed or you pass an icon through overrides. See Wallets.
10. Port custom wallets
A custom wallet is a plain object. The main renames are iconUrl to icon and the mobile WalletConnect link to walletConnect.mobileUri.
import type { Wallet } from 'wyllet'
const acme: Wallet = {
id: 'acme',
name: 'Acme',
icon: 'https://acme.xyz/icon.png',
iconBackground: '#ffffff',
rdns: 'xyz.acme',
walletConnect: { mobileUri: (uri) => `acme://wc?uri=${encodeURIComponent(uri)}` },
downloadUrls: { browserExtension: 'https://acme.xyz/download', ios: '…', android: '…' },
}To give a wallet a dedicated wagmi connector, set createConnector to a function that takes no arguments and returns a wagmi CreateConnectorFn.
11. Move recent transactions
const { addTransaction } = useWylletActivity()
const hash = await writeContractAsync({ /* ... */ })
addTransaction({ hash, description: 'Mint 1 NFT' })There is no showRecentTransactions flag. Transactions appear in the account panel's Activity tab, with pending and confirmed toasts. See Activity and transactions.
What works differently
- Presentations. The panel opens as a side drawer by default. Set
appearance.presentationto'popover'to anchor it to the button, or'modal'for a centered dialog. Phones get a draggable bottom sheet. See Presentations. - Activity. The Activity tab records connects, disconnects, network switches and sign-ins as well as transactions, and you can add your own entries with
log. Pending transactions are watched again after a reload. - Toasts.
useWylletToast()gives you themed, promise-aware notifications that match the panel. See Toasts. - Tokens. The account panel has a Tokens tab with balances for a token list you define in code, with pinning, sorting and optional user import. See Tokens.
- Networks. Switching networks happens in the account panel's Networks tab, which also shows the live gas price and a wrong-network banner.
- Gating.
ConnectGaterenders content only when the user is connected, on a supported network and signed in. See ConnectGate. - All wallets. With a WalletConnect project id, the WalletConnect option opens a searchable directory of wallets from the WalletConnect registry, and each opens its own app on phones. See All wallets.
- Styles. Wyllet injects its CSS, so there is no stylesheet import. Set
injectStyles={false}and importwyllet/styles.cssif you want to load it yourself. - Keyboard. Set
shortcut="mod+k"to toggle the panel from the keyboard. In the wallet list, arrow keys move and number keys pick a wallet. - Dependencies. Wyllet has no third-party runtime dependencies. See Security.
Checklist
@rainbow-me/rainbowkitremoved frompackage.jsonand the lockfile.- The RainbowKit stylesheet import removed.
projectIdrenamed towalletConnectProjectId.@walletconnect/ethereum-providerinstalled if you use WalletConnect.- Every
ConnectButton,ConnectButton.Customand modal hook replaced. - The auth adapter moved to the
authprop, withgetSessionif you restore sessions. - Your app still connects, switches networks and signs in. Try it in the playground first if you want to compare settings.