@a2ui/react

中文文档
0.15.0发布于 2026/11/02Apache-2.0TypeScriptESM + CJS近 30 天下载 65.4万

React renderer for A2UI (Agent-to-User Interface)

原始使用文档
文档目录29 节
  1. Features
  2. Installation
  3. Protocol Versioning
  4. Quick Start
  5. Defining Custom Components
  6. 1. Define the Component API
  7. 2. Create the React Implementation
  8. Generic Binder Features
  9. Binderless Components
  10. Defining Catalogs and Functions
  11. Basic Catalog Components
  12. Markdown in Text
  13. Styling and CSS Modules
  14. Legacy Support (v0.8)
  15. Import and Usage
  16. Key Differences (v0.8 vs v0.9)
  17. Security
  18. Development
  19. Setup
  20. Build
  21. Type Check
  22. Lint
  23. Testing
  24. Unit Tests
  25. Visual Parity Testing
  26. Running Visual Parity Tests
  27. Quick Commands
  28. Documentation
  29. Key Concepts

@a2ui/react

React renderer for A2UI (Agent-to-User Interface) - enables AI agents to generate rich, interactive user interfaces through declarative JSON.

Features

  • v0.9 Native: Built specifically for the A2UI v0.9 protocol with improved modularity and type safety.
  • Deep Reactivity: Powered by the A2UI Generic Binder for automatic, fine-grained updates.
  • Strongly Typed: Inferred property types from Zod schemas ensure compile-time safety.
  • Extensible: Easily define custom component catalogs and logic functions.
  • Multi-Surface: Built-in support for managing independent UI surfaces.

Installation

yarn add @a2ui/react @a2ui/web_core

Protocol Versioning

A2UI supports multiple protocol versions to ensure backward compatibility. For new projects, it is recommended to use the v0.9 protocol.

To use the v0.9 implementation, import from the versioned path:

import {A2uiSurface} from '@a2ui/react/v0_9';
import {basicCatalog} from '@a2ui/web_core/v0_9/basic_catalog';

Quick Start

The React renderer works alongside the MessageProcessor from @a2ui/web_core. The processor interprets JSON messages from an AI agent and manages the resulting UI state. The core concepts are:

  • Processor — receives and interprets the agent's messages.
  • Surface — an independent rendering area, uniquely identified by a string ID, that the agent creates and populates with components.
  • Catalog — the set of components (e.g. Text, Column, Button) available for a surface to use.

The example below creates a processor with the basicCatalog from @a2ui/web_core, feeds it a sequence of hardcoded messages, and renders the resulting surface.

import {useState, useEffect} from 'react';
import {MessageProcessor} from '@a2ui/web_core/v0_9';
import {basicCatalog} from '@a2ui/web_core/v0_9/basic_catalog';
import {A2uiSurface} from '@a2ui/react/v0_9';

export default function App() {
  // 1. Create the processor and feed it messages.
  const [processor] = useState(() => {
    const p = new MessageProcessor([basicCatalog]);
    p.processMessages(sampleAgentMessages);
    return p;
  });

  // 2. Set up listeners to keep the UI up to date as messages arrive.
  const [surfaces, setSurfaces] = useState(() => Array.from(processor.model.surfacesMap.values()));
  useEffect(() => {
    const sync = () => setSurfaces(Array.from(processor.model.surfacesMap.values()));

    const createdSub = processor.onSurfaceCreated(sync);
    const deletedSub = processor.onSurfaceDeleted(sync);

    return () => {
      createdSub.unsubscribe();
      deletedSub.unsubscribe();
    };
  }, [processor]);

  // 3. Render every surface the agent has created.
  return (
    <div className="a2ui-container">
      {surfaces.length === 0 && <div>Waiting for agent...</div>}
      {surfaces.map(surface => (
        <A2uiSurface key={surface.id} surface={surface} />
      ))}
    </div>
  );
}

// In a real app, these messages would come from an agent via WebSocket, SSE, etc.
// Here we hardcode them to show the message format.
const sampleAgentMessages = [
  {
    version: 'v0.9' as const,
    createSurface: {surfaceId: 'main-chat', catalogId: basicCatalog.id},
  },
  {
    version: 'v0.9' as const,
    updateComponents: {
      surfaceId: 'main-chat',
      components: [
        {id: 'root', component: 'Column', children: ['greeting', 'description']},
        {id: 'greeting', component: 'Text', text: {path: '/title'}},
        {id: 'description', component: 'Text', text: {path: '/body'}},
      ],
    },
  },
  {
    version: 'v0.9' as const,
    updateDataModel: {
      surfaceId: 'main-chat',
      path: '/',
      value: {
        title: 'Hello from A2UI!',
        body: 'Replace these messages with real agent responses to build interactive UIs.',
      },
    },
  },
];

