web3icons

Web3 UX: where most dApps lose people

The moments where people give up on a dApp, from wallet prompts and jargon to signatures, fees and waiting, and how to make each one easier.

5 min read

People rarely leave a dApp because blockchains are hard to understand. They leave at specific, predictable moments: a wallet prompt that appears before they know what the app does, a word they have never seen, a signature request full of hex, a fee that shows up at the last step, or a spinner with no explanation. Each of these is a design problem, and each has known fixes.

This guide walks through those moments in the order people usually hit them, then ends with principles you can apply to any flow. The assumption throughout is that friction is something the interface creates, not something users fail at.

Map the moments before fixing them

Before redesigning anything, list every point in your main flow where the person has to act outside your interface, or decide something they may not understand. For a typical token swap, the list looks like this:

Moment What the person sees Where it goes wrong
Landing Your app, often behind a Connect button Asked for a wallet before seeing anything useful
Connect A modal listing wallets Doesn't know which to pick, or has none installed
Network A wrong network warning Doesn't know what a network is or why it matters
Approve A wallet popup asking to approve a token Doesn't know why a swap needs two steps
Confirm A wallet popup with a fee Fee in a unit they can't price, seen for the first time
Wait A pending state No idea how long it takes, or whether it worked
Done A new balance, maybe Can't find the token in their wallet

Every row is a place where you can explain the step, remove it or move it later. Write this table for your own product. It will be more useful than any generic checklist because it reflects the flow you actually ship.

Asking for the wallet too early

A full-page "Connect wallet to continue" gate is the most common way to lose a curious visitor. They came to see prices, pools or a collection, and the first thing you ask for is access to their account.

Let people browse without connecting. Show the swap form with an empty balance, show pool yields, let them type an amount and see a quote. Ask for a connection at the moment it becomes necessary, on the button that needs it: the button reads "Connect wallet to swap" where it would otherwise read "Swap". When they do connect, put them back exactly where they were, with their inputs intact.

The connect modal deserves the same care: recognizable wallet icons with their names, detected wallets first, and a clear path for people who don't have a wallet yet. The details are in designing the connect wallet flow.

Jargon that leaks from the protocol

Developers work with the protocol's vocabulary all day, and those words end up in buttons and errors: nonce, gas limit, slippage, RPC, chain ID, approve, revert. Some are unavoidable. Most can be translated where they appear.

A workable rule: use the protocol term only where the person needs it to act or to search for help, and pair it with a plain description the first time it appears.

  • "Slippage tolerance 0.5%" becomes "Max price change 0.5%", with the word slippage in the help text.
  • A revert caused by slippage becomes "The swap didn't go through because the price moved more than your limit."
  • "Switch to chain 8453" becomes "Switch to Base", with the Base logo next to it.

Keep a short shared glossary of the terms you translate, so buttons, errors and support articles all use the same words.

Signatures nobody can read

Wallets display whatever the dApp sends them. If you request a personal_sign of an opaque hex string, the person sees an opaque hex string and has to decide whether to trust it. Declining is a reasonable response.

Two things help. First, explain in your own interface what the signature does before the wallet opens: "Sign a message to prove you own this address. It's free and doesn't move any funds." Second, use formats that wallets can render as readable text: EIP-712 typed data for structured requests, and Sign-In with Ethereum (EIP-4361) for login, which produces a plain message naming your domain. Making signature requests readable covers both.

Be most explicit when a signature is not harmless. Token approvals and permit-style signatures grant spending rights. Your interface should say so in words, with the amount and who receives the permission.

Fees that show up last

Many flows reveal the fee only in the wallet popup, after the person has already committed to the action. At that point the fee is a surprise, it's often shown in a unit like gwei or the native token, and the only choices left are accept or reject.

Show an estimated fee in your interface before the wallet opens, in local currency with the native amount beside it: "Network fee ≈ $0.42 (0.00012 ETH)". If the fee is large compared to the amount, say so before the click. If the action needs two transactions, such as an approval followed by the swap, say there will be two and estimate both. Showing gas fees people understand goes into estimates, ranges and fee spikes.

Waiting without feedback

After a transaction is submitted, many dApps show a spinner and nothing else. The person doesn't know whether it's working, how long it should take, or whether closing the tab will cancel it.

Name the state, give a rough expectation, and link to the transaction on a block explorer:

Swap submitted. This usually takes less than a minute. You can close this page and the swap will still go through. View on explorer

Then update the interface when the state changes, including failures and replaced transactions, which are covered in transaction state design. Updating the balance on screen once the transaction confirms matters more than it seems: it's the moment people decide whether the action worked.

Principles that hold across flows

Defer, don't gate. Any step you can move later, move later. Connection, network switching and token approvals can all happen at the point of action.

Explain before the wallet opens. You don't control the wallet popup, so it's the least informative screen in your flow. Your own interface is where explanation belongs.

Translate, then link. Use plain words in the main interface and keep the precise term one step away, in help text or a tooltip that keyboard users can reach too.

Show money as money. Fees and amounts are easier to judge in a currency people use daily. Keep the token amount visible as well, since that's what the wallet will show.

Don't make people feel at fault. "You rejected the request" sounds like an accusation. "Request cancelled in your wallet. Nothing was sent." describes what happened and confirms nothing went wrong.

Recover in place. When something fails, keep the form filled in and offer the next action on the same screen.

The most useful test is cheap: watch someone who has never used your app go through the main flow with a fresh wallet holding a small amount. Note where they pause, not only where they fail. The pauses show you what to fix next.

More in Web3 UX

All guides