diff --git a/.github/workflows/react_renderer.yml b/.github/workflows/react_renderer.yml
index 0732db2ad..de111ccc4 100644
--- a/.github/workflows/react_renderer.yml
+++ b/.github/workflows/react_renderer.yml
@@ -58,6 +58,14 @@ jobs:
- name: Test React renderer
working-directory: ./renderers/react
run: npm test
+
+ - name: Build React Demo
+ working-directory: ./renderers/react
+ run: npm run build:demo
+
+ - name: Test React Demo
+ working-directory: ./renderers/react
+ run: npm run test:demo
lint:
runs-on: ubuntu-latest
diff --git a/.gitignore b/.gitignore
index 879a4f263..822fbfab3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,4 @@ a2a_agents/python/a2ui_agent/src/a2ui/assets/**/*.json
agent_sdks/python/src/a2ui/assets/**/*.json
## Generated JS file from the strictly-typed `sandbox.ts`.
samples/client/angular/projects/mcp_calculator/public/sandbox_iframe/sandbox.js
+*.tsbuildinfo
diff --git a/renderers/react/a2ui_explorer/README.md b/renderers/react/a2ui_explorer/README.md
new file mode 100644
index 000000000..7db40651f
--- /dev/null
+++ b/renderers/react/a2ui_explorer/README.md
@@ -0,0 +1,62 @@
+# A2UI React Gallery App
+
+This is the reference Gallery Application for the A2UI React renderer. It allows you to explore A2UI samples, inspect the live data model, and step through the message rendering process.
+
+## Prerequisites
+
+This application depends on the following local libraries in this repository:
+1. `@a2ui/web_core` (located in `renderers/web_core`)
+2. `@a2ui/react_prototype` (located in `renderers/react_prototype`)
+
+## Building Dependencies
+
+Before running the gallery app, you must build the local renderer library:
+
+```bash
+# Navigate to the React renderer library
+cd ../../../renderers/react_prototype
+
+# Install and build the library
+npm install
+npm run build
+```
+
+*Note: Ensure `@a2ui/web_core` is also built if you have made changes to the core logic.*
+
+## Setup and Development
+
+Once the dependencies are built, you can start the gallery app:
+
+```bash
+# Navigate to this directory
+cd samples/client/react
+
+# Install dependencies
+npm install
+
+# Start the development server
+npm run dev
+```
+
+## Building for Production
+
+To create a production build of the gallery app:
+
+```bash
+npm run build
+```
+
+## Running Tests
+
+To run the integration tests:
+
+```bash
+npm test
+```
+
+## Gallery Features
+
+- **3-Column Layout**: Left (Sample selection), Center (Live preview & Message stepper), Right (Data model & Action logs).
+- **Progressive Stepper**: Use the "Advance" buttons next to each JSON message to see how the UI builds up incrementally.
+- **Action Logs**: View real-time logs of actions triggered by user interactions.
+- **Data Model Inspector**: Observe how the surface's data model changes as you interact with form fields.
diff --git a/renderers/react/a2ui_explorer/eslint.config.js b/renderers/react/a2ui_explorer/eslint.config.js
new file mode 100644
index 000000000..0842785dd
--- /dev/null
+++ b/renderers/react/a2ui_explorer/eslint.config.js
@@ -0,0 +1,39 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+import { defineConfig, globalIgnores } from 'eslint/config'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ js.configs.recommended,
+ tseslint.configs.recommended,
+ reactHooks.configs.flat.recommended,
+ reactRefresh.configs.vite,
+ ],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ },
+])
diff --git a/renderers/react/a2ui_explorer/index.html b/renderers/react/a2ui_explorer/index.html
new file mode 100644
index 000000000..77fa01208
--- /dev/null
+++ b/renderers/react/a2ui_explorer/index.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+ react
+
+
+
+
+
+
diff --git a/renderers/react/a2ui_explorer/public/vite.svg b/renderers/react/a2ui_explorer/public/vite.svg
new file mode 100644
index 000000000..e7b8dfb1b
--- /dev/null
+++ b/renderers/react/a2ui_explorer/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/renderers/react/a2ui_explorer/src/App.css b/renderers/react/a2ui_explorer/src/App.css
new file mode 100644
index 000000000..5c31aced7
--- /dev/null
+++ b/renderers/react/a2ui_explorer/src/App.css
@@ -0,0 +1,58 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#root {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
diff --git a/renderers/react/a2ui_explorer/src/App.tsx b/renderers/react/a2ui_explorer/src/App.tsx
new file mode 100644
index 000000000..dc08e5551
--- /dev/null
+++ b/renderers/react/a2ui_explorer/src/App.tsx
@@ -0,0 +1,328 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { useState, useEffect, useSyncExternalStore, useCallback } from 'react';
+import { MessageProcessor, SurfaceModel } from '@a2ui/web_core/v0_9';
+import { minimalCatalog, basicCatalog, A2uiSurface } from '@a2ui/react/v0_9';
+
+// Import Minimal examples
+import min1 from "../../../../specification/v0_9/json/catalogs/minimal/examples/1_simple_text.json";
+import min2 from "../../../../specification/v0_9/json/catalogs/minimal/examples/2_row_layout.json";
+import min3 from "../../../../specification/v0_9/json/catalogs/minimal/examples/3_interactive_button.json";
+import min4 from "../../../../specification/v0_9/json/catalogs/minimal/examples/4_login_form.json";
+import min5 from "../../../../specification/v0_9/json/catalogs/minimal/examples/5_complex_layout.json";
+import min6 from "../../../../specification/v0_9/json/catalogs/minimal/examples/6_capitalized_text.json";
+import min7 from "../../../../specification/v0_9/json/catalogs/minimal/examples/7_incremental.json";
+
+// Import Basic examples
+import b01 from "../../../../specification/v0_9/json/catalogs/basic/examples/01_flight-status.json";
+import b02 from "../../../../specification/v0_9/json/catalogs/basic/examples/02_email-compose.json";
+import b03 from "../../../../specification/v0_9/json/catalogs/basic/examples/03_calendar-day.json";
+import b04 from "../../../../specification/v0_9/json/catalogs/basic/examples/04_weather-current.json";
+import b05 from "../../../../specification/v0_9/json/catalogs/basic/examples/05_product-card.json";
+import b06 from "../../../../specification/v0_9/json/catalogs/basic/examples/06_music-player.json";
+import b07 from "../../../../specification/v0_9/json/catalogs/basic/examples/07_task-card.json";
+import b08 from "../../../../specification/v0_9/json/catalogs/basic/examples/08_user-profile.json";
+import b09 from "../../../../specification/v0_9/json/catalogs/basic/examples/09_login-form.json";
+import b10 from "../../../../specification/v0_9/json/catalogs/basic/examples/10_notification-permission.json";
+import b11 from "../../../../specification/v0_9/json/catalogs/basic/examples/11_purchase-complete.json";
+import b12 from "../../../../specification/v0_9/json/catalogs/basic/examples/12_chat-message.json";
+import b13 from "../../../../specification/v0_9/json/catalogs/basic/examples/13_coffee-order.json";
+import b14 from "../../../../specification/v0_9/json/catalogs/basic/examples/14_sports-player.json";
+import b15 from "../../../../specification/v0_9/json/catalogs/basic/examples/15_account-balance.json";
+import b16 from "../../../../specification/v0_9/json/catalogs/basic/examples/16_workout-summary.json";
+import b17 from "../../../../specification/v0_9/json/catalogs/basic/examples/17_event-detail.json";
+import b18 from "../../../../specification/v0_9/json/catalogs/basic/examples/18_track-list.json";
+import b19 from "../../../../specification/v0_9/json/catalogs/basic/examples/19_software-purchase.json";
+import b20 from "../../../../specification/v0_9/json/catalogs/basic/examples/20_restaurant-card.json";
+import b21 from "../../../../specification/v0_9/json/catalogs/basic/examples/21_shipping-status.json";
+import b22 from "../../../../specification/v0_9/json/catalogs/basic/examples/22_credit-card.json";
+import b23 from "../../../../specification/v0_9/json/catalogs/basic/examples/23_step-counter.json";
+import b24 from "../../../../specification/v0_9/json/catalogs/basic/examples/24_recipe-card.json";
+import b25 from "../../../../specification/v0_9/json/catalogs/basic/examples/25_contact-card.json";
+import b26 from "../../../../specification/v0_9/json/catalogs/basic/examples/26_podcast-episode.json";
+import b27 from "../../../../specification/v0_9/json/catalogs/basic/examples/27_stats-card.json";
+import b28 from "../../../../specification/v0_9/json/catalogs/basic/examples/28_countdown-timer.json";
+import b29 from "../../../../specification/v0_9/json/catalogs/basic/examples/29_movie-card.json";
+
+const exampleFiles = [
+ { key: 'min1', data: min1, catalog: 'Minimal' },
+ { key: 'min2', data: min2, catalog: 'Minimal' },
+ { key: 'min3', data: min3, catalog: 'Minimal' },
+ { key: 'min4', data: min4, catalog: 'Minimal' },
+ { key: 'min5', data: min5, catalog: 'Minimal' },
+ { key: 'min6', data: min6, catalog: 'Minimal' },
+ { key: 'min7', data: min7, catalog: 'Minimal' },
+ { key: 'b01', data: b01, catalog: 'Basic' },
+ { key: 'b02', data: b02, catalog: 'Basic' },
+ { key: 'b03', data: b03, catalog: 'Basic' },
+ { key: 'b04', data: b04, catalog: 'Basic' },
+ { key: 'b05', data: b05, catalog: 'Basic' },
+ { key: 'b06', data: b06, catalog: 'Basic' },
+ { key: 'b07', data: b07, catalog: 'Basic' },
+ { key: 'b08', data: b08, catalog: 'Basic' },
+ { key: 'b09', data: b09, catalog: 'Basic' },
+ { key: 'b10', data: b10, catalog: 'Basic' },
+ { key: 'b11', data: b11, catalog: 'Basic' },
+ { key: 'b12', data: b12, catalog: 'Basic' },
+ { key: 'b13', data: b13, catalog: 'Basic' },
+ { key: 'b14', data: b14, catalog: 'Basic' },
+ { key: 'b15', data: b15, catalog: 'Basic' },
+ { key: 'b16', data: b16, catalog: 'Basic' },
+ { key: 'b17', data: b17, catalog: 'Basic' },
+ { key: 'b18', data: b18, catalog: 'Basic' },
+ { key: 'b19', data: b19, catalog: 'Basic' },
+ { key: 'b20', data: b20, catalog: 'Basic' },
+ { key: 'b21', data: b21, catalog: 'Basic' },
+ { key: 'b22', data: b22, catalog: 'Basic' },
+ { key: 'b23', data: b23, catalog: 'Basic' },
+ { key: 'b24', data: b24, catalog: 'Basic' },
+ { key: 'b25', data: b25, catalog: 'Basic' },
+ { key: 'b26', data: b26, catalog: 'Basic' },
+ { key: 'b27', data: b27, catalog: 'Basic' },
+ { key: 'b28', data: b28, catalog: 'Basic' },
+ { key: 'b29', data: b29, catalog: 'Basic' },
+];
+
+const getMessages = (ex: any) => Array.isArray(ex) ? ex : ex?.messages;
+
+const DataModelViewer = ({ surface }: { surface: SurfaceModel }) => {
+ const subscribeHook = useCallback((callback: () => void) => {
+ const bound = surface.dataModel.subscribe('/', callback);
+ return () => bound.unsubscribe();
+ }, [surface]);
+
+ const getSnapshot = useCallback(() => {
+ return JSON.stringify(surface.dataModel.get('/'), null, 2);
+ }, [surface]);
+
+ const dataString = useSyncExternalStore(subscribeHook, getSnapshot);
+
+ return (
+
+ );
+});
diff --git a/renderers/react/src/v0_9/catalog/basic/components/ReactButton.tsx b/renderers/react/src/v0_9/catalog/basic/components/ReactButton.tsx
new file mode 100644
index 000000000..cd8e332e2
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/basic/components/ReactButton.tsx
@@ -0,0 +1,47 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {createReactComponent} from '../../../adapter';
+import {ButtonApi} from '@a2ui/web_core/v0_9/basic_catalog';
+import {LEAF_MARGIN} from '../utils';
+
+export const ReactButton = createReactComponent(ButtonApi, ({props, buildChild}) => {
+ const style: React.CSSProperties = {
+ margin: LEAF_MARGIN,
+ padding: '8px 16px',
+ cursor: 'pointer',
+ border: props.variant === 'borderless' ? 'none' : '1px solid #ccc',
+ backgroundColor:
+ props.variant === 'primary'
+ ? 'var(--a2ui-primary-color, #007bff)'
+ : props.variant === 'borderless'
+ ? 'transparent'
+ : '#fff',
+ color: props.variant === 'primary' ? '#fff' : 'inherit',
+ borderRadius: '4px',
+ display: 'inline-flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ boxSizing: 'border-box',
+ };
+
+ return (
+
+ );
+});
diff --git a/renderers/react/src/v0_9/catalog/basic/components/ReactCard.tsx b/renderers/react/src/v0_9/catalog/basic/components/ReactCard.tsx
new file mode 100644
index 000000000..a945f3213
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/basic/components/ReactCard.tsx
@@ -0,0 +1,31 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {createReactComponent} from '../../../adapter';
+import {CardApi} from '@a2ui/web_core/v0_9/basic_catalog';
+import {getBaseContainerStyle} from '../utils';
+
+export const ReactCard = createReactComponent(CardApi, ({props, buildChild}) => {
+ const style: React.CSSProperties = {
+ ...getBaseContainerStyle(),
+ backgroundColor: '#fff',
+ boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
+ width: '100%',
+ };
+
+ return
{props.child ? buildChild(props.child) : null}
;
+});
diff --git a/renderers/react/src/v0_9/catalog/basic/components/ReactCheckBox.tsx b/renderers/react/src/v0_9/catalog/basic/components/ReactCheckBox.tsx
new file mode 100644
index 000000000..6410f70d2
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/basic/components/ReactCheckBox.tsx
@@ -0,0 +1,57 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {createReactComponent} from '../../../adapter';
+import {CheckBoxApi} from '@a2ui/web_core/v0_9/basic_catalog';
+import {LEAF_MARGIN} from '../utils';
+
+export const ReactCheckBox = createReactComponent(CheckBoxApi, ({props}) => {
+ const onChange = (e: React.ChangeEvent) => {
+ props.setValue(e.target.checked);
+ };
+
+ const uniqueId = React.useId();
+
+ const hasError = props.validationErrors && props.validationErrors.length > 0;
+
+ return (
+
+ );
+});
diff --git a/renderers/react/src/v0_9/catalog/basic/components/ReactColumn.tsx b/renderers/react/src/v0_9/catalog/basic/components/ReactColumn.tsx
new file mode 100644
index 000000000..20c74f855
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/basic/components/ReactColumn.tsx
@@ -0,0 +1,38 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {createReactComponent} from '../../../adapter';
+import {ColumnApi} from '@a2ui/web_core/v0_9/basic_catalog';
+import {ReactChildList} from './ReactChildList';
+import {mapJustify, mapAlign} from '../utils';
+
+export const ReactColumn = createReactComponent(ColumnApi, ({props, buildChild, context}) => {
+ return (
+
+
+
+ );
+});
diff --git a/renderers/react/src/v0_9/catalog/basic/components/ReactDateTimeInput.tsx b/renderers/react/src/v0_9/catalog/basic/components/ReactDateTimeInput.tsx
new file mode 100644
index 000000000..cbab5f52f
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/basic/components/ReactDateTimeInput.tsx
@@ -0,0 +1,68 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {createReactComponent} from '../../../adapter';
+import {DateTimeInputApi} from '@a2ui/web_core/v0_9/basic_catalog';
+import {LEAF_MARGIN, STANDARD_BORDER, STANDARD_RADIUS} from '../utils';
+
+export const ReactDateTimeInput = createReactComponent(DateTimeInputApi, ({props}) => {
+ const onChange = (e: React.ChangeEvent) => {
+ props.setValue(e.target.value);
+ };
+
+ const uniqueId = React.useId();
+
+ // Map enableDate/enableTime to input type
+ let type = 'datetime-local';
+ if (props.enableDate && !props.enableTime) type = 'date';
+ if (!props.enableDate && props.enableTime) type = 'time';
+
+ const style: React.CSSProperties = {
+ padding: '8px',
+ width: '100%',
+ border: STANDARD_BORDER,
+ borderRadius: STANDARD_RADIUS,
+ boxSizing: 'border-box',
+ };
+
+ return (
+
+ {props.label && (
+
+ )}
+
+
+ );
+});
diff --git a/renderers/react/src/v0_9/catalog/basic/components/ReactDivider.tsx b/renderers/react/src/v0_9/catalog/basic/components/ReactDivider.tsx
new file mode 100644
index 000000000..d0aa2e88b
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/basic/components/ReactDivider.tsx
@@ -0,0 +1,39 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {createReactComponent} from '../../../adapter';
+import {DividerApi} from '@a2ui/web_core/v0_9/basic_catalog';
+import {LEAF_MARGIN} from '../utils';
+
+export const ReactDivider = createReactComponent(DividerApi, ({props}) => {
+ const isVertical = props.axis === 'vertical';
+ const style: React.CSSProperties = {
+ margin: LEAF_MARGIN,
+ border: 'none',
+ backgroundColor: '#ccc',
+ };
+
+ if (isVertical) {
+ style.width = '1px';
+ style.height = '100%';
+ } else {
+ style.width = '100%';
+ style.height = '1px';
+ }
+
+ return ;
+});
diff --git a/renderers/react/src/v0_9/catalog/basic/components/ReactIcon.tsx b/renderers/react/src/v0_9/catalog/basic/components/ReactIcon.tsx
new file mode 100644
index 000000000..748c5579b
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/basic/components/ReactIcon.tsx
@@ -0,0 +1,39 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {createReactComponent} from '../../../adapter';
+import {IconApi} from '@a2ui/web_core/v0_9/basic_catalog';
+import {getBaseLeafStyle} from '../utils';
+
+export const ReactIcon = createReactComponent(IconApi, ({props}) => {
+ const iconName = typeof props.name === 'string' ? props.name : (props.name as any)?.path;
+ const style: React.CSSProperties = {
+ ...getBaseLeafStyle(),
+ fontSize: '24px',
+ width: '24px',
+ height: '24px',
+ display: 'inline-flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ };
+
+ return (
+
+ {iconName}
+
+ );
+});
diff --git a/renderers/react/src/v0_9/catalog/basic/components/ReactImage.tsx b/renderers/react/src/v0_9/catalog/basic/components/ReactImage.tsx
new file mode 100644
index 000000000..cb9e427f3
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/basic/components/ReactImage.tsx
@@ -0,0 +1,48 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {createReactComponent} from '../../../adapter';
+import {ImageApi} from '@a2ui/web_core/v0_9/basic_catalog';
+import {getBaseLeafStyle} from '../utils';
+
+export const ReactImage = createReactComponent(ImageApi, ({props}) => {
+ const style: React.CSSProperties = {
+ ...getBaseLeafStyle(),
+ objectFit: props.fit as any,
+ width: '100%',
+ height: 'auto',
+ display: 'block',
+ };
+
+ if (props.variant === 'icon') {
+ style.width = '24px';
+ style.height = '24px';
+ } else if (props.variant === 'avatar') {
+ style.width = '40px';
+ style.height = '40px';
+ style.borderRadius = '50%';
+ } else if (props.variant === 'smallFeature') {
+ style.maxWidth = '100px';
+ } else if (props.variant === 'largeFeature') {
+ style.maxHeight = '400px';
+ } else if (props.variant === 'header') {
+ style.height = '200px';
+ style.objectFit = 'cover';
+ }
+
+ return ;
+});
diff --git a/renderers/react/src/v0_9/catalog/basic/components/ReactList.tsx b/renderers/react/src/v0_9/catalog/basic/components/ReactList.tsx
new file mode 100644
index 000000000..77e1e90de
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/basic/components/ReactList.tsx
@@ -0,0 +1,41 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {createReactComponent} from '../../../adapter';
+import {ListApi} from '@a2ui/web_core/v0_9/basic_catalog';
+import {ReactChildList} from './ReactChildList';
+import {mapAlign} from '../utils';
+
+export const ReactList = createReactComponent(ListApi, ({props, buildChild, context}) => {
+ const isHorizontal = props.direction === 'horizontal';
+ const style: React.CSSProperties = {
+ display: 'flex',
+ flexDirection: isHorizontal ? 'row' : 'column',
+ alignItems: mapAlign(props.align),
+ overflowX: isHorizontal ? 'auto' : 'hidden',
+ overflowY: isHorizontal ? 'hidden' : 'auto',
+ width: '100%',
+ margin: 0,
+ padding: 0,
+ };
+
+ return (
+
+
+
+ );
+});
diff --git a/renderers/react/src/v0_9/catalog/basic/components/ReactModal.tsx b/renderers/react/src/v0_9/catalog/basic/components/ReactModal.tsx
new file mode 100644
index 000000000..70bb51b2f
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/basic/components/ReactModal.tsx
@@ -0,0 +1,77 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {useState} from 'react';
+import {createReactComponent} from '../../../adapter';
+import {ModalApi} from '@a2ui/web_core/v0_9/basic_catalog';
+
+export const ReactModal = createReactComponent(ModalApi, ({props, buildChild}) => {
+ const [isOpen, setIsOpen] = useState(false);
+
+ return (
+ <>
+
+ )}
+ >
+ );
+});
diff --git a/renderers/react/src/v0_9/catalog/basic/components/ReactRow.tsx b/renderers/react/src/v0_9/catalog/basic/components/ReactRow.tsx
new file mode 100644
index 000000000..900c6fb2c
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/basic/components/ReactRow.tsx
@@ -0,0 +1,38 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {createReactComponent} from '../../../adapter';
+import {RowApi} from '@a2ui/web_core/v0_9/basic_catalog';
+import {ReactChildList} from './ReactChildList';
+import {mapJustify, mapAlign} from '../utils';
+
+export const ReactRow = createReactComponent(RowApi, ({props, buildChild, context}) => {
+ return (
+
+
+
+ );
+});
diff --git a/renderers/react/src/v0_9/catalog/basic/components/ReactSlider.tsx b/renderers/react/src/v0_9/catalog/basic/components/ReactSlider.tsx
new file mode 100644
index 000000000..9b4f02c00
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/basic/components/ReactSlider.tsx
@@ -0,0 +1,58 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {createReactComponent} from '../../../adapter';
+import {SliderApi} from '@a2ui/web_core/v0_9/basic_catalog';
+import {LEAF_MARGIN} from '../utils';
+
+export const ReactSlider = createReactComponent(SliderApi, ({props}) => {
+ const onChange = (e: React.ChangeEvent) => {
+ props.setValue(Number(e.target.value));
+ };
+
+ const uniqueId = React.useId();
+
+ return (
+
+
+ {props.label && (
+
+ )}
+ {props.value}
+
+
+
+ );
+});
diff --git a/renderers/react/src/v0_9/catalog/basic/components/ReactTabs.tsx b/renderers/react/src/v0_9/catalog/basic/components/ReactTabs.tsx
new file mode 100644
index 000000000..10ed1a4a0
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/basic/components/ReactTabs.tsx
@@ -0,0 +1,53 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {useState} from 'react';
+import {createReactComponent} from '../../../adapter';
+import {TabsApi} from '@a2ui/web_core/v0_9/basic_catalog';
+import {LEAF_MARGIN} from '../utils';
+
+export const ReactTabs = createReactComponent(TabsApi, ({props, buildChild}) => {
+ const [selectedIndex, setSelectedIndex] = useState(0);
+
+ const tabs = props.tabs || [];
+ const activeTab = tabs[selectedIndex];
+
+ return (
+
+
+ {tabs.map((tab: any, i: number) => (
+
+ ))}
+
+
{activeTab ? buildChild(activeTab.child) : null}
+
+ );
+});
diff --git a/renderers/react/src/v0_9/catalog/basic/components/ReactText.tsx b/renderers/react/src/v0_9/catalog/basic/components/ReactText.tsx
new file mode 100644
index 000000000..a4c5d631a
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/basic/components/ReactText.tsx
@@ -0,0 +1,46 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {createReactComponent} from '../../../adapter';
+import {TextApi} from '@a2ui/web_core/v0_9/basic_catalog';
+import {getBaseLeafStyle} from '../utils';
+
+export const ReactText = createReactComponent(TextApi, ({props}) => {
+ const text = props.text ?? '';
+ const style: React.CSSProperties = {
+ ...getBaseLeafStyle(),
+ display: 'inline-block',
+ };
+
+ switch (props.variant) {
+ case 'h1':
+ return
{text}
;
+ case 'h2':
+ return
{text}
;
+ case 'h3':
+ return
{text}
;
+ case 'h4':
+ return
{text}
;
+ case 'h5':
+ return
{text}
;
+ case 'caption':
+ return {text};
+ case 'body':
+ default:
+ return {text};
+ }
+});
diff --git a/renderers/react/src/v0_9/catalog/basic/components/ReactTextField.tsx b/renderers/react/src/v0_9/catalog/basic/components/ReactTextField.tsx
new file mode 100644
index 000000000..1c2b17ce9
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/basic/components/ReactTextField.tsx
@@ -0,0 +1,82 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {createReactComponent} from '../../../adapter';
+import {TextFieldApi} from '@a2ui/web_core/v0_9/basic_catalog';
+import {LEAF_MARGIN, STANDARD_BORDER, STANDARD_RADIUS} from '../utils';
+
+export const ReactTextField = createReactComponent(TextFieldApi, ({props}) => {
+ const onChange = (e: React.ChangeEvent) => {
+ props.setValue(e.target.value);
+ };
+
+ const isLong = props.variant === 'longText';
+ const type =
+ props.variant === 'number' ? 'number' : props.variant === 'obscured' ? 'password' : 'text';
+
+ const style: React.CSSProperties = {
+ padding: '8px',
+ width: '100%',
+ border: STANDARD_BORDER,
+ borderRadius: STANDARD_RADIUS,
+ boxSizing: 'border-box',
+ };
+
+ // Note: To have a unique id without passing context we can use a random or provided id,
+ // but the simplest is just relying on React's useId if we really need it.
+ // For now, we'll omit the `id` from the label connection since we removed context.
+ const uniqueId = React.useId();
+
+ const hasError = props.validationErrors && props.validationErrors.length > 0;
+
+ return (
+
+ );
+});
diff --git a/renderers/react/src/v0_9/catalog/basic/components/ReactVideo.tsx b/renderers/react/src/v0_9/catalog/basic/components/ReactVideo.tsx
new file mode 100644
index 000000000..4e42dd0d6
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/basic/components/ReactVideo.tsx
@@ -0,0 +1,30 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {createReactComponent} from '../../../adapter';
+import {VideoApi} from '@a2ui/web_core/v0_9/basic_catalog';
+import {getBaseLeafStyle} from '../utils';
+
+export const ReactVideo = createReactComponent(VideoApi, ({props}) => {
+ const style: React.CSSProperties = {
+ ...getBaseLeafStyle(),
+ width: '100%',
+ aspectRatio: '16/9',
+ };
+
+ return ;
+});
diff --git a/renderers/react/src/v0_9/catalog/basic/index.ts b/renderers/react/src/v0_9/catalog/basic/index.ts
new file mode 100644
index 000000000..cf63a72ee
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/basic/index.ts
@@ -0,0 +1,86 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {Catalog} from '@a2ui/web_core/v0_9';
+import {BASIC_FUNCTIONS} from '@a2ui/web_core/v0_9/basic_catalog';
+import type {ReactComponentImplementation} from '../../adapter';
+
+import {ReactText} from './components/ReactText';
+import {ReactImage} from './components/ReactImage';
+import {ReactIcon} from './components/ReactIcon';
+import {ReactVideo} from './components/ReactVideo';
+import {ReactAudioPlayer} from './components/ReactAudioPlayer';
+import {ReactRow} from './components/ReactRow';
+import {ReactColumn} from './components/ReactColumn';
+import {ReactList} from './components/ReactList';
+import {ReactCard} from './components/ReactCard';
+import {ReactTabs} from './components/ReactTabs';
+import {ReactDivider} from './components/ReactDivider';
+import {ReactModal} from './components/ReactModal';
+import {ReactButton} from './components/ReactButton';
+import {ReactTextField} from './components/ReactTextField';
+import {ReactCheckBox} from './components/ReactCheckBox';
+import {ReactChoicePicker} from './components/ReactChoicePicker';
+import {ReactSlider} from './components/ReactSlider';
+import {ReactDateTimeInput} from './components/ReactDateTimeInput';
+
+const basicComponents: ReactComponentImplementation[] = [
+ ReactText,
+ ReactImage,
+ ReactIcon,
+ ReactVideo,
+ ReactAudioPlayer,
+ ReactRow,
+ ReactColumn,
+ ReactList,
+ ReactCard,
+ ReactTabs,
+ ReactDivider,
+ ReactModal,
+ ReactButton,
+ ReactTextField,
+ ReactCheckBox,
+ ReactChoicePicker,
+ ReactSlider,
+ ReactDateTimeInput,
+];
+
+export const basicCatalog = new Catalog(
+ 'https://a2ui.org/specification/v0_9/basic_catalog.json',
+ basicComponents,
+ BASIC_FUNCTIONS
+);
+
+export {
+ ReactText,
+ ReactImage,
+ ReactIcon,
+ ReactVideo,
+ ReactAudioPlayer,
+ ReactRow,
+ ReactColumn,
+ ReactList,
+ ReactCard,
+ ReactTabs,
+ ReactDivider,
+ ReactModal,
+ ReactButton,
+ ReactTextField,
+ ReactCheckBox,
+ ReactChoicePicker,
+ ReactSlider,
+ ReactDateTimeInput,
+};
diff --git a/renderers/react/src/v0_9/catalog/basic/utils.ts b/renderers/react/src/v0_9/catalog/basic/utils.ts
new file mode 100644
index 000000000..375bac00d
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/basic/utils.ts
@@ -0,0 +1,78 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import type React from 'react';
+
+/** Standard leaf margin from the implementation guide. */
+export const LEAF_MARGIN = '8px';
+
+/** Standard internal padding for visually bounded containers. */
+export const CONTAINER_PADDING = '16px';
+
+/** Standard border for cards and inputs. */
+export const STANDARD_BORDER = '1px solid #ccc';
+
+/** Standard border radius. */
+export const STANDARD_RADIUS = '8px';
+
+export const mapJustify = (j?: string) => {
+ switch (j) {
+ case 'center':
+ return 'center';
+ case 'end':
+ return 'flex-end';
+ case 'spaceAround':
+ return 'space-around';
+ case 'spaceBetween':
+ return 'space-between';
+ case 'spaceEvenly':
+ return 'space-evenly';
+ case 'start':
+ return 'flex-start';
+ case 'stretch':
+ return 'stretch';
+ default:
+ return 'flex-start';
+ }
+};
+
+export const mapAlign = (a?: string) => {
+ switch (a) {
+ case 'start':
+ return 'flex-start';
+ case 'center':
+ return 'center';
+ case 'end':
+ return 'flex-end';
+ case 'stretch':
+ return 'stretch';
+ default:
+ return 'stretch';
+ }
+};
+
+export const getBaseLeafStyle = (): React.CSSProperties => ({
+ margin: LEAF_MARGIN,
+ boxSizing: 'border-box',
+});
+
+export const getBaseContainerStyle = (): React.CSSProperties => ({
+ margin: LEAF_MARGIN,
+ padding: CONTAINER_PADDING,
+ border: STANDARD_BORDER,
+ borderRadius: STANDARD_RADIUS,
+ boxSizing: 'border-box',
+});
diff --git a/renderers/react/src/v0_9/catalog/minimal/components/ReactButton.tsx b/renderers/react/src/v0_9/catalog/minimal/components/ReactButton.tsx
new file mode 100644
index 000000000..e60d849d9
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/minimal/components/ReactButton.tsx
@@ -0,0 +1,48 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {createReactComponent} from '../../../adapter';
+import {z} from 'zod';
+import {CommonSchemas} from '@a2ui/web_core/v0_9';
+
+export const ButtonSchema = z.object({
+ child: CommonSchemas.ComponentId,
+ action: CommonSchemas.Action,
+ variant: z.enum(['primary', 'borderless']).optional(),
+});
+
+export const ButtonApiDef = {
+ name: 'Button',
+ schema: ButtonSchema,
+};
+
+export const ReactButton = createReactComponent(ButtonApiDef, ({props, buildChild}) => {
+ const style: React.CSSProperties = {
+ padding: '8px 16px',
+ cursor: 'pointer',
+ border: props.variant === 'borderless' ? 'none' : '1px solid #ccc',
+ backgroundColor: props.variant === 'primary' ? '#007bff' : 'transparent',
+ color: props.variant === 'primary' ? '#fff' : 'inherit',
+ borderRadius: '4px',
+ };
+
+ return (
+
+ );
+});
diff --git a/renderers/react/src/v0_9/catalog/minimal/components/ReactChildList.tsx b/renderers/react/src/v0_9/catalog/minimal/components/ReactChildList.tsx
new file mode 100644
index 000000000..a331304c4
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/minimal/components/ReactChildList.tsx
@@ -0,0 +1,48 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {type ComponentContext} from '@a2ui/web_core/v0_9';
+
+export const ReactChildList: React.FC<{
+ childList: any;
+ context: ComponentContext;
+ buildChild: (id: string, basePath?: string) => React.ReactNode;
+}> = ({childList, buildChild}) => {
+ if (Array.isArray(childList)) {
+ return (
+ <>
+ {childList.map((item: any, i: number) => {
+ // The new binder outputs objects like { id: string, basePath: string } for arrays of structural nodes
+ if (item && typeof item === 'object' && item.id) {
+ return (
+
+ {buildChild(item.id, item.basePath)}
+
+ );
+ }
+ // Fallback for static string lists
+ if (typeof item === 'string') {
+ return {buildChild(item)};
+ }
+ return null;
+ })}
+ >
+ );
+ }
+
+ return null;
+};
diff --git a/renderers/react/src/v0_9/catalog/minimal/components/ReactColumn.tsx b/renderers/react/src/v0_9/catalog/minimal/components/ReactColumn.tsx
new file mode 100644
index 000000000..e4ffc9aab
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/minimal/components/ReactColumn.tsx
@@ -0,0 +1,85 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {createReactComponent} from '../../../adapter';
+import {z} from 'zod';
+import {CommonSchemas} from '@a2ui/web_core/v0_9';
+import {ReactChildList} from './ReactChildList';
+
+export const ColumnSchema = z.object({
+ children: CommonSchemas.ChildList,
+ justify: z
+ .enum(['start', 'center', 'end', 'spaceBetween', 'spaceAround', 'spaceEvenly', 'stretch'])
+ .optional(),
+ align: z.enum(['center', 'end', 'start', 'stretch']).optional(),
+});
+
+const mapJustify = (j?: string) => {
+ switch (j) {
+ case 'center':
+ return 'center';
+ case 'end':
+ return 'flex-end';
+ case 'spaceAround':
+ return 'space-around';
+ case 'spaceBetween':
+ return 'space-between';
+ case 'spaceEvenly':
+ return 'space-evenly';
+ case 'start':
+ return 'flex-start';
+ case 'stretch':
+ return 'stretch';
+ default:
+ return 'flex-start';
+ }
+};
+
+const mapAlign = (a?: string) => {
+ switch (a) {
+ case 'start':
+ return 'flex-start';
+ case 'center':
+ return 'center';
+ case 'end':
+ return 'flex-end';
+ case 'stretch':
+ return 'stretch';
+ default:
+ return 'stretch';
+ }
+};
+
+export const ColumnApiDef = {
+ name: 'Column',
+ schema: ColumnSchema,
+};
+
+export const ReactColumn = createReactComponent(ColumnApiDef, ({props, buildChild, context}) => {
+ return (
+
+
+
+ );
+});
diff --git a/renderers/react/src/v0_9/catalog/minimal/components/ReactRow.tsx b/renderers/react/src/v0_9/catalog/minimal/components/ReactRow.tsx
new file mode 100644
index 000000000..831bb8bee
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/minimal/components/ReactRow.tsx
@@ -0,0 +1,84 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {createReactComponent} from '../../../adapter';
+import {z} from 'zod';
+import {CommonSchemas} from '@a2ui/web_core/v0_9';
+import {ReactChildList} from './ReactChildList';
+
+export const RowSchema = z.object({
+ children: CommonSchemas.ChildList,
+ justify: z
+ .enum(['center', 'end', 'spaceAround', 'spaceBetween', 'spaceEvenly', 'start', 'stretch'])
+ .optional(),
+ align: z.enum(['start', 'center', 'end', 'stretch']).optional(),
+});
+
+const mapJustify = (j?: string) => {
+ switch (j) {
+ case 'center':
+ return 'center';
+ case 'end':
+ return 'flex-end';
+ case 'spaceAround':
+ return 'space-around';
+ case 'spaceBetween':
+ return 'space-between';
+ case 'spaceEvenly':
+ return 'space-evenly';
+ case 'start':
+ return 'flex-start';
+ case 'stretch':
+ return 'stretch';
+ default:
+ return 'flex-start';
+ }
+};
+
+const mapAlign = (a?: string) => {
+ switch (a) {
+ case 'start':
+ return 'flex-start';
+ case 'center':
+ return 'center';
+ case 'end':
+ return 'flex-end';
+ case 'stretch':
+ return 'stretch';
+ default:
+ return 'stretch';
+ }
+};
+
+export const RowApiDef = {
+ name: 'Row',
+ schema: RowSchema,
+};
+
+export const ReactRow = createReactComponent(RowApiDef, ({props, buildChild, context}) => {
+ return (
+
+
+
+ );
+});
diff --git a/renderers/react/src/v0_9/catalog/minimal/components/ReactText.tsx b/renderers/react/src/v0_9/catalog/minimal/components/ReactText.tsx
new file mode 100644
index 000000000..e9ca57bac
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/minimal/components/ReactText.tsx
@@ -0,0 +1,50 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {createReactComponent} from '../../../adapter';
+import {z} from 'zod';
+import {CommonSchemas} from '@a2ui/web_core/v0_9';
+
+export const TextSchema = z.object({
+ text: CommonSchemas.DynamicString,
+ variant: z.enum(['h1', 'h2', 'h3', 'h4', 'h5', 'caption', 'body']).optional(),
+});
+
+export const TextApiDef = {
+ name: 'Text',
+ schema: TextSchema,
+};
+
+export const ReactText = createReactComponent(TextApiDef, ({props}) => {
+ const text = props.text ?? '';
+ switch (props.variant) {
+ case 'h1':
+ return
{text}
;
+ case 'h2':
+ return
{text}
;
+ case 'h3':
+ return
{text}
;
+ case 'h4':
+ return
{text}
;
+ case 'h5':
+ return
{text}
;
+ case 'caption':
+ return {text};
+ case 'body':
+ default:
+ return {text};
+ }
+});
diff --git a/renderers/react/src/v0_9/catalog/minimal/components/ReactTextField.tsx b/renderers/react/src/v0_9/catalog/minimal/components/ReactTextField.tsx
new file mode 100644
index 000000000..a53828fd1
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/minimal/components/ReactTextField.tsx
@@ -0,0 +1,72 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {createReactComponent} from '../../../adapter';
+import {z} from 'zod';
+import {CommonSchemas} from '@a2ui/web_core/v0_9';
+
+export const TextFieldSchema = z.object({
+ label: CommonSchemas.DynamicString,
+ value: CommonSchemas.DynamicString,
+ variant: z.enum(['longText', 'number', 'shortText', 'obscured']).optional(),
+ validationRegexp: z.string().optional(),
+});
+
+export const TextFieldApiDef = {
+ name: 'TextField',
+ schema: TextFieldSchema,
+};
+
+export const ReactTextField = createReactComponent(TextFieldApiDef, ({props, context}) => {
+ const onChange = (e: React.ChangeEvent) => {
+ // In a reactive framework, we still update the DataModel directly for two-way binding.
+ // We look up the path from the un-resolved properties of the component model.
+ const valueProp = context.componentModel.properties.value;
+ if (valueProp && typeof valueProp === 'object' && valueProp.path) {
+ context.dataContext.set(valueProp.path, e.target.value);
+ }
+ };
+
+ const isLong = props.variant === 'longText';
+ const type =
+ props.variant === 'number' ? 'number' : props.variant === 'obscured' ? 'password' : 'text';
+
+ const style: React.CSSProperties = {
+ padding: '8px',
+ width: '100%',
+ border: '1px solid #ccc',
+ borderRadius: '4px',
+ boxSizing: 'border-box',
+ };
+
+ const id = `textfield-${context.componentModel.id}`;
+
+ return (
+
+ );
+});
diff --git a/renderers/react/src/v0_9/catalog/minimal/index.ts b/renderers/react/src/v0_9/catalog/minimal/index.ts
new file mode 100644
index 000000000..ba98e9dde
--- /dev/null
+++ b/renderers/react/src/v0_9/catalog/minimal/index.ts
@@ -0,0 +1,63 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {Catalog, createFunctionImplementation} from '@a2ui/web_core/v0_9';
+import {ReactText} from './components/ReactText';
+import {ReactButton} from './components/ReactButton';
+import {ReactRow} from './components/ReactRow';
+import {ReactColumn} from './components/ReactColumn';
+import {ReactTextField} from './components/ReactTextField';
+import type {ReactComponentImplementation} from '../../adapter';
+import {z} from 'zod';
+
+const minimalComponents: ReactComponentImplementation[] = [
+ ReactText,
+ ReactButton,
+ ReactRow,
+ ReactColumn,
+ ReactTextField,
+];
+
+export const minimalCatalog = new Catalog(
+ 'https://a2ui.org/specification/v0_9/catalogs/minimal/minimal_catalog.json',
+ minimalComponents,
+ [
+ createFunctionImplementation(
+ {
+ name: 'capitalize',
+ returnType: 'string',
+ schema: z.object({
+ value: z.any(),
+ }),
+ },
+ (args) => {
+ const val = args.value;
+ if (typeof val === 'string') {
+ return val.toUpperCase();
+ }
+ return val as string;
+ }
+ ),
+ ]
+);
+
+export {
+ ReactText,
+ ReactButton,
+ ReactRow,
+ ReactColumn,
+ ReactTextField,
+};
diff --git a/renderers/react/src/v0_9/index.ts b/renderers/react/src/v0_9/index.ts
new file mode 100644
index 000000000..c1e4ca167
--- /dev/null
+++ b/renderers/react/src/v0_9/index.ts
@@ -0,0 +1,27 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './A2uiSurface';
+export * from './adapter';
+export * from './catalog/minimal';
+export * from './catalog/basic';
+// Minimal catalog components are exported from here for backwards compatibility or specific use.
+export {ReactButton as MinimalButton} from './catalog/minimal/components/ReactButton';
+export {ReactColumn as MinimalColumn} from './catalog/minimal/components/ReactColumn';
+export {ReactRow as MinimalRow} from './catalog/minimal/components/ReactRow';
+export {ReactText as MinimalText} from './catalog/minimal/components/ReactText';
+export {ReactTextField as MinimalTextField} from './catalog/minimal/components/ReactTextField';
+export * from './catalog/minimal/components/ReactChildList';
diff --git a/renderers/react/src/v0_9/json.d.ts b/renderers/react/src/v0_9/json.d.ts
new file mode 100644
index 000000000..efa21e9e2
--- /dev/null
+++ b/renderers/react/src/v0_9/json.d.ts
@@ -0,0 +1,20 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+declare module "*.json" {
+ const value: any;
+ export default value;
+}
diff --git a/renderers/react/tests/utils.tsx b/renderers/react/tests/utils.tsx
new file mode 100644
index 000000000..292e31c37
--- /dev/null
+++ b/renderers/react/tests/utils.tsx
@@ -0,0 +1,110 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { render } from '@testing-library/react';
+import { vi } from 'vitest';
+import { SurfaceModel, ComponentModel, Catalog, ComponentContext } from '@a2ui/web_core/v0_9';
+import { BASIC_FUNCTIONS } from '@a2ui/web_core/v0_9/basic_catalog';
+import type { ReactComponentImplementation } from '../src/v0_9/adapter';
+
+export interface RenderA2uiOptions {
+ initialData?: Record;
+ /** Additional component implementations needed by the children */
+ additionalImpls?: ReactComponentImplementation[];
+ /** Pre-instantiated ComponentModels for child components */
+ additionalComponents?: ComponentModel[];
+ /** Functions to include in the catalog */
+ functions?: any[];
+}
+
+/**
+ * A robust test utility for rendering A2UI React components in isolation
+ * while maintaining a real A2UI state lifecycle.
+ */
+export function renderA2uiComponent(
+ impl: ReactComponentImplementation,
+ componentId: string,
+ initialProperties: Record,
+ options: RenderA2uiOptions = {}
+) {
+ const {
+ initialData = {},
+ additionalImpls = [],
+ additionalComponents = [],
+ functions = BASIC_FUNCTIONS
+ } = options;
+
+ // Combine all implementations into the catalog
+ const allImpls = [impl, ...additionalImpls];
+ const catalog = new Catalog('test-catalog', allImpls, functions);
+ const surface = new SurfaceModel('test-surface', catalog);
+
+ // Setup data model
+ surface.dataModel.set('/', initialData);
+
+ // Add the component under test
+ const mainModel = new ComponentModel(componentId, impl.name, initialProperties);
+ surface.componentsModel.addComponent(mainModel);
+
+ // Add any explicitly defined child component models
+ for (const childModel of additionalComponents) {
+ surface.componentsModel.addComponent(childModel);
+ }
+
+ const mainContext = new ComponentContext(surface, componentId, '/');
+
+ // Smart buildChild mock:
+ // 1. If the component exists in the model and catalog, render it for real.
+ // 2. Otherwise, render a placeholder div that tests can query.
+ const buildChild = vi.fn((id: string, basePath?: string) => {
+ const compModel = surface.componentsModel.get(id);
+
+ if (!compModel) {
+ return ;
+ }
+
+ const compImpl = surface.catalog.components.get(compModel.type);
+ if (!compImpl) {
+ return ;
+ }
+
+ const ctx = new ComponentContext(surface, id, basePath || '/');
+ const ChildComponent = (compImpl as ReactComponentImplementation).render;
+
+ return ;
+ });
+
+ const ComponentToRender = impl.render;
+
+ const view = render(
+
+ );
+
+ return {
+ view,
+ surface,
+ buildChild,
+ mainModel,
+ context: mainContext,
+ // Helper to trigger data model updates and wait for re-render
+ updateData: async (path: string, value: any) => {
+ surface.dataModel.set(path, value);
+ // Wait for React to process the useSyncExternalStore update
+ await new Promise(resolve => setTimeout(resolve, 0));
+ }
+ };
+}
diff --git a/renderers/react/tests/v0_9/adapter.test.tsx b/renderers/react/tests/v0_9/adapter.test.tsx
new file mode 100644
index 000000000..28587fc7d
--- /dev/null
+++ b/renderers/react/tests/v0_9/adapter.test.tsx
@@ -0,0 +1,128 @@
+/**
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { describe, it, expect, vi } from 'vitest';
+import { render, screen, act } from '@testing-library/react';
+import { createReactComponent } from '../../src/v0_9/adapter';
+import { ComponentContext, ComponentModel, SurfaceModel, Catalog, CommonSchemas } from '@a2ui/web_core/v0_9';
+import { z } from 'zod';
+
+const mockCatalog = new Catalog('test', [], []);
+
+describe('adapter', () => {
+ it('should render component with resolved props', () => {
+ const surface = new SurfaceModel('test-surface', mockCatalog);
+ const compModel = new ComponentModel('c1', 'TestComp', { text: 'Hello World', child: 'child1' });
+ surface.componentsModel.addComponent(compModel);
+
+ const context = new ComponentContext(surface, 'c1', '/');
+
+ const TestApiDef = {
+ name: 'TestComp',
+ schema: z.object({
+ text: CommonSchemas.DynamicString,
+ child: CommonSchemas.ComponentId
+ })
+ };
+
+ const TestComponent = createReactComponent(
+ TestApiDef,
+ ({ props, buildChild }) => {
+ return