@ainative/ai-kit-a2ui

December 24, 2025 Β· View on GitHub

Production-ready A2UI renderer for React 18.3+ using shadcn/ui components.

npm version License: MIT TypeScript React

React renderer for A2UI protocol (v0.9) that enables AI agents to dynamically generate rich, interactive user interfaces using declarative JSON. Built with shadcn/ui for beautiful, accessible components.

πŸš€ Features

  • βœ… 11/17 Components Implemented (64.7% complete) - Text, Heading, Button, Container, Divider, TextField, CheckBox, Slider, Tabs, List, ChoicePicker
  • 🎨 shadcn/ui Integration - Production-ready, accessible Radix UI components
  • βš›οΈ React Hooks - useA2UIAgent, useComponentState, useAgentAction (coming soon)
  • πŸ”’ TypeScript - Full type safety with strict mode
  • πŸ“ JSON Pointer Data Binding - RFC 6901 compliant reactivity
  • πŸ”Œ WebSocket Support - Real-time agent communication
  • β™Ώ Accessible - WCAG 2.1 AA compliant components
  • 🎭 Customizable - Tailwind CSS theming support
  • βœ… 70 Tests Passing - Comprehensive test coverage with Vitest

πŸ“¦ Installation

npm install @ainative/ai-kit-a2ui
yarn add @ainative/ai-kit-a2ui
pnpm add @ainative/ai-kit-a2ui

Peer Dependencies

This package requires React 18.3+ and React DOM:

npm install react@^18.3.0 react-dom@^18.3.0

Tailwind CSS Setup

Add the package to your Tailwind config:

// tailwind.config.js
module.exports = {
  content: [
    './src/**/*.{js,ts,jsx,tsx}',
    './node_modules/@ainative/ai-kit-a2ui/dist/**/*.{js,ts,jsx,tsx}'
  ],
  // ... rest of config
}

🎯 Quick Start

Basic Usage

import { A2UIRenderer } from '@ainative/ai-kit-a2ui'
import '@ainative/ai-kit-a2ui/styles.css' // Optional: Default styles

function App() {
  return (
    <A2UIRenderer
      agentUrl="wss://api.ainative.studio/agents/dashboard"
      onAction={(action, context) => {
        console.log('User action:', action, context)
      }}
      onError={(error) => {
        console.error('A2UI error:', error)
      }}
      theme="light" // or "dark"
    />
  )
}

With Manual Component Rendering

import { ComponentMapper } from '@ainative/ai-kit-a2ui/components'
import type { A2UIComponent } from '@ainative/ai-kit-a2ui-core/types'

const component: A2UIComponent = {
  id: 'card-1',
  type: 'container',
  properties: {},
  children: [
    {
      id: 'heading-1',
      type: 'heading',
      properties: { value: 'Welcome', level: 1 }
    },
    {
      id: 'text-1',
      type: 'text',
      properties: { value: 'This is a dynamic UI from an AI agent!' }
    }
  ]
}

function ManualRender() {
  const dataModel = {}
  const handleAction = (action: string, context: Record<string, unknown>) => {
    console.log('Action:', action, context)
  }

  return (
    <ComponentMapper
      component={component}
      dataModel={dataModel}
      onAction={handleAction}
    />
  )
}

🧩 Component Catalog

βœ… Implemented Components (11/17)

Content Components

ComponentTypeDescriptionPropertiesExample
TexttextDisplay static or dynamic textvalue{ type: 'text', properties: { value: 'Hello World' } }
HeadingheadingHeading with configurable levelvalue, level (1-6){ type: 'heading', properties: { value: 'Title', level: 2 } }

Input Components

ComponentTypeDescriptionPropertiesExample
ButtonbuttonClickable button with actionlabel, action{ type: 'button', properties: { label: 'Submit', action: 'form-submit' } }
TextFieldtextFieldText input with labellabel, value, placeholder, inputType, disabled, required, action{ type: 'textField', properties: { label: 'Email', placeholder: 'you@example.com' } }
CheckBoxcheckBoxCheckbox with labellabel, checked, disabled, required, action{ type: 'checkBox', properties: { label: 'Accept terms', checked: false } }
SlidersliderRange slider inputlabel, value, min, max, step, disabled, showValue, action{ type: 'slider', properties: { label: 'Volume', min: 0, max: 100, value: 50 } }
ChoicePickerchoicePickerSelect dropdownlabel, value, placeholder, options, disabled, required, action{ type: 'choicePicker', properties: { label: 'Country', options: [...] } }

