Porting from Vercel AI Elements
The upstream-to-port mapping with per-element deviations, the exact not-ported list, and why this port differs from the web original and the earlier React Native port.
This page maps the upstream Vercel AI Elements element set onto this registry. Read it before backporting a web pattern: the Deviations column states where the port changed the web original’s structure, and the not-ported list states what was deliberately left behind.
The upstream set is pinned. It is enumerated from the registry listing the vercel/ai-elements repository serves at /api/registry/registry.json (built from packages/elements/src), at commit 6a9d5b1822ffb10bba4bd97175f01edd7d8651cd, dated 2026-08-21: 49 elements, of which 40 ship here and 9 do not. The live listing at elements.ai-sdk.dev can lag that commit. The three sets also live as structured frontmatter arrays (mapping, notPorted, upstream) so tooling can diff them against the tracked registry — keep the arrays and the tables on this page in step.
What the library ships and does not ship in total is the boundary page’s job; this page never repeats those tables. Read What the library owns for that split.
Mapping
Forty of the 49 upstream elements ship as registry items. Each row below links to the item’s reference page, and the Deviations column states what differs from the web original — a cell starting with “None” means structure and behavior match, and every cell names one of three kinds: None, Adapted, or Substituted. The following table maps every ported element:
| Upstream element | Registry item | Deviations |
|---|---|---|
agent |
agent | Adapted — an identity and run card: persona, run-state badge, and a Start/Pause/Stop bar the web config card never had. The web card’s model badge, instructions box, tools accordion, and output-schema block are absent. |
artifact |
artifact | Adapted — the container and header chrome match the web part set; the web’s side-panel presentation becomes a host-mounted sheet, and the action tooltip becomes an accessibility label. |
attachments |
attachments | Substituted — the chip row ports at parity, but file acquisition does not: the native image and document pickers are the caller’s wiring, with their permission contracts in the consumer’s app; none of it ships here. |
audio-player |
audio-player | Substituted — the HTML audio element is dropped: transport controls and the scrubbable progress bar are the item, and the caller supplies the player (expo-audio, a native player, or an <audio> element on web) through a state-in/callbacks-out seam; no audio dependency enters the install graph. |
chain-of-thought |
chain-of-thought | None — the ordered step list with per-step status and a rail; the disclosure reuses the reasoning lifecycle, as on web. |
checkpoint |
checkpoint | Adapted — restore confirms first because it truncates the conversation; the web hides that warning behind a hover tooltip, which does not exist on touch. |
code-block |
code-block | Substituted — the DOM-emitting highlighter becomes a tokenizer rendering Text spans, with horizontal scroll and a copy action; ships as a registry:ui base primitive rather than registry:component. |
commit |
commit | None — commit reference row with hash chip, message, author, and changed files behind a disclosure; one-tap hash copy. |
confirmation |
confirmation | Adapted — a card rather than the web Alert container, with full-width Approve and Deny actions sized to platform touch targets. |
context |
context | Adapted — the hover reveal becomes a press-opened popover; the token and cost data shown is unchanged. |
conversation |
conversation | Substituted — the overflow container and scroll hook become an inverted FlatList with maintainVisibleContentPosition plus keyboard and safe-area handling; same surface, different implementation. |
environment-variables |
environment-variables | None — masked key and value rows with a single-value reveal, at parity with the web part set. |
file-tree |
file-tree | Adapted — the nested rows port, with horizontal scroll keeping deep indentation legible at phone width; the chevron and the name get separate touch targets. |
image |
image | None — the AI SDK result object and prop shape are preserved; alt maps to accessibilityLabel. |
inline-citation |
inline-citation | Adapted — the hover card becomes a pressable inline chip, nested in Text exactly as the web nests spans, reporting onSelect. |
message |
message | None — role alignment, surface, and avatar match; markdown rendering is injected through renderMarkdown because the web renderer does not run on React Native. |
mic-selector |
mic-selector | Substituted — the media devices API has no native equivalent; ships as the audio-route picker (built-in, wired, Bluetooth) doing the same job. |
model-selector |
model-selector | Adapted — the dropdown becomes a bottom sheet with search, composed on the command primitive; the selection contract is unchanged. |
open-in-chat |
open-in-chat | Adapted — the same six targets and menu, with target="_blank" replaced by the platform link handler behind an http/https allowlist; unavailable targets are hidden. |
package-info |
package-info | None — card of package name, version, and a copyable install command, all RNR primitives. |
persona |
persona | Adapted — the web original is a Rive animated avatar; the animation is not ported, so no consumer inherits a native animation runtime. |
plan |
plan | None — card and collapsible shell with per-step status; the steps compose the task rows, and title and description shimmer while streaming. |
prompt-input |
prompt-input | Substituted — the composer surface is at parity, but attachments arrive through the native pickers, enter-to-submit becomes an explicit send button, and the composer owns keyboard avoidance and safe-area insets. |
question |
question | Adapted — the select-then-submit form is dropped: an option tap is the submission, and the choice persists in the transcript. |
queue |
queue | None — collapsible queued-actions list with the count on the trigger and a remove action per row. |
reasoning |
reasoning | None — auto-opens while streaming, auto-closes one second after the answer begins, and shows the frozen duration on the collapsed header. |
schema-display |
schema-display | Adapted — the upstream collapse structure ports; nesting depth and key widths collapse and scroll to stay legible at phone width. |
shimmer |
shimmer | Substituted — the web effect is a linear-gradient with background-clip on text, which React Native does not have, so the signal becomes the RNR Skeleton house pulse (opacity 1 to 0.5 over 1000ms, repeating) on text at text-muted-foreground — a new visual treatment rather than an equivalent one, and no masked view ships. |
snippet |
snippet | Adapted — clipboard access moves to the native clipboard module, and long commands scroll horizontally. |
sources |
sources | None — collapsible grouped sources with title and domain per row. |
speech-input |
speech-input | Substituted — the web’s two browser engines do not exist in React Native, so the caller-supplied recorder-and-transcribe contract is the only contract; a denied-permission state is distinct from no engine. |
stack-trace |
stack-trace | None — mono frame list with expandable frames; every file and line legible through horizontal scroll. |
suggestion |
suggestion | None — the horizontally scrolling chip row maps to a horizontal ScrollView. |
task |
task | Adapted — collapsed by default where the web opens by default, so the transcript stays scannable; the collapsed trigger row carries the status badge. |
terminal |
terminal | Adapted — read-only and horizontally scrollable, with ANSI colors mapped to theme tokens; an interactive pseudo-terminal is out of scope. |
test-results |
test-results | None — passed, failed, and skipped counts with a failing test expanding to its message. |
tool |
tool | None — status header plus input and output disclosure, covering every AI SDK tool-part state. |
transcription |
transcription | None — display-only live transcript; capture belongs to speech-input, so nothing was substituted. |
voice-selector |
voice-selector | Adapted — the picker becomes a bottom sheet, and the voice set comes from the caller’s native speech synthesis provider. |
web-preview |
web-preview | Substituted — the iframe becomes a native webview with the same URL bar and reload affordance, and the console pane is dropped; the one registry item that names a native module (react-native-webview) as an install-time dependency. |
Not ported
Nine upstream elements are deliberately not ported: the seven react-flow canvas-family parts and two web-only surfaces. None of them is a shipped registry item, and the mapping above plus this list covers all 49 upstream elements. The following list names each and states why:
canvas— a react-flow workspace: there is no react-flow on React Native, and a pan-and-pinch node graph on a phone is a separate product, not a port. The agent structure it visualizes renders as the plan and task lists.connection— an SVG connection line with pointer-drag semantics; it has no meaning outside a node editor.controls— React Flow’s zoom, fit, and lock cluster, acting on the canvas viewport store; the run controls that job suggests ship inside agent.edge— an SVG edge path with no standalone mobile use.jsx-preview— evaluates a JSX string at runtime; Hermes has no runtime JSX transform, and shipping an evaluator is an app-store and security problem. Render the described UI server-side and show it as an artifact or web preview.node— a draggable graph node with no standalone mobile use.panel— an absolutely positioned view inside React Flow’s transform layer, positioned in canvas coordinates; sheet is the secondary surface beside the transcript.sandbox— fronts a hosted code-execution service and renders through a browser surface; the display half is covered by terminal and web-preview.toolbar— @xyflow/react’s NodeToolbar, positioned from the owning node’s measured DOM rect; the message action row ships inside message, and a composer row is button-group.
Port-side additions
The registry carries 56 items. Forty are the mapped upstream elements above; the other 16 have no upstream counterpart — base primitives and shared logic modules this port created so the mapped items stay thin. The following table names each and its job:
| Registry item | Kind | Job |
|---|---|---|
breadcrumb |
ui | Path trail for file-tree and web-preview headers; a horizontal ScrollView, because a phone bar overflows before it wraps. |
button-group |
ui | Segmented row for message actions and composer rows; corner rounding flows through context because React Native has no :first-child. |
command |
ui | The searchable picker: cmdk is DOM and keyboard-first, so on a phone the palette is a bottom sheet with a filter and a virtualized list; backs the three selectors. |
empty |
ui | Icon, title, description, and an optional action — the fallback surface for conversation, file-tree, sources, attachments, and test-results. |
input-group |
ui | Leading and trailing addons around a TextInput, with its own focus ring; backs prompt-input and every filter field. |
item |
ui | Generic media, content, and actions row; sources, file-tree, attachments, queue, plan, and task render lists of these. |
kbd |
ui | Keyboard-shortcut hint; renders null where no hardware keyboard exists, which on a phone is always. |
sheet |
ui | Bottom, top, left, or right sheet on @rn-primitives/dialog, mounting its own named PortalHost so overlays opened inside it layer above it. |
slider |
ui | Styled shell over @rn-primitives/slider, which RNR does not wrap; the scrubber for audio-player and transcription. |
table |
ui | Styled shell over @rn-primitives/table, horizontally scrollable by default; backs schema-display, test-results, package-info, and environment-variables. |
markdown |
lib | Closes unterminated emphasis and code runs in a streaming chunk, so a message never flashes literal syntax mid-token. |
mono |
lib | The one monospace class contract every mono surface reads. |
reasoning-lifecycle |
lib | The reasoning disclosure’s timing state machine: auto-open on stream start, auto-close after it ends. |
stack-trace-parser |
lib | Pure V8, Node, and Hermes stack-trace frame parsing for stack-trace. |
status |
lib | The one status-tone map every status badge reads. |
url |
lib | Where a source URL becomes display parts and an open decision; hostname extraction never throws, and the http/https allowlist lives here. |
Prior art
Two efforts came before this one, and this port builds on both.
The upstream is Vercel AI Elements — React DOM components over shadcn/ui, documented and published at elements.ai-sdk.dev. Every element in the mapping above was read there first; this registry answers it for React Native and never copies its code.
The earlier React Native port is muratcakmak/expo-ai-elements. Its on-device discoveries are baked into this project’s planning: registry-URL install as the one distribution path that works for copy-paste components, and the streaming-markdown option rename that silently crashes markdown streaming when missed.
Why differs
This port differs from both in one structural decision: it stays on React Native Reusables. The web original is React DOM over shadcn/ui with a single styling setup; this registry is universal React Native over RNR primitives, fanned from one source into two engine trees — nativewind, for Tailwind v3 projects, and uniwind, for Tailwind v4 — so the same item installs into either project as source compiled for its engine. The earlier port began on RNR, but its SDK 57 rewrite drops RNR for a private primitives set, and its npm package ships no component code, so registry-URL install is the only path that works there. This registry never publishes an npm package — distribution is the copy-paste registry alone, which is why the two engine trees exist.