Running this example should display "Hello from A2UI!". The example demonstrates three message types:

  • createSurface initializes the rendering surface.
  • updateComponents defines the UI tree. Here, a Column containing two Text components.
  • updateDataModel provides the data that the components reference via path bindings (e.g. { path: '/title' } resolves to the title field in the data model).

Defining Custom Components

A2UI v0.9 strictly separates a component's API (Schema) from its implementation.

1. Define the Component API

Use Zod to define the properties. Using CommonSchemas from web_core enables automatic binding for A2UI primitives.

import {z} from 'zod';
import {CommonSchemas} from '@a2ui/web_core/v0_9';

export const MyProfileApi = {
  name: 'Profile',
  schema: z.object({
    username: CommonSchemas.DynamicString, // Can be literal "Alice" or {path: "/user/name"}
    bio: CommonSchemas.DynamicString,
    avatarUrl: CommonSchemas.DynamicString,
    onEdit: CommonSchemas.Action, // Resolves to a clickable () => void
    isEditable: CommonSchemas.DynamicBoolean,
    // Add 'checks' if you want validation support (standard in v0.9 interactive components)
    checks: CommonSchemas.Checkable.shape.checks,
  }),
};

The API can also restrict where the component appears, with allowedParents and allowedChildren lists of component names. The message processor then rejects a payload that places the component under another parent with UNALLOWED_PARENT, or gives it a child of another type with UNALLOWED_CHILD. A root component's parent is Surface. Only properties declared with CommonSchemas.ComponentId or CommonSchemas.ChildList hold children, so an id in a plain z.string() property is not checked against allowedChildren.

2. Create the React Implementation

The createComponentImplementation factory uses a Generic Binder to resolve all dynamic values before your component renders.

import {createComponentImplementation} from '@a2ui/react/v0_9';

export const MyProfile = createComponentImplementation(MyProfileApi, ({props, buildChild}) => {
  // 'props' is strictly inferred from the Zod schema:
  // props.username is 'string' (resolved from DynamicString)
  // props.onEdit is '() => void' (resolved from Action)

  return (
    <div className="profile-widget">
      <img src={props.avatarUrl ?? ''} alt={props.username} />
      <h2>{props.username}</h2>
      <p>{props.bio}</p>

      {props.isEditable && (
        <button onClick={props.onEdit} disabled={props.isValid === false}>
          Edit Profile
        </button>
      )}

      {/* Render validation errors if any check fails */}
      {props.validationErrors?.map((err, i) => (
        <div key={i} className="error-hint" style={{color: 'red'}}>
          {err}
        </div>
      ))}
    </div>
  );
});

Generic Binder Features

The Generic Binder is a framework-agnostic engine that transforms raw JSON payload configurations into a cohesive reactive stream of strongly-typed props.

  • Automatic Resolution: Properties typed as DynamicString, DynamicNumber, and DynamicBoolean are automatically resolved to their current values (strings, numbers, booleans).
  • Two-Way Binding: If a schema uses a dynamic type (like DynamicString), the binder automatically injects a setter. For a property username, it adds props.setUsername(val: string), which updates the underlying data model.
  • Action Context: Action properties are resolved into ready-to-call functions. When called, they automatically resolve their deep context bindings (e.g., gathering form data from the model) before notifying the server.
  • Nested Values: Lists and objects are resolved item by item, so an action, a binding or a child list inside a list item resolves as it does at the top level, and its props are typed so.
  • Reactive Validation: If your schema includes checks, the binder reactively evaluates the rules and injects props.isValid and props.validationErrors based on the results of the logic functions.

Binderless Components

For advanced use cases where you need direct access to the ComponentContext or want to manage reactivity manually (e.g., for performance-critical animations), use createBinderlessComponentImplementation.

import {createBinderlessComponentImplementation} from '@a2ui/react/v0_9';

