Getting Started

Why? OpenZeppelin UIKit provides a modular, blockchain‑aware component system that simplifies building secure Web3 interfaces. It lets developers skip low‑level UI wiring and rely on tested components that handle forms, transactions, and runtime integration across multiple ecosystems.

This guide walks you through installing OpenZeppelin UIKit, configuring styles, and rendering your first blockchain transaction form.

Live example: See UIKit and ecosystem adapters working together in the browser at openzeppelin-ui.netlify.app.

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:

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, use:

pnpm add @openzeppelin/ui-types @openzeppelin/ui-utils @openzeppelin/ui-styles \
  @openzeppelin/ui-components @openzeppelin/ui-react @openzeppelin/ui-renderer

Optional Packages

# 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:

# 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 they do not include compiled CSS. Your application's Tailwind build must generate the final styles.

The dev CLI handles Tailwind configuration automatically:

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:

@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 for form state management, providing a consistent way to handle form input. Here is a simple form with an address field and a submit button:

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 (
    <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
      <AddressField
        name="recipient"
        label="Recipient Address"
        control={control}
        placeholder="0x..."
      />
      <TextField
        name="memo"
        label="Memo"
        control={control}
        placeholder="Optional note"
      />
      <Button type="submit">Send</Button>
    </form>
  );
}

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 defined declaritavely through the schema.

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 (
    <TransactionForm
      schema={schema}
      contractSchema={contractSchema}
      adapter={adapter}
      onTransactionSuccess={(result) => {
        console.log('Transaction successful:', result);
      }}
    />
  );
}

The adapter prop accepts a TransactionFormCapabilities object: a bundle of capabilities from your active Ecosystem Runtime.

Step 4: Wire Up React Providers

For wallet integration and multi-network support, wrap your app with RuntimeProvider and WalletStateProvider:

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 (
    <RuntimeProvider resolveRuntime={resolveRuntime}>
      <WalletStateProvider
        initialNetworkId="ethereum-mainnet"
        getNetworkConfigById={getNetworkById}
      >
        <YourApp />
      </WalletStateProvider>
    </RuntimeProvider>
  );
}

Then access wallet state and runtime capabilities from any component:

import { useWalletState } from '@openzeppelin/ui-react';

function WalletInfo() {
  const { activeNetworkConfig, activeRuntime, isRuntimeLoading } = useWalletState();

  if (isRuntimeLoading || !activeRuntime) {
    return <p>Loading...</p>;
  }

  return <p>Connected to {activeNetworkConfig?.name}</p>;
}

For the full React integration guide, see React Integration.

Next Steps