{"slug": "why-a2ui-s-json-driven-layout-schema-breaks-down-in-production-modal-stacks", "title": "Why a2ui's JSON-Driven Layout Schema Breaks Down in Production Modal Stacks", "summary": "A developer evaluating the a2ui-project/a2ui generative UI framework found its JSON layout schema breaks down for nested modal stacks, as TailwindCSS utility classes fail to cascade into React portals and focus management collapses when modals are chained. The developer patched the React adapter to inject data-focus-scope attributes, but the fix requires maintaining a fork that must be manually reconciled with upstream changes between versions like v0.8.3 and v0.9.1.", "body_md": "When evaluating **a2ui-project/a2ui** for a conversational dashboard rebuild, the pitch was clean: backend agents stream JSON layout descriptors, and the React adapter hydrates them into live components. No manual `createElement` chains, no brittle template strings. The repository ships adapters for React, Angular, Lit, Flutter, SwiftUI, and Jetpack Compose—universal generative UI.\n\nThe reality hit during our first modal implementation. a2ui's layout schema handles flat component trees elegantly, but once you introduce **nested modals with focus trapping and portal rendering**, the abstraction starts bleeding through the seams.\n\nOur agent streams this layout descriptor:\n\n```\n{\n  \"type\": \"modal\",\n  \"id\": \"confirm-delete\",\n  \"backdrop\": true,\n  \"children\": [\n    {\n      \"type\": \"box\",\n      \"className\": \"bg-white rounded-lg shadow-xl p-6 max-w-md\",\n      \"children\": [\n        { \"type\": \"text\", \"content\": \"Delete 47 items?\" },\n        {\n          \"type\": \"button\",\n          \"variant\": \"danger\",\n          \"label\": \"Confirm\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nThe React adapter wraps this in a Radix `Dialog.Root`. Problem: **TailwindCSS utility classes don't cascade into React portals** unless you explicitly configure `tailwind.config.js` to scan portal roots. The modal renders, but `bg-white` and `shadow-xl` vanish—the dialog appears as unstyled gray blocks.\n\nForce Tailwind to scan portal containers:\n\n```\n// tailwind.config.js\nmodule.exports = {\n  content: [\n    './src/**/*.{js,jsx,ts,tsx}',\n    './node_modules/@a2ui/react-adapter/**/*.js',\n    // Critical: scan React portal mount points\n    './public/index.html'\n  ],\n  theme: {\n    extend: {}\n  }\n};\n```\n\nBut now you've coupled your build pipeline to a2ui's internal DOM structure. When the adapter changes its portal strategy (observed between v0.8.3 and v0.9.1), styles break silently in production.\n\nAgent streams a second modal (\"Are you sure?\") while the first is still open. a2ui's React adapter chains `Dialog.Root` components, but **focus management collapses**:\n\n`Tab` inside the second modal.`Escape` key dismisses both modals simultaneously.\nRoot cause: a2ui's layout schema has no **explicit focus scope boundaries**. The JSON descriptor treats modals as generic containers, delegating focus control entirely to the component library (Radix, in this case). When you nest portals, Radix's `FocusScope` gets confused by React's reconciliation order.\n\nWe patched the adapter to inject `data-focus-scope` attributes:\n\n``` js\n// Custom React adapter override\nimport { Dialog } from '@radix-ui/react-dialog';\nimport { useEffect, useRef } from 'react';\n\nfunction A2UIModal({ descriptor, children }) {\n  const scopeRef = useRef(null);\n\n  useEffect(() => {\n    if (scopeRef.current) {\n      scopeRef.current.focus();\n    }\n  }, []);\n\n  return (\n    <Dialog.Root open={true}>\n      <Dialog.Portal>\n        <Dialog.Overlay className=\"fixed inset-0 bg-black/50\" />\n        <Dialog.Content\n          ref={scopeRef}\n          data-focus-scope={descriptor.id}\n          className=\"fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2\"\n        >\n          {children}\n        </Dialog.Content>\n      </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n```\n\nThis works, but now we're **maintaining a fork of the adapter**. Every upstream change requires manual merge conflict resolution.\n\nThe core tension: a2ui's JSON schema optimizes for **agent-side simplicity** (LLMs generate clean, validated layouts), but sacrifices **client-side flexibility**. When you hit edge cases—stacked z-index conflicts, viewport-aware positioning, touch gesture cancellation—you're forced to either:\n\nExample: We tried adding a `trapFocus: \"strict\"` flag to the modal descriptor. The React adapter honored it, but the Flutter adapter ignored it (no equivalent in Flutter's `showDialog`). Cross-platform consistency collapsed.\n\nIf your generative UI flows are **linear and non-overlapping** (form wizards, chatbot threads), a2ui's abstraction holds. But production apps demand:\n\nThese patterns expose a2ui's unresolved question: should the layout schema stay minimal (forcing platform-specific hacks), or should it expand into a full **declarative accessibility DSL** (risking bloat and divergence from native primitives)?\n\nNo clean answer yet. The repository's issue tracker shows 14 open threads on focus management alone.", "url": "https://wpnews.pro/news/why-a2ui-s-json-driven-layout-schema-breaks-down-in-production-modal-stacks", "canonical_source": "https://dev.to/chloe_chen/why-a2uis-json-driven-layout-schema-breaks-down-in-production-modal-stacks-2fee", "published_at": "2026-11-14 00:09:14+00:00", "updated_at": "2026-11-14 00:21:58.683797+00:00", "lang": "en", "topics": ["generative-ai", "ai-agents", "developer-tools", "ai-tools"], "entities": ["a2ui-project/a2ui", "React", "Radix", "TailwindCSS", "Angular", "Lit", "Flutter", "SwiftUI"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/why-a2ui-s-json-driven-layout-schema-breaks-down-in-production-modal-stacks", "markdown": "https://wpnews.pro/news/why-a2ui-s-json-driven-layout-schema-breaks-down-in-production-modal-stacks.md", "text": "https://wpnews.pro/news/why-a2ui-s-json-driven-layout-schema-breaks-down-in-production-modal-stacks.txt", "jsonld": "https://wpnews.pro/news/why-a2ui-s-json-driven-layout-schema-breaks-down-in-production-modal-stacks.jsonld"}}