export const RawInspector = createBinderlessComponentImplementation(InspectorApi, ({context}) => {
  // Access the raw, unresolved component model and the data model directly
  const rawData = context.componentModel.properties;
  const componentId = context.componentModel.id;

  return (
    <details>
      <summary>Raw Component State (ID: {componentId})</summary>
      <pre>{JSON.stringify(rawData, null, 2)}</pre>
    </details>
  );
});

Defining Catalogs and Functions

Group your components and logic functions into a Catalog to be used by the MessageProcessor.

import {Catalog, createFunctionImplementation} from '@a2ui/web_core/v0_9';
import {z} from 'zod';

// 1. Implement a custom logic function
const myCheckFunc = createFunctionImplementation(
  {
    name: 'is_admin',
    returnType: 'boolean',
    schema: z.object({role: z.string()}),
  },
  args => args.role === 'admin',
);

// 2. Compose the catalog
export const myCatalog = new Catalog(
  'https://example.com/catalogs/v1.json',
  'v0.9', // Protocol version the catalog targets
  [MyProfile, RawInspector], // List of ReactComponentImplementation
  [myCheckFunc], // List of FunctionImplementation
);

Basic Catalog Components

@a2ui/web_core/v0_9/basic_catalog provides a basicCatalog with the standard components. They are W3C Custom Elements, and the React renderer renders them as elements:

  • Layout: Row, Column, List, Card, Tabs, Modal, Divider
  • Content: Text, Image, Icon, Video, AudioPlayer
  • Input: Button, TextField, CheckBox, ChoicePicker, Slider, DateTimeInput

Markdown in Text

Text renders its Markdown with a renderer the app provides. Give MarkdownContext a function that returns HTML, such as the one from @a2ui/markdown-it, or give MarkdownComponentContext a React component:

import {A2uiSurface, MarkdownComponentContext} from '@a2ui/react/v0_9';

const ChatMarkdown = ({markdown}: {markdown: string}) => <Markdown text={markdown} />;

<MarkdownComponentContext.Provider value={ChatMarkdown}>
  <A2uiSurface surface={surface} />
</MarkdownComponentContext.Provider>;

The component renders inside each Text, as part of the app's React tree, so it reads the app's context. When both are given, the component is used. Text reads one Markdown renderer for the whole page, so give the component to every A2uiSurface on the page or to none.

Styling and CSS Modules

The basic catalog components are self-contained and styled using CSS variables exposed from @a2ui/web_core.

You can use CSS Modules for styling your custom components. Most modern React environments (like Vite, Next.js, and Create React App) support them out of the box. A custom build setup must be configured to handle .module.css files, for example with css-loader and modules enabled in Webpack.

/* MyComponent.module.css */
.myComponent {
  display: flex;
  gap: var(--a2ui-spacing-m, 8px);
}

/* Use :global to target nested elements (like those generated by Markdown) */
.myComponent :global(p) {
  color: var(--a2ui-color-on-background);
}
import styles from './MyComponent.module.css';

export const MyComponent = createComponentImplementation(MyComponentApi, ({props}) => {
  return <div className={styles.myComponent}>{/* ... */}</div>;
});

Legacy Support (v0.8)

A2UI v0.8 is maintained for backward compatibility. While it remains the default export for the @a2ui/react package, it is recommended to transition to v0.9 for new features like type-safe binders and logic functions.

Import and Usage

To use the v0.8 renderer, you use the A2UIProvider and A2UIRenderer components:

import {A2UIProvider, A2UIRenderer, injectStyles} from '@a2ui/react/v0_8';

// Inject v0.8 styles
injectStyles();

function LegacyApp() {
  const handleAction = msg => console.log('Action:', msg);

  return (
    <A2UIProvider onAction={handleAction}>
      {/* Renders the surface using v0.8 logic */}
      <A2UIRenderer surfaceId="main" />
    </A2UIProvider>
  );
}

Key Differences (v0.8 vs v0.9)

Featurev0.8 (Legacy)v0.9 (Current)
ProtocolUses BeginRendering, SurfaceUpdate.Uses createSurface, updateComponents, updateDataModel.
Data FlowUnidirectional surface updates.Bidirectional synchronization (sendDataModel).
Type SafetyProps accessed via node.properties.Strongly typed props inferred from Zod schemas.
LogicLimited client-side logic.Extensible Function system (e.g., formatString, required).
ReactivityHooks-based (useA2UIComponent).Automatic via the Generic Binder middleware.
BindingManual resolution in component body.Declarative two-way binding with injected setters.

