+ {/* Tab buttons - uses Tabs.element for the container */}
+
+ {tabItems.map((tab, index) => {
+ const title = resolveString(tab.title);
+ const isSelected = index === selectedIndex;
+
+ // Lit merges all + selected classes when selected
+ const classes = isSelected
+ ? mergeClassMaps(
+ theme.components.Tabs.controls.all,
+ theme.components.Tabs.controls.selected
+ )
+ : theme.components.Tabs.controls.all;
+
+ return (
+
+ );
+ })}
+
+
+ {/* Tab content */}
+ {tabItems[selectedIndex] && (
+
+ )}
+
+ );
+});
+
+export default Tabs;
diff --git a/renderers/react/src/components/layout/index.ts b/renderers/react/src/components/layout/index.ts
new file mode 100644
index 000000000..28023efd6
--- /dev/null
+++ b/renderers/react/src/components/layout/index.ts
@@ -0,0 +1,6 @@
+export { Row } from './Row';
+export { Column } from './Column';
+export { List } from './List';
+export { Card } from './Card';
+export { Tabs } from './Tabs';
+export { Modal } from './Modal';
diff --git a/renderers/react/src/core/A2UIProvider.tsx b/renderers/react/src/core/A2UIProvider.tsx
new file mode 100644
index 000000000..f59a7936c
--- /dev/null
+++ b/renderers/react/src/core/A2UIProvider.tsx
@@ -0,0 +1,210 @@
+import {
+ createContext,
+ useContext,
+ useRef,
+ useState,
+ useMemo,
+ type ReactNode,
+} from 'react';
+import { Data, Types } from '@a2ui/lit/0.8';
+import type { A2UIContextValue, A2UIActions } from './store';
+import { ThemeProvider } from '../theme/ThemeContext';
+import type { OnActionCallback } from '../types';
+
+/**
+ * Context for stable actions (never changes reference, prevents re-renders).
+ * Components that only need to dispatch actions or read data won't re-render.
+ */
+const A2UIActionsContext = createContext