Layout Components

ComponentTypeDescriptionPropertiesExample
ContainercontainerGeneric container for childrenN/A{ type: 'container', children: [...] }
TabstabsTabbed navigationdefaultValue, tabs, action{ type: 'tabs', properties: { tabs: [{ value: 'tab1', label: 'Tab 1' }] } }
ListlistOrdered or unordered listitems, ordered, clickable, dividers, action{ type: 'list', properties: { items: [{ id: '1', label: 'Item 1' }] } }

Utility Components

ComponentTypeDescriptionPropertiesExample
DividerdividerHorizontal separatorN/A{ type: 'divider' }

🚧 Coming Soon (6/17)

ComponentTypeStatusEstimated
ModalmodalIn developmentIssue #7
DateTimeInputdateTimeInputPlannedIssue #8
ImageimagePlannedIssue #9
VideovideoPlannedIssue #9
AudioPlayeraudioPlayerPlannedIssue #9
IconiconPlannedIssue #9

πŸ“– API Reference

A2UIRenderer

Main component that connects to an agent via WebSocket and renders the UI.

interface A2UIRendererProps {
  /** WebSocket URL to connect to agent */
  agentUrl: string

  /** Callback when user performs an action */
  onAction?: (action: string, context: Record<string, unknown>) => void

  /** Callback when an error occurs */
  onError?: (error: Error) => void

  /** Theme mode */
  theme?: 'light' | 'dark'

  /** Custom class name */
  className?: string

  /** Loading component to show during connection */
  loadingComponent?: React.ReactNode

  /** Error component to show on connection failure */
  errorComponent?: (error: Error) => React.ReactNode
}

function A2UIRenderer(props: A2UIRendererProps): JSX.Element

Example:

<A2UIRenderer
  agentUrl="wss://api.ainative.studio/agents/chat"
  onAction={(action, context) => {
    if (action === 'send-message') {
      console.log('Message sent:', context.message)
    }
  }}
  onError={(error) => {
    console.error('Connection failed:', error)
  }}
  theme="dark"
  loadingComponent={<div>Connecting to agent...</div>}
  errorComponent={(error) => <div>Error: {error.message}</div>}
/>

ComponentMapper

Renders a single A2UI component with data binding and action handling.

interface ComponentMapperProps {
  /** The A2UI component to render */
  component: A2UIComponent

  /** The current data model for data binding */
  dataModel: Record<string, unknown>

  /** Callback when user performs an action */
  onAction: (action: string, context: Record<string, unknown>) => void
}

function ComponentMapper(props: ComponentMapperProps): JSX.Element

Example:

const component = {
  id: 'field-1',
  type: 'textField',
  properties: {
    label: 'Username',
    value: '/user/username', // JSON Pointer
    action: 'update-username'
  }
}

const dataModel = {
  user: { username: 'alice' }
}

<ComponentMapper
  component={component}
  dataModel={dataModel}
  onAction={(action, context) => {
    console.log(action, context) // "update-username", { componentId: 'field-1', value: 'bob' }
  }}
/>

React Hooks (Coming Soon)

// useA2UIAgent - Connect to agent and manage state
const { components, dataModel, surfaceId, status } = useA2UIAgent(agentUrl)

// useComponentState - Access data by JSON Pointer
const userName = useComponentState('/user/name')

// useAgentAction - Send actions to agent
const sendAction = useAgentAction()
sendAction('submit', { formData: {...} })

// useA2UIConnection - Connection status
const { isConnected, isReconnecting } = useA2UIConnection()

🎨 Styling & Theming

Using Tailwind CSS

Components are built with Tailwind utility classes and support custom theming:

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        // Custom A2UI component colors
        'a2ui-primary': '#3b82f6',
        'a2ui-secondary': '#64748b',
      }
    }
  }
}

Dark Mode

Components support dark mode via Tailwind's dark: variant:

<A2UIRenderer
  agentUrl="wss://..."
  theme="dark" // Applies dark mode classes
/>

Custom Styles

Override component styles using CSS modules or global styles:

/* Custom styles for A2UI components */
.a2ui-textfield input {
  border-radius: 0.5rem;
  border-color: #3b82f6;
}

