SelectTokenModal

The popup token picker with search, common tokens, recent tokens, and keyboard navigation

SelectTokenModal is the popup token picker. Your users click a trigger (a button, an avatar, whatever you put inside it), the modal opens, they search and pick. That is the flow.

Tokens come from the TokenKit API. Anything launched on tokenkithq.io shows up here automatically, alongside the usual blue chips.

Basic Usage

Wrap your app in TokenKitWrapper once, then drop the modal in wherever you need a token picker.

Code
tsx
1import { useState } from 'react';
2import {
3 TokenKitWrapper,
4 SelectTokenModal,
5 themes,
6 type IToken,
7} from 'starknet-tokenkit';
8 
9function App() {
10 const [selectedToken, setSelectedToken] = useState<IToken | null>(null);
11 
12 return (
13 <TokenKitWrapper
14 network="SN_MAIN"
15 apiKey="your-api-key"
16 mainnetEndpoint="https://api.tokenkithq.io"
17 sepoliaEndpoint="https://api.sepolia.tokenkithq.io"
18 themeObject={themes.dark}
19 options={{ enableRecent: true }}
20 >
21 <SelectTokenModal
22 callBackFunc={(token) => setSelectedToken(token)}
23 selectedToken={selectedToken}
24 >
25 <button>
26 {selectedToken ? selectedToken.symbol : 'Select Token'}
27 </button>
28 </SelectTokenModal>
29 </TokenKitWrapper>
30 );
31}
Info

There is no open / onClose. The native `

` element manages its own visibility — clicking the trigger child opens the modal; click-outside, the close button, or Esc closes it. The animation is built in.

Props

PropTypeRequiredDescription
selectedTokenIToken | null | undefinedYesThe currently selected token. Drives the "selected" highlight in the list.
callBackFunc(token: IToken) => voidYesCalled with the chosen token when the user picks one. The modal closes automatically afterwards.
childrenReactNodeNoThe trigger element rendered before the dialog. Click anywhere on it to open.
modalHeightstringNoModal height (e.g. "95dvh"). Defaults to the theme's height value.
modalWidthstringNoModal width (e.g. "450px"). Defaults to 420px, shrinks on mobile.
animation'bounce' | 'slide' | 'ease' | 'fade'NoOpen/close animation style.

Wrapper-level options (TokenKitOptions)

Several picker behaviors are configured on TokenKitWrapper (or TokenKitProvider) via the options prop, not on SelectTokenModal itself — the modal reads them from context so every picker in the tree shares them.

Code
tsx
1<TokenKitWrapper
2 /* ...other props */
3 options={{
4 tokensToLoad: 'public', // or 'all' — see below
5 enableRecent: true, // opt in to recent-tokens persistence
6 }}
7 origin="chrome-extension://abcdef..." // optional, for extension wallets
8>
OptionTypeDefaultWhat it does
tokensToLoad'public' | 'all''public''public' shows only tokens flagged public=true in the TokenKit catalog (the curated set most apps want). 'all' shows every indexed ERC-20, including unverified contracts — use this when your users need to find arbitrary tokens by address.
enableRecentbooleanfalseWhen true, recently-picked tokens are persisted to localStorage per-network and shown in a "Recent" section above the full list. When false, nothing is read from or written to storage. Opt in explicitly.

origin (on the wrapper itself, not inside options) is sent as the X-Origin HTTP header on every API request — useful for browser-extension wallets to identify themselves to allow-listed API keys.

Features

  • Search — by name, symbol, or contract address (debounced 400 ms)
  • Common tokens — a horizontal quick-pick row at the top
  • Recent tokens — opt in via options.enableRecent; persisted to localStorage keyed by network
  • Infinite scroll — more tokens load as you reach the bottom of the list
  • Keyboard navigation:
    • / — move focus through results
    • Enter — select the focused token
    • Esc — close the modal
  • Click-outside-to-close — clicking the backdrop closes the dialog
  • Animated transitions — built-in fade/slide on open and close
  • Selected-state styling — the currently selected token is dimmed and unclickable in the list

Sometimes you want both a popup picker and an always-visible list (say, the modal in your swap form and the container in a sidebar). They share the same selection state via React.

Code
tsx
1import { useState } from 'react';
2import {
3 TokenKitWrapper,
4 SelectTokenModal,
5 SelectTokenContainer,
6 themes,
7 type IToken,
8} from 'starknet-tokenkit';
9 
10function App() {
11 const [selectedToken, setSelectedToken] = useState<IToken | null>(null);
12 
13 return (
14 <TokenKitWrapper
15 network="SN_MAIN"
16 apiKey="your-api-key"
17 mainnetEndpoint="https://api.tokenkithq.io"
18 sepoliaEndpoint="https://api.sepolia.tokenkithq.io"
19 themeObject={themes.dark}
20 >
21 {/* Modal — opens on trigger click */}
22 <SelectTokenModal
23 callBackFunc={setSelectedToken}
24 selectedToken={selectedToken}
25 modalHeight="95dvh"
26 modalWidth="450px"
27 >
28 <button>Select Token (Modal)</button>
29 </SelectTokenModal>
30 
31 {/* Container — renders inline */}
32 <SelectTokenContainer
33 callBackFunc={setSelectedToken}
34 selectedToken={selectedToken}
35 modalHeight="60dvh"
36 modalWidth="400px"
37 />
38 </TokenKitWrapper>
39 );
40}

The IToken shape

When the user picks a token, your callback gets an IToken:

Code
tsx
1interface IToken {
2 address: string; // Contract address
3 name: string; // Token name (e.g. "Ethereum")
4 symbol: string; // Token symbol (e.g. "ETH")
5 decimals: number; // Token decimals (e.g. 18)
6 logo: string; // URL to token logo image
7 verified?: boolean; // Whether the token is verified
8 public?: boolean; // Whether the token is publicly listed
9 common?: boolean; // Whether the token is one of the curated common tokens
10 id?: number; // TokenKit DB id (when present)
11 price?: number | string | null; // Optional last-known USD price
12}

logo may be an empty string for some tokens — the picker falls back to initials in a circle, so you don't need to handle that case yourself.