Type-safe DeFi UI primitives for elm-web3. 57 modules: wallet, transaction, address, balance, signing, token, approval-flow, event-feed, simulate-first, staking, voting, bonding-curve, fee, gauge and price primitives — all returning plain Html msg, all closing the frontend security gap that owns DeFi.
No internal Msg, no subscriptions, no state of their own. You pass the state in, the component renders it, your msg comes back. The compiler enforces the rest.
Live gallery → every generic primitive in every state, driven entirely by simulated messages — no wallet, no node, no JS. The kit is riced from ~10 CSS tokens;
examples/gallery/gallery.cssis the reference theme. Map and roadmap:PRIMITIVES.md. The proofs behind the machines: what is actually proved.
For the underlying correctness / supply-chain / frontend-security pitch — why this stack at all — see intrepidshape/elm-web3. This package is the rendering layer on top.
elm install intrepidshape/elm-web3-uiRequires intrepidshape/elm-web3 1.0.0 or later.
- No default styles. Every element has a semantic
classattribute. Write your own CSS, or use one of the included stylesheets instyles/as a starting point. - Attribute passthrough. Every function takes
List (Html.Attribute msg)as its first argument, merged onto the root element. Pass[]if you have nothing to add. - No internal Msg. All callbacks are plain
msgvalues from your application. The components do not own any update logic. - Validation feedback.
Input.address,Input.bigInt, andInput.bytesaccept avalid : Boolfield. PassFalseto apply the--invalidmodifier class after a failed parse.
| Module | Contents |
|---|---|
Web3.Ui.Wallet |
Connect button, wallet picker, full wallet state view, chain badge |
Web3.Ui.Transaction |
Status badge, action button, tx hash link, receipt view |
Web3.Ui.Address |
Address display (with optional explorer link), short helper, address input |
Web3.Ui.Balance |
Balance display with formatUnits / formatEther, loading-state variants |
Web3.Ui.Input |
Typed inputs: address, bigInt, bool, text, bytes |
Web3.Ui.Sign |
Sign state display, sign button, signature display |
import Web3.Chain as Chain
import Web3.Ui.Wallet as WalletUi
import Web3.Ui.Transaction as TxUi
import Web3.Ui.Address as AddressUi
view : Model -> Html Msg
view model =
div []
[ WalletUi.viewState []
{ onConnect = ConnectWallet
, onSwitchChain = SwitchChain
, onDisconnect = DisconnectWallet
, knownChains = [ Chain.pulsechain, Chain.ethereum ]
}
model.wallet
, TxUi.statusBadge [] model.tx
, TxUi.actionButton []
{ label = "Submit", pendingLabel = "Submitting…", onPress = Submit }
model.tx
]connectButton :
List (Html.Attribute msg)
-> { onConnect : msg, onDisconnect : msg }
-> Wallet.State
-> Html msgShows "Connect" in disconnected/error/read-only states. Shows "Disconnect" when connected or on the wrong chain. Disabled with "Connecting…" while Connecting.
walletPicker :
List (Html.Attribute msg)
-> (String -> msg) -- called with the RDNS of the chosen wallet
-> List Wallet.WalletProvider
-> Html msgRenders a list of EIP-6963 discovered wallets as buttons with icon and name.
viewState :
List (Html.Attribute msg)
-> { onConnect : msg, onSwitchChain : msg, onDisconnect : msg
, knownChains : List Chain }
-> Wallet.State
-> Html msgCovers all six Wallet.State variants. Pass knownChains so the WrongChain branch can display the target network name.
chainBadge :
List (Html.Attribute msg)
-> List Chain
-> Wallet.State
-> Html msgLabels: connected chain name · "Wrong Chain" · "Read-only" · "—" (connecting / disconnected / error).
statusBadge :
List (Html.Attribute msg)
-> Tx.Status
-> Html msgRenders a <span> with web3-tx-badge plus one modifier class. Labels: "Idle" / "Awaiting Signature" / "Pending" / "Confirming (N)" / "Confirmed" / "Failed" / "Rejected".
actionButton :
List (Html.Attribute msg)
-> { label : String, pendingLabel : String, onPress : msg }
-> Tx.Status
-> Html msgActive in Idle and terminal states. Disabled (with pendingLabel) while in-flight.
txHashLink :
List (Html.Attribute msg)
-> { explorerUrl : String }
-> T.TxHash
-> Html msg
statusHashLink :
List (Html.Attribute msg)
-> { explorerUrl : String }
-> Tx.Status
-> Maybe (Html msg)
receiptView :
List (Html.Attribute msg)
-> { explorerUrl : String }
-> Tx.Receipt
-> Html msgstatusHashLink returns Nothing when there is no hash in the current state. receiptView shows block number, gas used, and a tx hash link.
view :
List (Html.Attribute msg)
-> { explorerUrl : Maybe String }
-> T.Address
-> Html msgRenders "0x1234…abcd". Wraps in <a> when explorerUrl is Just url, otherwise a <span>.
short : T.Address -> StringPure function — no Html. Returns "0x" ++ first4 ++ "…" ++ last4.
input :
List (Html.Attribute msg)
-> { value : String, onInput : String -> msg, valid : Bool }
-> Html msgText input for address entry. Adds web3-input-address--invalid when valid is False.
view : List (Html.Attribute msg) -> { decimals : Int, symbol : String } -> BigInt -> Html msg
viewEther : List (Html.Attribute msg) -> { symbol : String } -> BigInt -> Html msg
viewMaybe : List (Html.Attribute msg) -> { decimals : Int, symbol : String, loading : String } -> Maybe BigInt -> Html msg
viewEtherMaybe : List (Html.Attribute msg) -> { symbol : String, loading : String } -> Maybe BigInt -> Html msgviewMaybe / viewEtherMaybe render opts.loading with class web3-balance--loading while Nothing, and the formatted balance once it arrives.
address : List (Html.Attribute msg) -> { value : String, onInput : String -> msg, valid : Bool } -> Html msg
bigInt : List (Html.Attribute msg) -> { value : String, onInput : String -> msg, valid : Bool } -> Html msg
bytes : List (Html.Attribute msg) -> { value : String, onInput : String -> msg, valid : Bool } -> Html msg
bool : List (Html.Attribute msg) -> { value : Bool, onToggle : Bool -> msg } -> Html msg
text : List (Html.Attribute msg) -> { value : String, onInput : String -> msg } -> Html msgNo validation is performed inside the components. Call T.address, BigInt.fromString, etc. in your update and pass the result back as valid.
Web3.Ui.Input.address delegates to Web3.Ui.Address.input — they are the same component.
stateView :
List (Html.Attribute msg)
-> Sign.SignState
-> Html msgLabels: "Idle" / "Waiting for signature…" / "Signed" / "Failed: <err>" / "Rejected".
signButton :
List (Html.Attribute msg)
-> { label : String, onSign : msg }
-> Sign.SignState
-> Html msgDisabled while SignPending, active otherwise.
signatureView :
List (Html.Attribute msg)
-> Sign.SignState
-> Html msgRenders the signature in a <code> element when in Signed state. Returns an empty text node for all other states — safe to always include.
| Class | Element |
|---|---|
web3-connect-btn |
Connect button |
web3-disconnect-btn |
Disconnect button |
web3-wallet-picker |
Wallet picker container |
web3-wallet-option |
Individual wallet option button |
web3-chain-badge |
Chain badge span |
web3-wallet-state |
Full wallet state wrapper div |
web3-tx-badge |
Transaction status badge |
web3-tx-badge--idle |
Idle state |
web3-tx-badge--pending |
Pending / in-flight state |
web3-tx-badge--confirmed |
Confirmed state |
web3-tx-badge--failed |
Failed state |
web3-tx-badge--rejected |
Rejected state |
web3-action-btn |
Action button |
web3-action-btn--pending |
Action button while disabled |
web3-tx-link |
Transaction hash link |
web3-receipt |
Receipt view root div |
web3-receipt--success |
Receipt — EVM status true |
web3-receipt--failed |
Receipt — EVM status false |
web3-receipt-field |
Receipt field row div |
web3-address |
Address display |
web3-balance |
Balance display span |
web3-balance--loading |
Balance loading state |
web3-input-address |
Address text input |
web3-input-address--invalid |
Address input — invalid |
web3-input-bigint |
BigInt text input |
web3-input-bigint--invalid |
BigInt input — invalid |
web3-input-bool |
Bool checkbox input |
web3-input-text |
Plain text input |
web3-input-bytes |
Bytes hex input |
web3-input-bytes--invalid |
Bytes input — invalid |
web3-sign-state |
Sign state span |
web3-sign-btn |
Sign button |
web3-signature |
Signature display wrapper div |
web3-signature-value |
Signature value code element |
The styles/ directory contains a few optional CSS files if you want something to start from:
vanilla.css— minimal, unstyleddark.css— dark themeshadcn.css— shadcn/ui-inspiredbrutalist.css— high contrast, no-frillstailwind.plugin.cjs— Tailwind plugin that maps the class names above to utility classes
None of these are loaded automatically. Copy or import whichever suits your project.
Intrepid Development — Solidity team. Dapps, contracts, audits.
We write the contracts and the frontends that talk to them. They deserve the same rigour. This lib is what we use on our own.
Pairs with intrepidshape/elm-web3; both ship together.
If you want it wired into a production dapp, or the frontend hardened alongside a contract engagement: Jake@intrepiddev.com.au.
If your team is using these in production and wants help wiring up dApp UX, hardening contracts, or auditing a deployment, reach out at Jake@intrepiddev.com.au.
MIT © Intrepid Development