.a2ui-button {
  background: linear-gradient(to right, #3b82f6, #8b5cf6);
}

πŸ“ Data Binding

Components support JSON Pointer (RFC 6901) for data binding:

const component = {
  id: 'email-field',
  type: 'textField',
  properties: {
    label: 'Email',
    value: '/user/profile/email', // JSON Pointer reference
    placeholder: 'you@example.com'
  }
}

const dataModel = {
  user: {
    profile: {
      email: 'alice@example.com' // Automatically bound to TextField
    }
  }
}

<ComponentMapper component={component} dataModel={dataModel} onAction={...} />
// TextField will display: alice@example.com

Supported Binding Properties:

  • value - Initial value or JSON Pointer
  • options - Array of options or JSON Pointer (for ChoicePicker)
  • items - Array of items or JSON Pointer (for List)
  • Any string property starting with / is treated as a JSON Pointer

πŸ§ͺ Testing

Run Tests

# Run all tests
npm test

# Watch mode
npm run test:watch

# Coverage report
npm run test:coverage

# UI mode (Vitest UI)
npm run test:ui

Test Status

Test Files  8 passed (8)
     Tests  70 passed | 1 skipped (71)
  Coverage  TBD (run npm run test:coverage)

Components Tested:

  • βœ… A2UIRenderer (10 tests)
  • βœ… ComponentMapper (11 tests)
  • βœ… TextField (11 tests)
  • βœ… CheckBox (8 tests)
  • βœ… Slider (8 tests)
  • βœ… Tabs (7 tests)
  • βœ… List (9 tests)
  • βœ… ChoicePicker (7 tests, 1 skipped)

πŸ“¦ Bundle Size

  • ESM: ~45 KB minified, ~15 KB gzipped (excluding React)
  • Dependencies: @ainative/ai-kit-a2ui-core, @radix-ui/react-*, clsx, tailwind-merge
  • Peer Dependencies: React 18.3+, React DOM 18.3+

πŸ› οΈ Development

Local Setup

# Clone repository
git clone https://github.com/AINative-Studio/ai-kit-a2ui.git
cd ai-kit-a2ui

# Install dependencies
npm install

# Run tests in watch mode
npm run test:watch

# Build
npm run build

# Type check
npm run type-check

Project Structure

@ainative/ai-kit-a2ui/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”œβ”€β”€ A2UIRenderer.tsx        # Main renderer component
β”‚   β”‚   β”œβ”€β”€ ComponentMapper.tsx     # Component router
β”‚   β”‚   β”œβ”€β”€ ComponentRenderer.tsx   # Individual component wrapper
β”‚   β”‚   └── ui/                     # shadcn/ui components
β”‚   β”‚       β”œβ”€β”€ input.tsx
β”‚   β”‚       β”œβ”€β”€ label.tsx
β”‚   β”‚       β”œβ”€β”€ checkbox.tsx
β”‚   β”‚       β”œβ”€β”€ slider.tsx
β”‚   β”‚       β”œβ”€β”€ tabs.tsx
β”‚   β”‚       └── select.tsx
β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   └── utils.ts                # Utility functions (cn)
β”‚   └── index.ts                    # Public exports
β”œβ”€β”€ tests/
β”‚   β”œβ”€β”€ components/                 # Component tests
β”‚   └── setup.ts                    # Test setup (jsdom mocks)
β”œβ”€β”€ package.json
β”œβ”€β”€ tsconfig.json
β”œβ”€β”€ vitest.config.ts
└── README.md

πŸ”§ Configuration

TypeScript

// tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "jsx": "react-jsx",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

Vitest

// vitest.config.ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    environment: 'jsdom',
    setupFiles: ['./tests/setup.ts'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'html'],
      exclude: ['**/node_modules/**', '**/dist/**', '**/tests/**']
    }
  }
})

πŸ’‘ Examples

Contact Form

const contactFormComponents: A2UIComponent[] = [
  {
    id: 'form-container',
    type: 'container',
    children: [
      {
        id: 'heading',
        type: 'heading',
        properties: { value: 'Contact Us', level: 2 }
      },
      {
        id: 'name-field',
        type: 'textField',
        properties: {
          label: 'Name',
          value: '/form/name',
          required: true,
          action: 'field-change'
        }
      },
      {
        id: 'email-field',
        type: 'textField',
        properties: {
          label: 'Email',
          value: '/form/email',
          inputType: 'email',
          required: true,
          action: 'field-change'
        }
      },
      {
        id: 'subscribe-checkbox',
        type: 'checkBox',
        properties: {
          label: 'Subscribe to newsletter',
          checked: false,
          action: 'checkbox-change'
        }
      },
      {
        id: 'submit-button',
        type: 'button',
        properties: {
          label: 'Send Message',
          action: 'submit-form'
        }
      }
    ]
  }
]

