+
+Displays read-only contract state by querying view functions. The widget automatically discovers all view functions from the contract schema and renders their return types. Supports auto-refresh to keep values current.
+
+Each row shows the function name, return type, and live result — no wallet connection required. Pass `query` and `schema` capabilities from the active runtime:
+
+```tsx
+import { ContractStateWidget } from '@openzeppelin/ui-renderer';
+
+
+```
+
+
+
+
+
+
+### AddressBookWidget
+
+A full-featured address book UI for managing saved addresses and their human-readable aliases. Supports CRUD operations, search, and import/export.
+
+`AddressBookWidget` works with the [`@openzeppelin/ui-storage`](/tools/uikit/storage) package — specifically the **account alias plugin** which persists address-to-name mappings in IndexedDB via Dexie.js. When mounted alongside `AddressLabelProvider` and `AddressSuggestionProvider`, saved aliases automatically appear in all `AddressDisplay` and `AddressField` components throughout the app.
+
+```tsx
+import { AddressBookWidget } from '@openzeppelin/ui-renderer';
+import { createDexieDatabase, ALIAS_SCHEMA, useAddressBookWidgetProps } from '@openzeppelin/ui-storage';
+import Dexie from 'dexie';
+
+const db = createDexieDatabase(new Dexie('my-app'), ALIAS_SCHEMA);
+
+function AddressBook({ addressing }) {
+ const widgetProps = useAddressBookWidgetProps(db, { networkId: 'ethereum-mainnet' });
+ return ;
+}
+```
+
+See the [Storage](/tools/uikit/storage) page for setup and plugin configuration.
+
+### Other Renderer Components
+
+| Component | Description |
+| --- | --- |
+| `ContractActionBar` | Network status display with contract state toggle |
+| `ExecutionConfigDisplay` | Configure execution method (EOA / Relayer) |
+| `NetworkSettingsDialog` | Configure RPC endpoints and indexer URLs |
+| `WalletConnectionWithSettings` | Wallet connection UI with integrated settings |
+| `AliasEditPopover` | Inline popover for editing address aliases |
+
+## Next Steps
+
+- [React Integration](/tools/uikit/react-integration) — Connect components to wallet state and runtime capabilities
+- [Theming & Styling](/tools/uikit/theming) — Customize the visual appearance
+- [Getting Started](/tools/uikit/getting-started) — Step-by-step setup guide
diff --git a/content/tools/uikit/getting-started.mdx b/content/tools/uikit/getting-started.mdx
new file mode 100644
index 00000000..02e1729f
--- /dev/null
+++ b/content/tools/uikit/getting-started.mdx
@@ -0,0 +1,218 @@
+---
+title: Getting Started
+---
+
+This guide walks you through installing OpenZeppelin UIKit, configuring styles, and rendering your first blockchain transaction form.
+
+## Prerequisites
+
+- **Node.js** >= 20.19.0
+- **React** 19
+- **Tailwind CSS** 4
+- A package manager: `pnpm` (recommended), `npm`, or `yarn`
+
+## Installation
+
+Install only the packages your application needs. The packages are designed to be incrementally adopted.
+
+### Minimal Setup (Types + Components)
+
+For projects that only need the component library and type system:
+
+```bash
+pnpm add @openzeppelin/ui-types @openzeppelin/ui-utils @openzeppelin/ui-components @openzeppelin/ui-styles
+```
+
+### Full Setup (With Rendering + React Integration)
+
+For applications that need transaction form rendering and wallet integration:
+
+```bash
+pnpm add @openzeppelin/ui-types @openzeppelin/ui-utils @openzeppelin/ui-styles \
+ @openzeppelin/ui-components @openzeppelin/ui-react @openzeppelin/ui-renderer
+```
+
+### Optional Packages
+
+```bash
+# IndexedDB persistence (address book, settings, etc.)
+pnpm add @openzeppelin/ui-storage
+
+# Dev CLI for Tailwind wiring and local development
+pnpm add -D @openzeppelin/ui-dev-cli
+```
+
+### Ecosystem Adapters
+
+You also need at least one ecosystem adapter package for the blockchain(s) your app supports. For background on how adapters are structured, see [Building New Adapters](/ui-builder/building-adapters):
+
+```bash
+# EVM (Ethereum, Polygon, Arbitrum, etc.)
+pnpm add @openzeppelin/adapter-evm
+
+# Stellar / Soroban
+pnpm add @openzeppelin/adapter-stellar
+
+# Polkadot (EVM-compatible path)
+pnpm add @openzeppelin/adapter-polkadot
+```
+
+## Step 1: Configure Tailwind CSS
+
+UIKit uses Tailwind CSS 4 for styling. Components ship class names but **not** compiled CSS — your application's Tailwind build must know where to find them.
+
+### Automated Setup (Recommended)
+
+The dev CLI handles Tailwind configuration automatically:
+
+```bash
+pnpm add -D @openzeppelin/ui-dev-cli
+pnpm exec oz-ui-dev tailwind doctor --project "$PWD"
+pnpm exec oz-ui-dev tailwind fix --project "$PWD"
+```
+
+This generates an `oz-tailwind.generated.css` file with the correct `@source` directives for all installed OpenZeppelin packages.
+
+### Manual Setup
+
+If you prefer manual configuration, your entry CSS must register the OpenZeppelin package sources:
+
+```css
+@layer base, components, utilities;
+
+@import 'tailwindcss' source(none);
+@source "./";
+@source "../";
+@source "../node_modules/@openzeppelin/ui-components";
+@source "../node_modules/@openzeppelin/ui-react";
+@source "../node_modules/@openzeppelin/ui-renderer";
+@source "../node_modules/@openzeppelin/ui-styles";
+@source "../node_modules/@openzeppelin/ui-utils";
+@import '@openzeppelin/ui-styles/global.css';
+```
+
+
+A bare Tailwind import is not enough. Tailwind v4 must be told to scan the OpenZeppelin `node_modules` paths, or component classes will be missing from the final CSS.
+
+
+## Step 2: Use Components
+
+UIKit components work with [react-hook-form](https://react-hook-form.com/) for form state management. Here is a simple form with an address field and a submit button:
+
+```tsx
+import { useForm } from 'react-hook-form';
+import { Button, TextField, AddressField } from '@openzeppelin/ui-components';
+
+function SimpleForm() {
+ const { control, handleSubmit } = useForm();
+
+ const onSubmit = (data) => {
+ console.log('Form data:', data);
+ };
+
+ return (
+
+ );
+}
+```
+
+## Step 3: Render a Transaction Form
+
+The renderer package provides a declarative way to build transaction forms from a schema. The form fields, layout, and submission are all driven by data.
+
+```tsx
+import { TransactionForm } from '@openzeppelin/ui-renderer';
+import type { RenderFormSchema } from '@openzeppelin/ui-types';
+
+const schema: RenderFormSchema = {
+ id: 'transfer-form',
+ title: 'Transfer Tokens',
+ fields: [
+ { id: 'to', name: 'to', type: 'address', label: 'Recipient' },
+ { id: 'amount', name: 'amount', type: 'amount', label: 'Amount' },
+ ],
+ layout: { columns: 1, spacing: 'normal', labelPosition: 'top' },
+ submitButton: { text: 'Transfer', loadingText: 'Transferring...' },
+};
+
+function TransferPage({ adapter, contractSchema }) {
+ return (
+ {
+ console.log('Transaction successful:', result);
+ }}
+ />
+ );
+}
+```
+
+The `adapter` prop accepts a `TransactionFormCapabilities` object — a bundle of capabilities from your active [Ecosystem Runtime](/tools/uikit/architecture#runtimes).
+
+## Step 4: Wire Up React Providers
+
+For wallet integration and multi-network support, wrap your app with `RuntimeProvider` and `WalletStateProvider`:
+
+```tsx
+import { RuntimeProvider, WalletStateProvider } from '@openzeppelin/ui-react';
+import { ecosystemDefinition } from '@openzeppelin/adapter-evm';
+
+async function resolveRuntime(networkConfig) {
+ return ecosystemDefinition.createRuntime('composer', networkConfig);
+}
+
+function App() {
+ return (
+
+
+
+
+
+ );
+}
+```
+
+Then access wallet state and runtime capabilities from any component:
+
+```tsx
+import { useWalletState } from '@openzeppelin/ui-react';
+
+function WalletInfo() {
+ const { activeNetworkConfig, activeRuntime, isRuntimeLoading } = useWalletState();
+
+ if (isRuntimeLoading || !activeRuntime) {
+ return
Loading...
;
+ }
+
+ return
Connected to {activeNetworkConfig?.name}
;
+}
+```
+
+For the full React integration guide, see [React Integration](/tools/uikit/react-integration).
+
+## Next Steps
+
+- [Architecture](/tools/uikit/architecture) — Understand the package layers, capability tiers, and runtime model
+- [Components](/tools/uikit/components) — Explore UI primitives and blockchain-aware form fields
+- [React Integration](/tools/uikit/react-integration) — Deep dive into providers, hooks, and wallet state
+- [Theming & Styling](/tools/uikit/theming) — Customize tokens, colors, and dark mode
+- [Building New Adapters](/ui-builder/building-adapters) — Background on adapter packages and ecosystem integrations
diff --git a/content/tools/uikit/index.mdx b/content/tools/uikit/index.mdx
new file mode 100644
index 00000000..9391cdb1
--- /dev/null
+++ b/content/tools/uikit/index.mdx
@@ -0,0 +1,120 @@
+---
+title: OpenZeppelin UIKit
+---
+
+A modular React component library for building blockchain transaction interfaces — chain-agnostic, capability-driven, and designed for multi-ecosystem applications.
+
+
+**Source code**: OpenZeppelin UIKit is open-source. Browse the implementation, open issues, and contribute at [**github.com/OpenZeppelin/openzeppelin-ui**](https://github.com/OpenZeppelin/openzeppelin-ui).
+
+
+
+
+
+
+
+
+
+
+
+## What is OpenZeppelin UIKit?
+
+OpenZeppelin UIKit is a set of modular npm packages that provide everything needed to build rich blockchain UIs in React. Instead of a monolithic library, it ships as a **layered stack** — from low-level types and utilities up to high-level form renderers and wallet integration.
+
+Each layer is independently installable. Use only the pieces you need: the type system for a headless integration, the component library for a design system, or the full renderer for turnkey transaction forms.
+
+```mermaid
+%%{init: {'flowchart': {'nodeSpacing': 20, 'rankSpacing': 40}} }%%
+flowchart TD
+ App(["Your Application"])
+
+ Storage(["ui-storage"])
+ Renderer(["ui-renderer"])
+ ReactPkg(["ui-react"])
+ Components(["ui-components"])
+ Utils(["ui-utils"])
+ Types(["ui-types"])
+
+ EVM(["adapter-evm"])
+ Stellar(["adapter-stellar"])
+ Polkadot(["adapter-polkadot"])
+
+ App --> Storage & Renderer & ReactPkg
+ Storage --> Utils
+ Renderer --> Components
+ ReactPkg --> Components
+ Components --> Utils
+ Utils --> Types
+ ReactPkg -.->|consumes| EVM & Stellar & Polkadot
+
+ style App fill:#e8eaf6,stroke:#5c6bc0,color:#1a237e
+ style Storage fill:#e0f2f1,stroke:#26a69a,color:#004d40
+ style Renderer fill:#e0f2f1,stroke:#26a69a,color:#004d40
+ style ReactPkg fill:#e0f2f1,stroke:#26a69a,color:#004d40
+ style Components fill:#e0f2f1,stroke:#26a69a,color:#004d40
+ style Utils fill:#fff3e0,stroke:#ff9800,color:#e65100
+ style Types fill:#fff3e0,stroke:#ff9800,color:#e65100
+ style EVM fill:#fce4ec,stroke:#e91e63,color:#880e4f
+ style Stellar fill:#fce4ec,stroke:#e91e63,color:#880e4f
+ style Polkadot fill:#fce4ec,stroke:#e91e63,color:#880e4f
+```
+
+## Packages
+
+| Package | Description | Layer |
+| --- | --- | --- |
+| [`@openzeppelin/ui-types`](https://www.npmjs.com/package/@openzeppelin/ui-types) | Shared TypeScript type definitions — capabilities, schemas, form models | 1 |
+| [`@openzeppelin/ui-utils`](https://www.npmjs.com/package/@openzeppelin/ui-utils) | Framework-agnostic utilities — config, logging, validation, routing | 2 |
+| [`@openzeppelin/ui-styles`](https://www.npmjs.com/package/@openzeppelin/ui-styles) | Centralized Tailwind CSS 4 theme with OKLCH tokens and dark mode | 3 |
+| [`@openzeppelin/ui-components`](https://www.npmjs.com/package/@openzeppelin/ui-components) | React UI primitives and blockchain-aware form fields (shadcn/ui based) | 4 |
+| [`@openzeppelin/ui-react`](https://www.npmjs.com/package/@openzeppelin/ui-react) | React context providers, runtime management, and wallet hooks | 5 |
+| [`@openzeppelin/ui-renderer`](https://www.npmjs.com/package/@openzeppelin/ui-renderer) | Transaction form rendering engine and contract state widgets | 6 |
+| [`@openzeppelin/ui-storage`](https://www.npmjs.com/package/@openzeppelin/ui-storage) | IndexedDB storage abstraction with Dexie.js and address book plugin | 7 |
+
+## Key Design Principles
+
+**Chain-agnostic core.** UIKit packages never import chain-specific logic. Blockchain details are handled entirely by ecosystem adapter packages. For background on the adapter pattern, see [Building New Adapters](/ui-builder/building-adapters).
+
+**Capability-driven, not monolithic.** Instead of one large adapter interface, the system defines small, focused [capabilities](/tools/uikit/architecture#capabilities) (addressing, query, execution, wallet, etc.) organized into tiers. Components request only the capabilities they need.
+
+**Pay for what you use.** Install only the layers your app requires. A simple form builder can use just `ui-types` + `ui-components`. A full transaction dashboard can add `ui-renderer` + `ui-react` + `ui-storage`.
+
+**Multi-ecosystem ready.** A single app can support EVM, Stellar, Polkadot, and more simultaneously. The runtime system manages per-network adapter instances with proper lifecycle and disposal.
+
+## Ecosystem Adapter Integration
+
+UIKit connects to blockchains through ecosystem adapter packages — standalone packages that translate chain-specific operations into the shared capability model. For more background, see [Building New Adapters](/ui-builder/building-adapters).
+
+```mermaid
+sequenceDiagram
+ participant App as Your App
+ participant React as UIKit React
+ participant Adapter as Ecosystem Adapter
+ participant Chain as Blockchain
+
+ App->>React: RuntimeProvider + WalletStateProvider
+ React->>Adapter: createRuntime(profile, networkConfig)
+ Adapter-->>React: EcosystemRuntime (capabilities)
+ React-->>App: useWalletState() → activeRuntime
+
+ App->>React: TransactionForm adapter={capabilities}
+ React->>Adapter: execution.signAndBroadcast(...)
+ Adapter->>Chain: Submit transaction
+ Chain-->>Adapter: Transaction hash
+ Adapter-->>React: Status updates
+ React-->>App: UI reflects tx status
+```
+
+For more background on how adapters work and how new ecosystem integrations are structured, see [Building New Adapters](/ui-builder/building-adapters).
+
+## Requirements
+
+- **Node.js** >= 20.19.0
+- **React** 19
+- **Tailwind CSS** 4
+
+## Next Steps
+
+- [Getting Started](/tools/uikit/getting-started) — Install, configure, and render your first form
+- [Architecture](/tools/uikit/architecture) — Deep dive into the capability model and runtime lifecycle
+- [Building New Adapters](/ui-builder/building-adapters) — How chain-specific logic is decoupled from the UI
diff --git a/content/tools/uikit/react-integration.mdx b/content/tools/uikit/react-integration.mdx
new file mode 100644
index 00000000..6d1cf54c
--- /dev/null
+++ b/content/tools/uikit/react-integration.mdx
@@ -0,0 +1,268 @@
+---
+title: React Integration
+---
+
+The `@openzeppelin/ui-react` package provides the runtime and wallet infrastructure that connects UIKit components to blockchain ecosystems. This page covers providers, hooks, wallet state management, and multi-ecosystem support.
+
+## Provider Setup
+
+Two providers form the foundation of a UIKit-powered React app:
+
+```mermaid
+%%{init: {'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'wrappingWidth': 400}} }%%
+flowchart TD
+ RP["RuntimeProvider manages EcosystemRuntime instances"]
+ WSP["WalletStateProvider active network · wallet state · hooks"]
+ App["Your Components"]
+
+ RP --> WSP --> App
+
+ classDef provider fill:#e0f2f1,stroke:#26a69a,color:#004d40,width:350px
+ classDef component fill:#e8eaf6,stroke:#5c6bc0,color:#1a237e
+
+ class RP,WSP provider
+ class App component
+```
+
+### RuntimeProvider
+
+`RuntimeProvider` maintains a **registry of `EcosystemRuntime` instances** — one per network ID. It creates runtimes on demand, caches them, and disposes all of them on unmount.
+
+```tsx
+import { RuntimeProvider } from '@openzeppelin/ui-react';
+import { ecosystemDefinition } from '@openzeppelin/adapter-evm';
+
+async function resolveRuntime(networkConfig) {
+ return ecosystemDefinition.createRuntime('composer', networkConfig);
+}
+
+function App() {
+ return (
+
+ {/* children */}
+
+ );
+}
+```
+
+**Props:**
+
+| Prop | Type | Description |
+| --- | --- | --- |
+| `resolveRuntime` | `(networkConfig) => Promise` | Factory function that creates a runtime for a given network |
+
+### WalletStateProvider
+
+`WalletStateProvider` builds on `RuntimeProvider` to manage:
+
+- The **active network** ID and config
+- The **active runtime** and its loading state
+- **Wallet facade hooks** from the active runtime's `UiKitCapability`
+- The ecosystem's **React UI provider** component (e.g. wagmi's `WagmiProvider`)
+
+```tsx
+import { RuntimeProvider, WalletStateProvider } from '@openzeppelin/ui-react';
+
+function App() {
+ return (
+
+
+
+
+
+ );
+}
+```
+
+**Props:**
+
+| Prop | Type | Description |
+| --- | --- | --- |
+| `initialNetworkId` | `string` | Network to activate on mount |
+| `getNetworkConfigById` | `(id: string) => NetworkConfig` | Resolver for network configurations |
+| `loadConfigModule` | `(runtime) => Promise` | Optional — load UI kit config modules for the runtime |
+
+## Hooks
+
+### useWalletState
+
+The primary hook for accessing global wallet and runtime state:
+
+```tsx
+import { useWalletState } from '@openzeppelin/ui-react';
+
+function Dashboard() {
+ const {
+ activeNetworkId, // Current network ID string
+ activeNetworkConfig, // Full NetworkConfig object
+ activeRuntime, // EcosystemRuntime instance (or null)
+ isRuntimeLoading, // True while runtime is being created
+ walletFacadeHooks, // Ecosystem-specific React hooks
+ setActiveNetworkId, // Switch networks
+ reconfigureActiveUiKit // Re-initialize UI kit config
+ } = useWalletState();
+
+ if (isRuntimeLoading || !activeRuntime) {
+ return
Loading runtime...
;
+ }
+
+ return (
+
+
Network: {activeNetworkConfig?.name}
+
Ecosystem: {activeRuntime.ecosystem}
+
+ );
+}
+```
+
+### useRuntimeContext
+
+Low-level hook for direct access to the runtime registry:
+
+```tsx
+import { useRuntimeContext } from '@openzeppelin/ui-react';
+
+function AdvancedComponent() {
+ const { getRuntimeForNetwork } = useRuntimeContext();
+
+ const handleQuery = async () => {
+ const { runtime, isLoading } = getRuntimeForNetwork(polygonConfig);
+ if (isLoading || !runtime) return;
+ const result = await runtime.query.queryViewFunction(/* ... */);
+ };
+}
+```
+
+### Derived Wallet Hooks
+
+These hooks abstract wallet interactions across ecosystems, providing a consistent API regardless of whether the user is connected to EVM, Stellar, or any other chain:
+
+| Hook | Returns |
+| --- | --- |
+| `useDerivedAccountStatus()` | `{ isConnected, address, chainId }` |
+| `useDerivedConnectStatus()` | `{ connect, isPending, error }` |
+| `useDerivedDisconnect()` | `{ disconnect }` |
+| `useDerivedSwitchChainStatus()` | `{ switchChain, isPending }` |
+| `useDerivedChainInfo()` | `{ chainId, chains }` |
+| `useWalletReconnectionHandler()` | Manages automatic wallet reconnection |
+
+These are built on top of the `walletFacadeHooks` from the active runtime's `UiKitCapability`, which wraps the ecosystem's native wallet library (e.g. `wagmi` for EVM, Stellar Wallets Kit for Stellar).
+
+## Wallet Components
+
+`@openzeppelin/ui-react` ships ready-to-use wallet UI components:
+
+| Component | Description |
+| --- | --- |
+| `WalletConnectionHeader` | Compact header bar with wallet status and connect/disconnect |
+| `WalletConnectionUI` | Full wallet connection interface |
+| `WalletConnectionWithSettings` | Wallet connection with integrated network/RPC settings |
+| `NetworkSwitchManager` | Handles programmatic network switching with user confirmation |
+
+### NetworkSwitchManager
+
+Pass capabilities from the active runtime — not a monolithic adapter instance:
+
+```tsx
+import { useState } from 'react';
+import { NetworkSwitchManager, useWalletState } from '@openzeppelin/ui-react';
+
+function MyApp() {
+ const [targetNetwork, setTargetNetwork] = useState(null);
+ const { activeRuntime } = useWalletState();
+
+ const wallet = activeRuntime?.wallet;
+ const networkCatalog = activeRuntime?.networkCatalog;
+
+ return (
+ <>
+ {wallet && networkCatalog && targetNetwork && (
+ setTargetNetwork(null)}
+ />
+ )}
+ >
+ );
+}
+```
+
+### Ecosystem Wallet Components
+
+Each adapter can provide its own wallet components via the `UiKitCapability`. These are accessed through the runtime:
+
+```tsx
+const walletComponents = runtime.uiKit?.getEcosystemWalletComponents();
+// { ConnectButton, AccountDisplay, NetworkSwitcher }
+```
+
+For EVM, this integrates with [RainbowKit](https://www.rainbowkit.com/). For Stellar, it integrates with [Stellar Wallets Kit](https://stellarwalletskit.dev/). Each adapter maps its ecosystem's wallet library into the standardized component interface.
+
+## Multi-Ecosystem Apps
+
+A single app can support multiple blockchain ecosystems. The key is the `resolveRuntime` function, which determines which adapter to use based on the network config:
+
+```tsx
+import { ecosystemDefinition as evmDef } from '@openzeppelin/adapter-evm';
+import { ecosystemDefinition as stellarDef } from '@openzeppelin/adapter-stellar';
+
+async function resolveRuntime(networkConfig) {
+ switch (networkConfig.ecosystem) {
+ case 'evm':
+ return evmDef.createRuntime('composer', networkConfig);
+ case 'stellar':
+ return stellarDef.createRuntime('composer', networkConfig);
+ default:
+ throw new Error(`Unsupported ecosystem: ${networkConfig.ecosystem}`);
+ }
+}
+
+function App() {
+ return (
+
+
+
+
+
+ );
+}
+```
+
+When the user switches networks, `WalletStateProvider` automatically resolves the correct runtime and updates the wallet facade hooks, UI provider, and wallet components for the new ecosystem.
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant WSP as WalletStateProvider
+ participant RP as RuntimeProvider
+ participant EVM as EVM Adapter
+ participant Stellar as Stellar Adapter
+
+ User->>WSP: setActiveNetworkId("ethereum-mainnet")
+ WSP->>RP: getRuntimeForNetwork(ethConfig)
+ RP->>EVM: createRuntime("composer", ethConfig)
+ EVM-->>RP: EVM Runtime
+ RP-->>WSP: EVM Runtime
+ Note over WSP: Wallet hooks: wagmi / UI: RainbowKit
+
+ User->>WSP: setActiveNetworkId("stellar-pubnet")
+ WSP->>RP: getRuntimeForNetwork(stellarConfig)
+ RP->>Stellar: createRuntime("composer", stellarConfig)
+ Stellar-->>RP: Stellar Runtime
+ RP-->>WSP: Stellar Runtime
+ Note over WSP: Wallet hooks: Stellar Wallets Kit
+```
+
+## Next Steps
+
+- [Components](/tools/uikit/components) — Browse all available components and form fields
+- [Theming & Styling](/tools/uikit/theming) — Customize the visual design
+- [Building New Adapters](/ui-builder/building-adapters) — Background on adapter packages and ecosystem integrations
diff --git a/content/tools/uikit/storage.mdx b/content/tools/uikit/storage.mdx
new file mode 100644
index 00000000..78da5e4d
--- /dev/null
+++ b/content/tools/uikit/storage.mdx
@@ -0,0 +1,341 @@
+---
+title: Storage
+---
+
+`@openzeppelin/ui-storage` provides client-side persistence for UIKit applications. It is built on [Dexie.js](https://dexie.org/) (an IndexedDB wrapper) and ships a plugin system for extending storage with domain-specific functionality.
+
+## Installation
+
+```bash
+pnpm add @openzeppelin/ui-storage
+```
+
+## Why Use the Storage Plugin?
+
+Browser applications commonly reach for `localStorage` to persist user data. While fine for a handful of string values, `localStorage` hits hard limits as your app grows:
+
+| Concern | `localStorage` | `@openzeppelin/ui-storage` (IndexedDB) |
+| --- | --- | --- |
+| **Storage quota** | ~5 MB per origin | Hundreds of MB — often limited only by available disk space |
+| **Data model** | Flat key-value strings — every read/write requires `JSON.parse`/`JSON.stringify` | Structured object stores with typed records, indexes, and compound queries |
+| **Performance** | Synchronous — blocks the main thread on every call | Asynchronous — all reads and writes are non-blocking |
+| **Querying** | Full-scan only; no way to filter or sort without loading everything | Indexed lookups and range queries via Dexie.js |
+| **Concurrent tabs** | No built-in synchronization; race conditions on simultaneous writes | Transactional; supports multi-tab coordination out of the box |
+| **Schema evolution** | Manual — you must handle migrations yourself | Declarative versioned schemas with automatic upgrade migrations |
+
+Beyond these raw IndexedDB advantages, the storage plugin adds an **opinionated layer** designed for blockchain UIs:
+
+- **Typed base classes** — `EntityStorage` and `KeyValueStorage` give you CRUD, validation, quota handling, and timestamps without boilerplate.
+- **Plugin system** — domain-specific plugins (like the built-in account alias plugin) drop into any Dexie database and integrate with UIKit providers automatically.
+- **Reactive hooks** — `useLiveQuery` re-renders components when the underlying IndexedDB data changes, including changes from other browser tabs.
+- **Quota-safe writes** — the `withQuotaHandling` wrapper catches `QuotaExceededError` and surfaces a typed error code so your UI can handle it gracefully instead of silently failing.
+
+### When Does It Make Sense?
+
+Use `@openzeppelin/ui-storage` when your application needs to persist **structured, queryable data** on the client — especially data that grows over time or must survive page reloads. Common examples include:
+
+- **Contract history and recent contracts** — the [Role Manager](https://github.com/OpenZeppelin/role-manager) persists recently accessed contracts per network in a `RecentContractsStorage` built on `EntityStorage`. Records are indexed by `[networkId+address]` for fast lookups and sorted by `lastAccessed` so the most recent entries always appear first — something that would require deserializing, sorting, and re-serializing an entire array in `localStorage`.
+
+- **UI configuration and form state** — the [UI Builder](https://github.com/OpenZeppelin/ui-builder) stores complete contract UI configurations (including large ABIs and compiled contract definitions) in a `ContractUIStorage` entity store. With records that can reach tens of megabytes, `localStorage`'s 5 MB limit would be a non-starter. The storage plugin also powers the builder's import/export and multi-tab auto-save features.
+
+- **User preferences and settings** — both projects use `KeyValueStorage` for simple typed preferences (theme, active network, page size) — a lightweight alternative that still benefits from async I/O and schema versioning.
+
+- **Address book and aliases** — the built-in account alias plugin persists address-to-name mappings in IndexedDB and wires them into UIKit's `AddressLabelProvider` and `AddressSuggestionProvider`. Every `AddressDisplay` and `AddressField` in the component tree resolves labels automatically without any per-component wiring.
+
+If your app only stores a single flag or token, `localStorage` is perfectly adequate. Reach for the storage plugin when you need **indexed queries, large payloads, multi-tab safety, or domain-specific persistence** that integrates with the rest of the UIKit ecosystem.
+
+## Core Abstractions
+
+The package exposes two base classes that your application can extend:
+
+| Class | Description |
+| --- | --- |
+| `EntityStorage` | Generic IndexedDB store for typed entities. Handles create, read, update, delete, and query. |
+| `KeyValueStorage` | Simple key-value store backed by IndexedDB. Useful for persisting settings and preferences. |
+
+Both classes wrap a Dexie database instance and can be composed with plugins.
+
+## Account Alias Plugin
+
+
+
+
+
+
+
+
+
+The built-in **account alias plugin** persists address-to-name mappings. It powers the `AddressBookWidget` and integrates with `AddressLabelProvider` and `AddressSuggestionProvider` to resolve human-readable labels automatically across all `AddressDisplay` and `AddressField` components.
+
+
+
+
+### Setup
+
+Create a Dexie database instance with the alias schema, then use the provided hooks:
+
+```tsx
+import {
+ createDexieDatabase,
+ ALIAS_SCHEMA,
+ useAliasLabelResolver,
+ useAliasSuggestionResolver,
+ useAddressBookWidgetProps,
+} from '@openzeppelin/ui-storage';
+import Dexie from 'dexie';
+
+const db = createDexieDatabase(new Dexie('my-app'), ALIAS_SCHEMA);
+```
+
+### Integration with Address Providers
+
+Mount the providers near the root of your app to activate automatic label resolution. The `useAliasLabelResolver` and `useAliasSuggestionResolver` hooks return props that can be spread directly into the respective providers:
+
+```tsx
+import {
+ AddressLabelProvider,
+ AddressSuggestionProvider,
+} from '@openzeppelin/ui-components';
+import {
+ createDexieDatabase,
+ ALIAS_SCHEMA,
+ useAliasLabelResolver,
+ useAliasSuggestionResolver,
+} from '@openzeppelin/ui-storage';
+import Dexie from 'dexie';
+
+const db = createDexieDatabase(new Dexie('my-app'), ALIAS_SCHEMA);
+
+function App() {
+ const labelResolver = useAliasLabelResolver(db);
+ const suggestionResolver = useAliasSuggestionResolver(db);
+
+ return (
+
+
+
+
+
+ );
+}
+```
+
+Once mounted:
+
+- Every `AddressDisplay` in the subtree automatically shows the saved alias instead of the raw address.
+- Every `AddressField` shows autocomplete suggestions as the user types.
+
+### AddressBookWidget
+
+Wire `AddressBookWidget` using the `useAddressBookWidgetProps` hook, which returns all the props the widget needs:
+
+```tsx
+import { AddressBookWidget } from '@openzeppelin/ui-renderer';
+import { createDexieDatabase, ALIAS_SCHEMA, useAddressBookWidgetProps } from '@openzeppelin/ui-storage';
+import Dexie from 'dexie';
+
+const db = createDexieDatabase(new Dexie('my-app'), ALIAS_SCHEMA);
+
+function AddressBook({ addressing }) {
+ const widgetProps = useAddressBookWidgetProps(db, { networkId: 'ethereum-mainnet' });
+
+ return (
+
+ );
+}
+```
+
+See [Components — AddressBookWidget](/tools/uikit/components#addressbookwidget) for a screenshot.
+
+## Custom Entity Stores
+
+Extend `EntityStorage` to create typed stores for your own domain objects. The constructor takes a Dexie database instance and a table name:
+
+```tsx
+import { EntityStorage, createDexieDatabase } from '@openzeppelin/ui-storage';
+import Dexie from 'dexie';
+
+interface SavedContract {
+ id: string;
+ address: string;
+ name: string;
+ networkId: string;
+ addedAt: number;
+}
+
+const MY_SCHEMA = { contracts: '++id, address, networkId' };
+const db = createDexieDatabase(new Dexie('my-app'), MY_SCHEMA);
+
+class ContractStore extends EntityStorage {
+ constructor() {
+ super(db, 'contracts');
+ }
+
+ async findByNetwork(networkId: string): Promise {
+ return this.query((item) => item.networkId === networkId);
+ }
+}
+
+const contractStore = new ContractStore();
+await contractStore.add({ id: '...', address: '0x...', name: 'My Token', networkId: 'ethereum-mainnet', addedAt: Date.now() });
+```
+
+## Key-Value Store
+
+Use `KeyValueStorage` for simple settings and flags. Like `EntityStorage`, it takes a Dexie db instance and a table name:
+
+```tsx
+import { KeyValueStorage, createDexieDatabase } from '@openzeppelin/ui-storage';
+import Dexie from 'dexie';
+
+const MY_SCHEMA = { settings: 'key' };
+const db = createDexieDatabase(new Dexie('my-app'), MY_SCHEMA);
+
+class AppSettings extends KeyValueStorage {
+ constructor() {
+ super(db, 'settings');
+ }
+}
+
+const settings = new AppSettings();
+await settings.set('theme', 'dark');
+const theme = await settings.get('theme'); // 'dark'
+```
+
+## React Hook Factories
+
+The storage package ships a set of **factory functions** that turn any `EntityStorage` or `KeyValueStorage` into a fully reactive React hook — complete with live queries, CRUD wrappers, and file import/export. These are the recommended way to consume storage in React components.
+
+### `createRepositoryHook`
+
+The main factory. It composes the lower-level factories below into a single hook that provides everything a component needs: live data, loading state, CRUD operations, and optional file I/O.
+
+```tsx
+import { createRepositoryHook, createDexieDatabase, EntityStorage } from '@openzeppelin/ui-storage';
+import Dexie from 'dexie';
+import { toast } from 'sonner';
+
+interface Bookmark { id: string; url: string; label: string; }
+
+const SCHEMA = { bookmarks: '++id, url' };
+const db = createDexieDatabase(new Dexie('my-app'), [{ version: 1, stores: SCHEMA }]);
+
+class BookmarkStore extends EntityStorage {
+ constructor() { super(db, 'bookmarks'); }
+ async exportJson() { return JSON.stringify(await this.getAll()); }
+ async importJson(json: string) { /* parse and bulk-insert */ }
+}
+
+const bookmarkStore = new BookmarkStore();
+
+const useBookmarks = createRepositoryHook({
+ db,
+ tableName: 'bookmarks',
+ repo: bookmarkStore,
+ onError: (title, err) => toast.error(title),
+ fileIO: {
+ exportJson: () => bookmarkStore.exportJson(),
+ importJson: (json) => bookmarkStore.importJson(json),
+ filePrefix: 'bookmarks-backup',
+ },
+});
+
+function BookmarkList() {
+ const { records, isLoading, save, remove, exportAsFile, importFromFile } = useBookmarks();
+
+ if (isLoading) return
Loading…
;
+
+ return (
+
+ {records?.map((b) => (
+
+ {b.label}
+
+ ))}
+
+ );
+}
+```
+
+The hook returned by `createRepositoryHook` exposes:
+
+| Property | Type | Description |
+| --- | --- | --- |
+| `records` | `T[] \| undefined` | Live query result — `undefined` while loading |
+| `isLoading` | `boolean` | `true` until the first query resolves |
+| `save` | `(record) => Promise` | Insert a new record |
+| `update` | `(id, partial) => Promise` | Patch an existing record |
+| `remove` | `(id) => Promise` | Delete by ID |
+| `clear` | `() => Promise` | Remove all records |
+| `exportAsFile` | `(ids?) => Promise` | Download records as a timestamped JSON file (only when `fileIO` is configured) |
+| `importFromFile` | `(file) => Promise` | Import from a JSON `File` (only when `fileIO` is configured) |
+
+### Lower-Level Factories
+
+`createRepositoryHook` is built from three smaller factories that can be used independently when you need finer-grained control:
+
+| Factory | Purpose |
+| --- | --- |
+| `createLiveQueryHook(db, tableName, query?)` | Returns a hook that re-renders whenever the underlying Dexie table changes. Powered by `useLiveQuery` from `dexie-react-hooks`. |
+| `createCrudHook(repo, { onError? })` | Wraps a `CrudRepository` (anything with `save`, `update`, `delete`, `clear`) with unified error handling. |
+| `createJsonFileIO({ exportJson, importJson }, { filePrefix, onError? })` | Produces `exportAsFile` / `importFromFile` functions that handle Blob creation, download triggers, file reading, and JSON validation. |
+
+#### `createLiveQueryHook`
+
+```tsx
+import { createLiveQueryHook, createDexieDatabase } from '@openzeppelin/ui-storage';
+
+const useContracts = createLiveQueryHook(db, 'contracts');
+
+function ContractList() {
+ const contracts = useContracts(); // undefined while loading, then T[]
+ return
{contracts?.map((c) =>
{c.name}
)}
;
+}
+```
+
+Pass an optional `query` function for filtered or sorted results:
+
+```tsx
+const useRecentContracts = createLiveQueryHook(
+ db,
+ 'contracts',
+ (table) => table.orderBy('addedAt').reverse().limit(10).toArray(),
+);
+```
+
+#### `createCrudHook`
+
+```tsx
+import { createCrudHook } from '@openzeppelin/ui-storage';
+
+const useContractCrud = createCrudHook(contractStore, {
+ onError: (title, err) => console.error(title, err),
+});
+
+function AddButton() {
+ const { save } = useContractCrud();
+ return ;
+}
+```
+
+#### `createJsonFileIO`
+
+```tsx
+import { createJsonFileIO } from '@openzeppelin/ui-storage';
+
+const { exportAsFile, importFromFile } = createJsonFileIO(
+ { exportJson: () => store.exportJson(), importJson: (json) => store.importJson(json) },
+ { filePrefix: 'my-data', onError: (title, err) => toast.error(title) },
+);
+
+// exportAsFile() triggers a browser download of "my-data-2026-04-09.json"
+// importFromFile(file) reads a File, validates JSON, and calls importJson()
+```
+
+## Next Steps
+
+- [Components](/tools/uikit/components) — UI components that consume storage plugins
+- [React Integration](/tools/uikit/react-integration) — Wire up providers and wallet state
diff --git a/content/tools/uikit/theming.mdx b/content/tools/uikit/theming.mdx
new file mode 100644
index 00000000..89e3c5be
--- /dev/null
+++ b/content/tools/uikit/theming.mdx
@@ -0,0 +1,169 @@
+---
+title: Theming & Styling
+---
+
+OpenZeppelin UIKit uses **Tailwind CSS 4** with a centralized design token system. This page explains how styling works, how to customize the theme, and how to set up your application's CSS pipeline.
+
+## How Styling Works
+
+UIKit components use Tailwind CSS utility classes internally. The `@openzeppelin/ui-styles` package provides:
+
+- A **`global.css`** file with Tailwind v4 `@theme` definitions using OKLCH color tokens
+- CSS custom properties for colors, radii, spacing, and animations
+- A `@custom-variant dark` for dark mode support
+- Chart and sidebar color tokens
+
+Components do **not** ship pre-compiled CSS. Your application runs Tailwind and produces the final stylesheet, which means:
+
+- You have full control over the CSS output
+- Unused component styles are automatically tree-shaken
+- You can extend or override any design token
+
+## Setting Up Styles
+
+### Automated Setup
+
+The dev CLI generates and maintains the Tailwind configuration:
+
+```bash
+pnpm add -D @openzeppelin/ui-dev-cli
+pnpm exec oz-ui-dev tailwind doctor --project "$PWD"
+pnpm exec oz-ui-dev tailwind fix --project "$PWD"
+```
+
+This creates `oz-tailwind.generated.css` with `@source` directives that tell Tailwind where to find class names in OpenZeppelin packages.
+
+### Manual Setup
+
+Add these directives to your application's entry CSS file:
+
+```css
+@layer base, components, utilities;
+
+@import 'tailwindcss' source(none);
+
+/* Your app sources */
+@source "./";
+@source "../";
+
+/* OpenZeppelin UIKit sources */
+@source "../node_modules/@openzeppelin/ui-components";
+@source "../node_modules/@openzeppelin/ui-react";
+@source "../node_modules/@openzeppelin/ui-renderer";
+@source "../node_modules/@openzeppelin/ui-styles";
+@source "../node_modules/@openzeppelin/ui-utils";
+
+/* OpenZeppelin theme tokens */
+@import '@openzeppelin/ui-styles/global.css';
+```
+
+
+If you also use Ecosystem Adapter packages, add their `@source` directives too — adapters may ship UI components (wallet dialogs, network selectors) that need Tailwind scanning. The `oz-ui-dev tailwind fix` command handles this automatically.
+
+
+## Design Tokens
+
+The theme is defined in `@openzeppelin/ui-styles/global.css` using Tailwind v4's `@theme` directive with [OKLCH](https://oklch.com/) color values. OKLCH provides perceptually uniform colors across light and dark modes.
+
+### Color Tokens
+
+The theme defines semantic color tokens rather than raw color values:
+
+| Token | Purpose |
+| --- | --- |
+| `--background` / `--foreground` | Page background and default text |
+| `--card` / `--card-foreground` | Card surfaces |
+| `--primary` / `--primary-foreground` | Primary actions (buttons, links) |
+| `--secondary` / `--secondary-foreground` | Secondary actions |
+| `--muted` / `--muted-foreground` | Subdued elements |
+| `--accent` / `--accent-foreground` | Highlighted elements |
+| `--destructive` / `--destructive-foreground` | Destructive actions (delete, error) |
+| `--border` | Border colors |
+| `--input` | Input field borders |
+| `--ring` | Focus ring color |
+
+### Layout Tokens
+
+| Token | Purpose |
+| --- | --- |
+| `--radius` | Base border radius (used with `rounded-*` utilities) |
+| `--sidebar-*` | Sidebar-specific colors and dimensions |
+| `--chart-1` through `--chart-5` | Chart/data visualization colors |
+
+## Dark Mode
+
+UIKit uses `next-themes` for dark mode support, with a `@custom-variant dark` in the theme CSS. The dark variant activates automatically based on the user's system preference or an explicit toggle.
+
+All color tokens have dark mode equivalents defined in the theme. Components automatically adjust when the variant is active.
+
+### Integrating with next-themes
+
+If your app uses `next-themes`, dark mode works out of the box:
+
+```tsx
+import { ThemeProvider } from 'next-themes';
+
+function App({ children }) {
+ return (
+
+ {children}
+
+ );
+}
+```
+
+## Component Variants
+
+Button and other interactive components use [`class-variance-authority`](https://cva.style/) for variant management:
+
+```tsx
+import { Button } from '@openzeppelin/ui-components';
+
+// Variant options: default, destructive, outline, secondary, ghost, link
+
+
+
+// Size options: default, sm, lg, icon
+
+
+```
+
+## Customizing the Theme
+
+Since the theme is CSS custom properties, you can override any token in your app's CSS:
+
+```css
+@import '@openzeppelin/ui-styles/global.css';
+
+:root {
+ --primary: oklch(0.65 0.2 250);
+ --radius: 0.75rem;
+}
+```
+
+This approach works because UIKit components reference the CSS variables, not hard-coded values. Your overrides take precedence and propagate to all components.
+
+## Utility Functions
+
+`@openzeppelin/ui-components` exports styling utilities used internally and available for your custom components:
+
+| Utility | Source | Purpose |
+| --- | --- | --- |
+| `cn(...classes)` | `tailwind-merge` + `clsx` | Merge Tailwind classes with conflict resolution |
+| `buttonVariants` | `class-variance-authority` | Apply button variant styles to custom elements |
+
+```tsx
+import { cn } from '@openzeppelin/ui-utils';
+
+function CustomCard({ className, ...props }) {
+ return (
+
+ );
+}
+```
+
+## Next Steps
+
+- [Getting Started](/tools/uikit/getting-started) — Full setup walkthrough including Tailwind configuration
+- [Components](/tools/uikit/components) — Browse all available UI components
+- [Architecture](/tools/uikit/architecture) — Understand the package layer system
diff --git a/public/uikit/address-book-widget.png b/public/uikit/address-book-widget.png
new file mode 100644
index 00000000..d05c5bc6
Binary files /dev/null and b/public/uikit/address-book-widget.png differ
diff --git a/public/uikit/contract-state-widget.png b/public/uikit/contract-state-widget.png
new file mode 100644
index 00000000..5a6ba485
Binary files /dev/null and b/public/uikit/contract-state-widget.png differ
diff --git a/public/uikit/example-app-overview.png b/public/uikit/example-app-overview.png
new file mode 100644
index 00000000..aa9b8775
Binary files /dev/null and b/public/uikit/example-app-overview.png differ
diff --git a/public/uikit/form-renderer.png b/public/uikit/form-renderer.png
new file mode 100644
index 00000000..849055c1
Binary files /dev/null and b/public/uikit/form-renderer.png differ
diff --git a/public/uikit/storage-account-alias-plugin.png b/public/uikit/storage-account-alias-plugin.png
new file mode 100644
index 00000000..00b2aac4
Binary files /dev/null and b/public/uikit/storage-account-alias-plugin.png differ
diff --git a/src/navigation/ethereum-evm.json b/src/navigation/ethereum-evm.json
index 8ce1463b..09441417 100644
--- a/src/navigation/ethereum-evm.json
+++ b/src/navigation/ethereum-evm.json
@@ -1246,6 +1246,47 @@
"name": "Role Manager",
"url": "/role-manager"
},
+ {
+ "type": "folder",
+ "name": "UIKit",
+ "index": {
+ "type": "page",
+ "name": "Overview",
+ "url": "/tools/uikit"
+ },
+ "children": [
+ {
+ "type": "page",
+ "name": "Getting Started",
+ "url": "/tools/uikit/getting-started"
+ },
+ {
+ "type": "page",
+ "name": "Architecture",
+ "url": "/tools/uikit/architecture"
+ },
+ {
+ "type": "page",
+ "name": "Components",
+ "url": "/tools/uikit/components"
+ },
+ {
+ "type": "page",
+ "name": "React Integration",
+ "url": "/tools/uikit/react-integration"
+ },
+ {
+ "type": "page",
+ "name": "Theming & Styling",
+ "url": "/tools/uikit/theming"
+ },
+ {
+ "type": "page",
+ "name": "Storage",
+ "url": "/tools/uikit/storage"
+ }
+ ]
+ },
{
"type": "folder",
"name": "Defender",