In v0.8, components are responsible for resolving their own dynamic values using hooks:

// v0.8 Manual Resolution
function TextField({node, surfaceId}) {
  const {resolveString, setValue} = useA2UIComponent(node, surfaceId);
  const label = resolveString(node.properties.label);
  // ...
}

In v0.9, this is handled by the Generic Binder before the component even renders, resulting in cleaner, framework-native view code.

Security

[!IMPORTANT] The sample code provided is for demonstration purposes and illustrates the mechanics of A2UI. When building production applications, it is critical to treat any agent operating outside of your direct control as a potentially untrusted entity.

All operational data received from an external agent—including its messages and UI definitions—should be handled as untrusted input. Malicious agents could attempt to spoof legitimate interfaces to deceive users (phishing), inject malicious scripts via property values (XSS), or generate excessive layout complexity to degrade client performance (DoS). If your application supports optional embedded content (such as iframes or web views), additional care must be taken to prevent exposure to malicious external sites.

Developer Responsibility: Failure to properly validate data and strictly sandbox rendered content can introduce severe vulnerabilities. Developers are responsible for implementing appropriate security measures—such as input sanitization, Content Security Policies (CSP), and secure credential handling—to protect their systems and users.

Development

Setup

# In the A2UI monorepo root
yarn install

Build

yarn build    # Build the package
yarn dev      # Watch mode

Type Check

yarn typecheck

Lint

yarn lint

Testing

Unit Tests

Uses Vitest + React Testing Library.

yarn test              # Run once
yarn test:watch    # Watch mode

Structure: tests/ ├── setup.ts # Initializes component catalog ├── helpers.tsx # TestWrapper, TestRenderer, message creators └── components/ # Component tests (*.test.tsx)

Example: ```tsx import { render, screen, fireEvent } from '@testing-library/react'; import { TestWrapper, TestRenderer, createSimpleMessages } from '../helpers';

it('should dispatch action on click', () => { const onAction = vi.fn(); const messages = createSimpleMessages('btn-1', 'Button', { child: 'text-1', action: { name: 'submit' }, });

render( );

fireEvent.click(screen.getByRole('button')); expect(onAction).toHaveBeenCalled(); }); ```

Visual Parity Testing

The React renderer maintains visual parity with the Lit renderer (reference implementation). A comprehensive test suite compares pixel-perfect screenshots between both renderers.

Running Visual Parity Tests

cd visual-parity
yarn test

Quick Commands

# Run all tests
yarn test

# Run specific component tests
yarn test --grep "button"

# Run with UI mode
yarn test:ui

# Start dev servers for manual inspection
yarn dev
# React: http://localhost:5001
# Lit: http://localhost:5002

Documentation

Key Concepts

  1. Structural Mirroring: React components mirror Lit's Shadow DOM structure
  2. CSS Selector Transformation: :host → .a2ui-surface .a2ui-{component}
  3. Specificity Matching: Uses :where() to match Lit's low specificity

版本与发布记录

@a2ui/react 共发布 17 个版本,首次发布于 2026/03/18;过去 12 个月发布了 17 个版本。

版本发布日期距今
0.15.0latest2026/11/024 天前
0.14.02026/10/1720 天前
0.13.02026/10/1027 天前
0.12.02026/09/291 个月前
0.11.12026/09/121 个月前
0.11.02026/09/012 个月前

关于 @a2ui/react 的常见问题

@a2ui/react 是什么?

React renderer for A2UI (Agent-to-User Interface)

如何安装 @a2ui/react?

在项目目录运行 npm install @a2ui/react,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:zod(^3.25.76)、react(^18.0.0 || ^19.0.0)、react-dom(^18.0.0 || ^19.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@a2ui/react 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@a2ui/react 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@a2ui/react 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@a2ui/react 可以商用吗?

@a2ui/react 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@a2ui/react 的下载量有多少?

根据 npm 官方下载统计,@a2ui/react 在 2026/10/05 – 2026/11/03 这 30 天内被下载 653,562 次,日均约 21,785 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@a2ui/react 还在维护吗?

最新版本 0.15.0 发布于 2026/11/02(4 天前);至今共发布 17 个版本,其中过去 12 个月发布了 17 个;首次发布于 2026/03/18。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。