const dataModel = {
  form: { name: '', email: '' }
}

<ComponentMapper
  component={contactFormComponents[0]}
  dataModel={dataModel}
  onAction={(action, context) => {
    if (action === 'submit-form') {
      console.log('Form submitted:', dataModel.form)
    }
  }}
/>

Dashboard with Tabs

const dashboardComponents: A2UIComponent[] = [
  {
    id: 'dashboard',
    type: 'tabs',
    properties: {
      defaultValue: 'overview',
      tabs: [
        {
          value: 'overview',
          label: 'Overview',
          content: 'Dashboard overview content...'
        },
        {
          value: 'analytics',
          label: 'Analytics',
          content: 'Analytics charts and graphs...'
        },
        {
          value: 'settings',
          label: 'Settings',
          content: 'Settings panel...'
        }
      ],
      action: 'tab-change'
    }
  }
]

Settings Panel

const settingsComponents: A2UIComponent[] = [
  {
    id: 'settings',
    type: 'container',
    children: [
      {
        id: 'heading',
        type: 'heading',
        properties: { value: 'Settings', level: 1 }
      },
      {
        id: 'theme-picker',
        type: 'choicePicker',
        properties: {
          label: 'Theme',
          value: '/settings/theme',
          options: [
            { value: 'light', label: 'Light' },
            { value: 'dark', label: 'Dark' },
            { value: 'auto', label: 'Auto' }
          ],
          action: 'theme-change'
        }
      },
      {
        id: 'volume-slider',
        type: 'slider',
        properties: {
          label: 'Notification Volume',
          value: '/settings/volume',
          min: 0,
          max: 100,
          step: 10,
          showValue: true,
          action: 'volume-change'
        }
      },
      {
        id: 'notifications-checkbox',
        type: 'checkBox',
        properties: {
          label: 'Enable notifications',
          checked: '/settings/notificationsEnabled',
          action: 'notifications-toggle'
        }
      }
    ]
  }
]

const dataModel = {
  settings: {
    theme: 'dark',
    volume: 75,
    notificationsEnabled: true
  }
}

🀝 Contributing

We welcome contributions! See CONTRIBUTING.md for guidelines.

Development Workflow

  1. Fork the repository
  2. Clone your fork: git clone https://github.com/YOUR_USERNAME/ai-kit-a2ui.git
  3. Create branch: git checkout -b feature/my-feature
  4. Make changes following TDD (RED β†’ GREEN β†’ REFACTOR)
  5. Run tests: npm test (all tests must pass)
  6. Commit: git commit -m "Add my feature"
  7. Push: git push origin feature/my-feature
  8. Create Pull Request on GitHub

Code Style

  • TDD Required - Write tests first (RED), then implementation (GREEN), then refactor
  • TypeScript - Use strict mode, no any types
  • Naming - PascalCase for components, camelCase for functions
  • Formatting - Run npm run format before committing

πŸ“ License

MIT Β© AINative Studio

πŸ™‹ Support

πŸ—ΊοΈ Roadmap

v0.2.0 (Current)

  • βœ… Core rendering (11/17 components)
  • βœ… JSON Pointer data binding
  • βœ… WebSocket transport
  • βœ… TDD workflow (70+ tests)

v0.3.0 (Next)

  • 🚧 Remaining 6 components (Modal, DateTimeInput, Image, Video, AudioPlayer, Icon)
  • 🚧 React Hooks (useA2UIAgent, useComponentState, useAgentAction)
  • 🚧 80%+ test coverage
  • 🚧 Performance optimizations

v1.0.0 (Stable)

  • πŸ“‹ All 17 standard components
  • πŸ“‹ Complete React Hooks API
  • πŸ“‹ Comprehensive examples
  • πŸ“‹ Storybook documentation
  • πŸ“‹ Chrome DevTools extension

Built with ❀️ by AINative Studio

Status: 🟒 Alpha - 11/17 components implemented, production-ready core, active development