AI Elements
@elirobinson/ai-elements is the assistant tier: conversation logs, prompt inputs, tool and
reasoning panels, and the rest of the chrome an AI product needs and a general component
library has no business owning. It is the one package in this system whose source is not
ours — it is vendored from vercel/ai-elements at
a pinned release, and skinned with the system's tokens rather than rewritten.
It requires Tailwind 4
Read this before you install. Every vendored component is Tailwind utility markup, and without Tailwind 4 in your app it renders as unstyled HTML — no error, just a page that looks broken.
That requirement is the entire reason this is a separate package. @elirobinson/react has
no UI dependencies; Elements needs Tailwind 4, shadcn/ui, Radix and lucide-react. Folding
them into @elirobinson/react would force Tailwind on every consumer of Button and
Input for a feature most of them will never use. Installing this package is how you opt
in — see Installation, which is three stylesheet imports and one
directive.
It also means the two tiers are not alternatives. Use @elirobinson/react for the product
around the assistant and for anything a model returns as structured output; use Elements
for the conversation itself.
Vendored at a pinned release
The tree in this repo is upstream's bytes at ai-elements@1.9.0
(bc87126), plus a transform layer, and nothing else.
elements.lock.json records the pin, every vendored file opens with a header naming its
upstream path, and pnpm sync:elements re-derives the expected bytes from fresh upstream
bytes and compares — so every difference is attributable to upstream moving, or to somebody
editing a file they should not have.
What that buys you as a consumer: the version you install names the upstream release it came from, in data rather than in prose.
node -p "require('@elirobinson/ai-elements/upstream').upstream.ref"Most of that transform layer is mechanical import rewriting. The part worth knowing about is
the skin, which rewrites the places where upstream reaches for Tailwind's own palette —
text-zinc-500, bg-red-100 dark:bg-red-900/30 — because no alias can re-point a literal.
It also re-points shadcn's --accent, which upstream means as a subtle hover tint and this
system means as Miltinson Amber, a brand signal.
How it comes out on-brand
@elirobinson/tokens/tailwind.css maps Tailwind's colour, radius, shadow and font
namespaces onto the system's tokens with @theme inline, so bg-background,
text-muted-foreground, border-border and rounded-md compile to var(--token). Because
the aliases are inline, the utilities read the token at paint time: the theme, palette and
platform dials all move Elements at runtime, with no rebuild and no edit to a vendored file.
The same stylesheet declares @custom-variant dark pointing at [data-theme="dark"], so
dark: follows the system's theme dial rather than the reader's operating system. Left
alone those are two independent switches, and a theme toggle would move every token and none
of the dark: utilities.
Accessibility
Elements was not built to this system's contracts, and shadcn/Radix defaults land at 32 to
40px — which clears WCAG 2.2 AA and misses this system's 44x44 floor. pnpm a11y:elements
mounts every component in the components tier across two themes, three palettes and two
platform settings, and runs four browser-settled checks over each.
The per-control verdicts are published rather than written down here: each one names the control, the geometry a browser measured, which of the two touch-target floors it was held to, and why that one. Read them from the contracts:
node -p "Object.keys(require('@elirobinson/ai-patterns/contracts').vendoredElementTargets)"One control is a documented exception. React Flow paints its own licence notice into the corner of the canvas components; it appears in no vendored class string, so the transform layer cannot reach it, and its licence requires it to stay.
Licensing
The vendored tree is Apache-2.0. The package ships LICENSE (the licence text) and NOTICE
(attribution to Vercel and to shadcn, plus the complete list of modifications required by
§4(b)). Both are generated from the same lockfile as the code, so neither can drift from it.
Redistributing the package means shipping the NOTICE with it.
What is in it
There are 74 vendored files across three subpath namespaces, and no page on
this site keeps a list of them. The roster is upstream's, it changes on a re-sync, and a list
written into prose would be wrong from that commit onward — so
the component index is generated from
@elirobinson/ai-elements/manifest, which is rebuilt from the emitted declaration files on
every build and cannot name a component the package does not ship.
Next steps
- Component index — the generated list of every subpath and what it exports.
- Installation — the package, its peers, and the three stylesheet imports.
- Examples — a chat round trip, a tool panel that reads, and a structured surface.
- All components — the two tiers side by side, which is where this one is reached from.