Phase 9b · Tier A
RN-universal Dialog pilot
ScaffoldedRN Modal-backed dialog with backdrop, fade-in, and a compound API that mirrors shadcn. Both controlled (open + onOpenChange) and uncontrolled (defaultOpen) modes are supported, so AI agents can drive the primitive declaratively.
Side-by-side: confirm action
shadcn (Radix)
RN
Controlled (agent-driven)
Drive the dialog from external state — exactly how an A2UI agent would: it sends a JSON descriptor with open: true and a list of actions, and the ThemeProvider renders the dialog. No imperative ref needed.
Semantics
| Aspect | shadcn | RN | Notes |
|---|---|---|---|
| Root | Radix Root | <RnDialog> context provider | Tracks open state and trigger ref |
| Trigger | Radix Trigger (asChild) | <RnDialogTrigger> Pressable | Opens on press; restores focus on close |
| Content | Portal + role="dialog" | RN Modal + role="dialog" | Modal renders fixed overlay on web; OS modal on native |
| Backdrop | fixed bg-black/50 + animate-out | Animated.View opacity 0→0.5 | Tap to dismiss; same on both |
| Title | Radix Title | <RnDialogTitle> Text | aria-labelledby wires automatically |
| Description | Radix Description | <RnDialogDescription> Text | aria-describedby wires automatically |
| Close (esc) | Radix focus trap + esc | Modal onRequestClose | Web: esc; Android: hardware back |
| Focus trap | Radix maintains | Not implemented in v1 | Modal native focus + ARIA is enough for WCAG AA |
Use with agents (A2UI)
A2UI-style descriptor → component:
{
"type": "dialog",
"open": true,
"title": "Submit application?",
"description": "Once submitted, you can no longer edit responses.",
"actions": [
{ "id": "cancel", "kind": "secondary", "label": "Cancel" },
{ "id": "submit", "kind": "primary", "label": "Submit" }
]
}The renderer maps open directly to the prop, and actions to RnDialogClose + RnButton children.