Vercel has released UI SDK as a public beta: an open-source TypeScript library that treats things like focus, selection, popups and keyboard navigation as small reusable behaviors, which you (or a coding agent) combine into components. The same component definition then renders in React, Vue, Svelte, Solid or plain Web Components. The first npm packages, 0.1.0-beta.1, were published on 12 November 2026, and 0.1.0-beta.2 followed a day later. This tutorial explains the idea, installs it, and walks through building and rendering a primitive.
The idea: behaviors instead of finished components
A typical component library hands you a finished Select. UI SDK breaks that Select into the behaviors it is made of: a popup, selection logic, typeahead, focus management, and the ARIA attributes each needs. The project README describes @ui-sdk/core as the home of keyboard navigation, focus, selection, dismissal, popups and drag and drop.
The pitch is aimed at AI coding agents: give an agent createPopup, createSelection and createTypeahead, and it can build an autocomplete, a combobox, or a component nobody has designed yet, while keeping accessibility behavior consistent. But it is also useful for humans who maintain design systems across more than one framework.
Install
Install the core package and the adapter for your framework:
npm install @ui-sdk/react @ui-sdk/core
# Other frameworks: swap the adapter
# npm install @ui-sdk/vue @ui-sdk/core
# npm install @ui-sdk/svelte @ui-sdk/core
# npm install @ui-sdk/solid @ui-sdk/core
# npm install @ui-sdk/web @ui-sdk/core
Vercel also publishes an agent skill so tools such as Claude Code, Codex or Cursor learn the library’s conventions:
npx skills add vercel-labs/ui --skill ui-sdk
According to launch coverage, the React adapter needs React 19 or newer, and the package is Apache-2.0 licensed.
Step 1: define a primitive
A primitive is a framework-neutral description of a component: its state, its API, and its slots (the parts that render). This search field example is from the official README. It has an input and a clear button that only appears when there is text, and it uses a focus-recovery behavior so focus returns to the input after clearing:
// search-field.ts
import { createFocusRecovery, definePrimitive } from "@ui-sdk/core"
const searchField = definePrimitive({
name: "search-field",
state: () => ({ value: "" }),
api: (context) => ({
set: (value: string) => context.store.set({ value }),
recovery: createFocusRecovery({ fallback: () => context.element("input") }),
}),
slots: (slot) => ({
input: slot({
attrs: (state) => ({ value: state.value }),
on: {
input: (event, { api }) =>
api.set((event.target as HTMLInputElement).value),
},
}),
clear: slot({
present: (state) => state.value !== "",
attrs: () => ({ type: "button" }),
press: ({ api }) => api.set(""),
bind: (el, part) => part.api.recovery.bind(el, part),
}),
}),
})
export { searchField }
A few things to notice:
stateis the primitive’s own store;apiexposes actions that change it.- Each slot declares its attributes from state, its event handlers, and when it is
present. pressis a behavior-level event, not a rawclick, so keyboard and pointer activation are handled for you.
Step 2: render it in React
createParts turns the primitive into components. You map each slot to an HTML element:
// Search.tsx
import { createParts } from "@ui-sdk/react/primitive"
import { searchField } from "./search-field"
const parts = createParts(searchField, { input: "input", clear: "button" })
const SearchField = parts.Root
const SearchFieldInput = parts.Input
const SearchFieldClear = parts.Clear
export function Search() {
return (
<SearchField>
<SearchFieldInput placeholder="Search" />
<SearchFieldClear>Clear</SearchFieldClear>
</SearchField>
)
}
The components are unstyled. Add your own classes or CSS; UI SDK only handles behavior and accessibility.
Step 3: the same primitive in Vue or Web Components
The parts are identical across adapters. In Vue:
<script setup lang="ts">
import { createParts } from "@ui-sdk/vue/primitive"
import { searchField } from "./search-field"
const parts = createParts(searchField, { input: "input", clear: "button" })
const SearchField = parts.Root
const SearchFieldInput = parts.Input
const SearchFieldClear = parts.Clear
</script>
<template>
<SearchField>
<SearchFieldInput placeholder="Search" />
<SearchFieldClear>Clear</SearchFieldClear>
</SearchField>
</template>
For framework-free pages, register custom elements:
import { createParts, wrappedButton, wrappedChild } from "@ui-sdk/web"
import { searchField } from "./search-field"
createParts(searchField, {
root: "search-field",
input: { tag: "search-field-input", target: wrappedChild("input") },
clear: { tag: "search-field-clear", target: wrappedButton },
})
<search-field>
<search-field-input><input placeholder="Search" /></search-field-input>
<search-field-clear><button>Clear</button></search-field-clear>
</search-field>
This is the strongest practical argument for the library today: a company with a React app and a Vue admin panel can share one behavior definition instead of two diverging components.
Starter components
If you do not want to write primitives yet, the docs ship a starter set. Launch coverage reports that the React docs list 55 unstyled components, from Accordion to Waterfall, with matching parts in the other adapters, and that the team has built more than 50 behaviors. You bring the styles.
The agent angle
UI SDK also lets agents use interfaces, not just build them. According to the launch coverage, an app can expose component state and actions (such as setting an input’s value) to an agent, gated by an allow callback you provide, and change callbacks receive source: "agent" so you can tell agent edits from user edits. The docs also describe registering these tools with WebMCP. Vercel published a small comparison where an agent completed a settings task faster and cheaper through this bridge than with screenshot-based browser tools; it was a median of three runs on one task, so treat it as an early signal rather than a benchmark.
Should you use it now?
- Yes, to experiment, especially if you maintain components in more than one framework or you are building agent-driven UIs.
- Carefully in production. It is a public beta under Vercel’s beta terms, and the README says APIs and behavior may change before general availability. Pin exact versions.
- Not a replacement for AI SDK. Despite the similar name, UI SDK is a separate library; Vercel’s AI SDK UI hooks like
useChatare unaffected.
Takeaway
UI SDK moves the reusable unit from “component” down to “behavior”. Define a primitive once with definePrimitive, render it anywhere with createParts, and let accessibility come from tested behaviors instead of hand-written ARIA. Install the beta, rebuild one small component like the search field above, and see whether the model fits your design system. Docs are at ui-sdk.dev.
