Skip to content

Base UI prompt

Rules that make your coding agent build on Base UI 1.9 as it works: subpath imports, the render prop, real data attributes, CSS-variable positioning and Field forms.

What it makes your agent good at

Agents treat Base UI as Radix with another name: they write asChild, style data-[state=open], put side on the popup and import @base-ui-components/react. This prompt gives them the real 1.9 API: render and nativeButton, the attributes and CSS variables it sets, change events with reasons, and Field and Form for validation.

  1. Composition with renderrender with an element or a function of props and state, nativeButton when the element isn't a button, no asChild or forwardRef.
  2. The attributes Base UI setsdata-open, data-highlighted, data-checked, data-starting-style and the rest as Tailwind variants, never data-state.
  3. Popups that fit their triggerPositioner props for placement, --anchor-width, --available-height and --transform-origin for size and origin.
  4. Accessible, validated formsField, Fieldset and Form with native constraints, validate, validationMode and server errors by field name.
  5. Controlled state, done rightvalue with onValueChange, eventDetails.reason and cancel(), handles for triggers that live elsewhere.
  6. Your components, same APIuseRender and mergeProps so your own components take the same render prop and merge handlers and refs.

The Base UI rules

104 lines, current to Base UI 1.9, React 19.3 and Tailwind CSS 4.3. Last reviewed October 23, 2026. Plain Markdown, no front matter: each agent's is below.

Download

base-ui.md

# Base UI

You are working in a React 19 project that builds its components on Base UI (`@base-ui/react` 1.x) and TypeScript, usually styled with Tailwind CSS v4. Write code for the version in `package.json`, not the one in your training data: Base UI reached 1.0 after most of it, and its APIs differ from Radix in ways that compile but behave wrongly.

## Before you write code

- Read the docs the package ships: `node_modules/@base-ui/react/docs/react/components/<name>.md` (anatomy, examples, every prop, data attribute and CSS variable) and `handbook/` (composition, styling, animation, forms, customization). They are authoritative over what you remember.
- The package is `@base-ui/react`. The old name `@base-ui-components/react` must not appear in imports or installs.
- Check `components/ui/` (or the project's equivalent) first. If wrappers exist, as in shadcn/ui on Base UI, use and extend them instead of importing primitives into pages.
- Portaled popups need the app root to set `isolation: isolate` so they stack above page content without z-index fights; backdrops on iOS 26+ need `body { position: relative }`. Add both once if missing.

## Imports and anatomy

- Import each component from its subpath; parts are properties of it: `import { Popover } from "@base-ui/react/popover"`, then `Popover.Root`, `Popover.Trigger`, `Popover.Portal`, `Popover.Positioner`, `Popover.Popup`, `Popover.Arrow`, `Popover.Title`, `Popover.Description`, `Popover.Close`.
- Floating popups (Popover, Menu, Select, Combobox, Tooltip, Preview Card) nest Portal > Positioner > Popup. Positioning props (`side`, `align`, `sideOffset`, `alignOffset`, `collisionPadding`) go on the Positioner, never the Popup. Dialog and Alert Dialog have no Positioner: Portal > Backdrop + Popup (inside `Dialog.Viewport` when the dialog scrolls).
- Types live on the part's namespace: `Menu.Root.Props`, `Select.Trigger.State`, `Combobox.Root.ChangeEventDetails`. Type wrappers with them instead of hand-written prop interfaces.
- Files that use Base UI components are Client Components in Next.js: start them with `"use client"`.

## Composition with render

Base UI has no `asChild`. Use `render` to compose a part with your own component or to change its element:

```tsx
<Menu.Trigger render={<Button variant="outline" />}>Options</Menu.Trigger>
<Tooltip.Trigger render={<Dialog.Trigger render={<Button size="icon" aria-label="Share" />} />}>
  <ShareIcon />
</Tooltip.Trigger>
<Switch.Thumb render={(props, state) => <span {...props}>{state.checked ? <On /> : <Off />}</span>} />
```

- The element form merges Base UI's props into your element. The function form gets `(props, state)`: spread `props` on the root element every time, it carries the ref, handlers and ARIA attributes.
- A component passed to `render` must accept `ref` as a prop and spread the rest onto its DOM node (React 19: no `forwardRef`).
- `nativeButton`: triggers, Button and close parts default to `true`. When `render` produces something that is not a `<button>`, pass `nativeButton={false}`. Items, Checkbox, Radio and Switch default to `false`; pass `true` if you render a `<button>`.
- Do not render links through Button or a trigger. Style the `<a>` (or framework `Link`) directly; inside menus use `Menu.LinkItem`.
- Base UI's Button renders `type="button"`: write `type="submit"` explicitly on submit buttons.

## Styling

- Parts are unstyled. Style with `className` (a string, or a function of state: `className={(state) => …}`), `style` (an object or a function of state), data attributes and CSS variables.
- Base UI has no `data-state` attribute. Target the real ones: `data-open` and `data-closed`, `data-starting-style` and `data-ending-style` (enter and exit), `data-side` and `data-align` (Positioner and Popup), `data-popup-open` and `data-pressed` (triggers), `data-highlighted` (menu, select and combobox items), `data-selected` (select and combobox items), `data-checked`, `data-unchecked`, `data-indeterminate`, `data-disabled`, `data-panel-open` (accordion and collapsible triggers), `data-active` (tabs), and on Field parts `data-invalid`, `data-valid`, `data-dirty`, `data-touched`, `data-filled`, `data-focused`. Confirm names in the component's doc page.
- In Tailwind v4 they are plain variants: `data-highlighted:bg-accent`, `data-disabled:opacity-50`, `data-[side=top]:mb-1`.
- Use the CSS variables instead of measuring in JavaScript: on Positioner and Popup `--anchor-width`, `--anchor-height`, `--available-width`, `--available-height`, `--transform-origin`; `--accordion-panel-height` and `--collapsible-panel-height` on panels; `--active-tab-left` and `--active-tab-width` on `Tabs.Indicator`. For example `origin-(--transform-origin) max-h-(--available-height) min-w-(--anchor-width)`.

## Animation

- Prefer CSS transitions with `data-starting-style` and `data-ending-style`: they cancel smoothly when a popup closes mid-open. `transition-[opacity,scale] data-starting-style:scale-95 data-starting-style:opacity-0 data-ending-style:scale-95 data-ending-style:opacity-0`.
- For keyframe animations use `data-open` and `data-closed`. Base UI keeps the popup mounted until the animation finishes, then calls `onOpenChangeComplete`.
- With Motion: control `open`, put `keepMounted` on the Portal, wrap it in `AnimatePresence`, and pass `render={<motion.div … />}` to the Popup. Animate `opacity` so Base UI detects the exit.

## State and events

- Uncontrolled by default (`defaultValue`, `defaultOpen`). To control, pass `value` with `onValueChange` or `open` with `onOpenChange`.
- Change handlers receive `(value, eventDetails)`. `eventDetails.reason` says why (`"escape-key"`, `"outside-press"`, `"item-press"`, `"trigger-press"`…), `eventDetails.cancel()` stops the change without controlling state, `eventDetails.allowPropagation()` lets Escape reach a parent.
- To open a dialog, popover or menu from elsewhere, create a handle (`Dialog.createHandle()`) and pass it as `handle` to the Root and to triggers placed anywhere; `handle.open()` works while that Root is mounted.
- Select: pass `items` to `Select.Root` so `Select.Value` shows labels instead of raw values; `placeholder` on `Select.Value`; `multiple` for multi-select; object values need `itemToStringValue` (and `isItemEqualToValue` for refetched objects). The popup overlaps the trigger by default (`alignItemWithTrigger`); set it `false` on the Positioner for a dropdown below.
- Combobox: pass `items` to the Root and render `<Combobox.List>{(item) => <Combobox.Item key={item.value} value={item}>…</Combobox.Item>}</Combobox.List>` with a `Combobox.Empty`. Filtering is built in; pass `filteredItems` for server results; use `Combobox.createItems` when selection should be an ID. Use Autocomplete for free text, Select when there is no input.
- Menu: `Menu.Item` takes `onClick` (it closes the menu unless `closeOnClick={false}`), not `onSelect`. `Menu.GroupLabel` goes inside `Menu.Group` or `Menu.RadioGroup`; submenus are `Menu.SubmenuRoot` with `Menu.SubmenuTrigger`.
- Dialog: always render `Dialog.Title` (and `Dialog.Description` when there is one). Use Alert Dialog for confirmations. `initialFocus` and `finalFocus` on the Popup move focus deliberately.
- Tooltip: wrap the app once in `Tooltip.Provider` so tooltips share delays. A tooltip supplements a name, never replaces it: icon-only triggers still need `aria-label`, and nothing interactive goes inside.
- Toast: render `Toast.Provider` once with a `Toast.Viewport` that maps `useToastManager().toasts` to `Toast.Root`; add toasts with `Toast.useToastManager().add({ title, description })`, or through `Toast.createToastManager()` outside React.

## Forms

- Compose `Field.Root` (with `name`) > `Field.Label`, `Field.Control` or another control, `Field.Description`, `Field.Error`. Group with `Fieldset.Root` and `Fieldset.Legend`; wrap each checkbox or radio option in `Field.Item`. Select, Slider and an input-in-popup Combobox use their own `Label` part.
- `Form` validates natively (`required`, `minLength`, `pattern`) and with `validate` on `Field.Root` (sync or async, returns a message or an array of them). Choose when with `validationMode` (`"onSubmit"` default, `"onBlur"`, `"onChange"`) and debounce with `validationDebounceTime`.
- `Field.Error` with no children shows the browser's message; `match="valueMissing"` (or another validity key) shows yours for that case.
- `onFormSubmit(values, eventDetails)` gives values as an object and prevents the native submit. For server validation, pass errors keyed by field name to `<Form errors={…}>`, for example from `useActionState`.
- With React Hook Form, wrap controls in `Controller` and pass `invalid`, `touched` and `dirty` from `fieldState` to `Field.Root`, plus `inputRef` so invalid fields get focus. With TanStack Form, drop the Base UI `Form`.

## Your own components

Give your own components the same `render` prop with `useRender` and `mergeProps`:

```tsx
import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render";

interface TagProps extends useRender.ComponentProps<"span"> {}

export function Tag({ render, ...props }: TagProps) {
  return useRender({
    defaultTagName: "span",
    render,
    props: mergeProps<"span">({ className: "inline-flex items-center gap-1" }, props),
  });
}
```

- Pass `state` to `useRender` to expose it to `render` functions and as `data-*` attributes; pass an internal ref as `ref` to merge it with the consumer's.
- `mergeProps` chains handlers (rightmost first), joins `className` and `style`, and keeps only the rightmost `ref`. In a merged handler, `event.preventBaseUIHandler()` skips Base UI's own logic.

## Do not

- Do not write `asChild`, `Slot`, Radix imports, `data-[state=open]:` selectors, `onSelect` on menu items or `type="single"` props: those are Radix APIs.
- Do not wrap components in `forwardRef`, import from internal paths, or use `@base-ui-components/react`.
- Do not put `side` or `sideOffset` on the Popup, measure the trigger with JavaScript, or add `z-50` to popups to fix stacking.
- Do not animate exits with conditional rendering of the Popup: Base UI unmounts it after the exit animation itself.
- Do not render an `<a>` as a Button, or a `<div>` trigger without `nativeButton={false}`.

## Before you finish

- Run the type checker and the linter and fix what they report.
- Use every component you touched from the keyboard: Tab to it, open with Enter, Space or the arrow keys, move with the arrows and typeahead, close with Escape, and check focus returns to the trigger.
- Check each popup's open and close animation in both directions, its position near viewport edges, and its contrast in light and dark mode.

10,745 characters, 1,408 words. Under Windsurf's 12,000-character limit for a rule, as every prompt here is.

Use it in your agent

The same rules, where your agent reads them. Each file has the front matter its agent needs, set to apply on every request.

Claude Code

A rule it loads every session

  1. Save it as .claude/rules/base-ui.md in your project, front matter included, or run the install command.
  2. Claude Code loads every file in .claude/rules with your CLAUDE.md when a session starts.
  3. Prefer one file? Paste it into CLAUDE.md under its own heading instead.

.claude/rules/base-ui.md

# Base UIYou are working in a React 19 project that builds its components on Base UI (`@base-ui/react` 1.x) and TypeScript, usually styled with Tailwind CSS v4. Write code for the version in `package.json`, not the one in your training data: Base UI reached 1.0 after most of it, and its APIs differ from Radix in ways that compile but behave wrongly.## Before you write code- Read the docs the package ships: `node_modules/@base-ui/react/docs/react/components/<name>.md` (anatomy, examples, every prop, data attribute and CSS variable) and `handbook/` (composition, styling, animation, forms, customization). They are authoritative over what you remember.- The package is `@base-ui/react`. The old name `@base-ui-components/react` must not appear in imports or installs.- Check `components/ui/` (or the project's equivalent) first. If wrappers exist, as in shadcn/ui on Base UI, use and extend them instead of importing primitives into pages.- Portaled popups need the app root to set `isolation: isolate` so they stack above page content without z-index fights; backdrops on iOS 26+ need `body { position: relative }`. Add both once if missing.## Imports and anatomy…

Or let the shadcn CLI write this file: sign in (free) for your command.

Cursor

A project rule with front matter

  1. Save it as .cursor/rules/base-ui.mdc, front matter included, or run the install command.
  2. alwaysApply: true adds it to every Agent request in this project.
  3. Check it under Cursor Settings, Rules, then commit the folder for your team.

.cursor/rules/base-ui.mdc

---description: "Building or styling components with Base UI (@base-ui/react): parts, render prop, data attributes, popups, forms and useRender."alwaysApply: true---# Base UIYou are working in a React 19 project that builds its components on Base UI (`@base-ui/react` 1.x) and TypeScript, usually styled with Tailwind CSS v4. Write code for the version in `package.json`, not the one in your training data: Base UI reached 1.0 after most of it, and its APIs differ from Radix in ways that compile but behave wrongly.## Before you write code- Read the docs the package ships: `node_modules/@base-ui/react/docs/react/components/<name>.md` (anatomy, examples, every prop, data attribute and CSS variable) and `handbook/` (composition, styling, animation, forms, customization). They are authoritative over what you remember.- The package is `@base-ui/react`. The old name `@base-ui-components/react` must not appear in imports or installs.- Check `components/ui/` (or the project's equivalent) first. If wrappers exist, as in shadcn/ui on Base UI, use and extend them instead of importing primitives into pages.- Portaled popups need the app root to set `isolation: isolate` so they stack above page content without z-index fights; backdrops on iOS 26+ need `body { position: relative }`. Add both once if missing.## Imports and anatomy…

Or let the shadcn CLI write this file: sign in (free) for your command.

Windsurf

A workspace rule for Cascade

  1. Save it as .windsurf/rules/base-ui.md, or run the install command.
  2. trigger: always_on applies it to every Cascade request in this workspace.
  3. Windsurf reads up to 12,000 characters per rule; this one is 10,745.

.windsurf/rules/base-ui.md

---trigger: always_on---# Base UIYou are working in a React 19 project that builds its components on Base UI (`@base-ui/react` 1.x) and TypeScript, usually styled with Tailwind CSS v4. Write code for the version in `package.json`, not the one in your training data: Base UI reached 1.0 after most of it, and its APIs differ from Radix in ways that compile but behave wrongly.## Before you write code- Read the docs the package ships: `node_modules/@base-ui/react/docs/react/components/<name>.md` (anatomy, examples, every prop, data attribute and CSS variable) and `handbook/` (composition, styling, animation, forms, customization). They are authoritative over what you remember.- The package is `@base-ui/react`. The old name `@base-ui-components/react` must not appear in imports or installs.- Check `components/ui/` (or the project's equivalent) first. If wrappers exist, as in shadcn/ui on Base UI, use and extend them instead of importing primitives into pages.- Portaled popups need the app root to set `isolation: isolate` so they stack above page content without z-index fights; backdrops on iOS 26+ need `body { position: relative }`. Add both once if missing.## Imports and anatomy…

Or let the shadcn CLI write this file: sign in (free) for your command.

GitHub Copilot

Instructions for the repository

  1. Save it as .github/instructions/base-ui.instructions.md, or run the install command.
  2. applyTo: "**" applies it to every request in this repository.
  3. Or paste it into .github/copilot-instructions.md, which Copilot reads for every request.

.github/instructions/base-ui.instructions.md

---description: "Building or styling components with Base UI (@base-ui/react): parts, render prop, data attributes, popups, forms and useRender."applyTo: "**"---# Base UIYou are working in a React 19 project that builds its components on Base UI (`@base-ui/react` 1.x) and TypeScript, usually styled with Tailwind CSS v4. Write code for the version in `package.json`, not the one in your training data: Base UI reached 1.0 after most of it, and its APIs differ from Radix in ways that compile but behave wrongly.## Before you write code- Read the docs the package ships: `node_modules/@base-ui/react/docs/react/components/<name>.md` (anatomy, examples, every prop, data attribute and CSS variable) and `handbook/` (composition, styling, animation, forms, customization). They are authoritative over what you remember.- The package is `@base-ui/react`. The old name `@base-ui-components/react` must not appear in imports or installs.- Check `components/ui/` (or the project's equivalent) first. If wrappers exist, as in shadcn/ui on Base UI, use and extend them instead of importing primitives into pages.- Portaled popups need the app root to set `isolation: isolate` so they stack above page content without z-index fights; backdrops on iOS 26+ need `body { position: relative }`. Add both once if missing.## Imports and anatomy…

Or let the shadcn CLI write this file: sign in (free) for your command.

Codex & AGENTS.md

The file most agents read

  1. Paste it into AGENTS.md at your project's root, under its own heading.
  2. Codex reads AGENTS.md before it starts work, and so do Cursor, Zed, Jules, Amp and Copilot's coding agent.
  3. A folder can have its own AGENTS.md: the one closest to the file being edited wins.

AGENTS.md

## Base UIYou are working in a React 19 project that builds its components on Base UI (`@base-ui/react` 1.x) and TypeScript, usually styled with Tailwind CSS v4. Write code for the version in `package.json`, not the one in your training data: Base UI reached 1.0 after most of it, and its APIs differ from Radix in ways that compile but behave wrongly.### Before you write code- Read the docs the package ships: `node_modules/@base-ui/react/docs/react/components/<name>.md` (anatomy, examples, every prop, data attribute and CSS variable) and `handbook/` (composition, styling, animation, forms, customization). They are authoritative over what you remember.- The package is `@base-ui/react`. The old name `@base-ui-components/react` must not appear in imports or installs.- Check `components/ui/` (or the project's equivalent) first. If wrappers exist, as in shadcn/ui on Base UI, use and extend them instead of importing primitives into pages.- Portaled popups need the app root to set `isolation: isolate` so they stack above page content without z-index fights; backdrops on iOS 26+ need `body { position: relative }`. Add both once if missing.### Imports and anatomy…

AGENTS.md is shared with your own instructions, so paste the rules in: the copy sets their headings a level down.

Rules teach it. The MCP equips it. With Pro, your agent installs every component, block and icon it needs, from a prompt.

Get Pro

Pricing

Give your agent the parts, with Pro

The prompts are free. Every Pro plan adds the MCP server, so your agent installs the components, blocks and icons it writes code around.

4.9/5 out of 5 · 150+ reviewsfrom 150+ founders and developers.

Billed once a year in USD. Cancel anytime.

Individual

For personal projects & open source

$32instead of $49/mo

$389 billed yearly

  • How many people can use the license.Seats: 1 seat
  • Client work, SaaS products and anything you charge for.Commercial use: Personal & open source only
  • Page sections in 56 categories, from heroes and pricing tables to dashboards and settings, on every framework and UI library, in your theme.Blocks: All 6,181 blocks
  • Complete Next.js sites for startups, SaaS, agencies and portfolios, every page designed, ready to ship.Templates: All 14 templates
  • Your AI agent searches, previews and installs any component, block, icon or DESIGN.md from a prompt: 22 tools in Claude Code, Cursor, VS Code and 35 more clients.MCP server: 38 AI clients
  • Buttons with motion and states of their own, from an arrow that slides in to a burst of confetti.Animated buttons: All 35 buttons
  • Typewriters, scrambles, counters and more, for headlines, labels and numbers.Text effects: All 97 effects
  • Animated backgrounds for heroes, sections and cards, from a soft aurora to a flock that flees the cursor. Most are pure CSS.Backgrounds: All 100 backgrounds
  • WebGPU shaders, from a slow aurora to a black hole, each drawing only while it's on screen.Shaders: All 100 shaders
  • One design and one theme on all four: the components, blocks and effects are built for each framework.Frameworks: React, Vue, Svelte, Solid
  • React on Base UI, Radix UI, React Aria or Ark UI: install the version for the library your project already uses. Vue, Svelte and Solid run on Ark UI.UI libraries: Base UI, Radix, Aria, Ark
  • From a button to a date picker, a color picker or a guided tour: accessible, typed in TypeScript, each with live demos.Components: All 174 components
  • Real-world patterns built from the components, in 53 galleries: forms, tables, menus, dialogs and more.Examples: All 1,103 examples
  • Area, bar, line, pie, radar and radial charts on TanStack Charts, on every framework. React also gets the Recharts originals.Charts: All 70 charts
  • Debounce, local storage, media queries and more: each hook is one typed file you own.React hooks: All 35 hooks
  • From 222 libraries, Lucide, Heroicons, Tabler and Material Symbols among them, as SVG or a React component in one command.Icons: 294k+ icons
  • Stripe, Linear, Apple and 497 more: a brand's colors, typography and components in one file your AI agent builds from.DESIGN.md files: All 500 brands
  • Restyle every component and block at once: 8 styles, 7 grays, 18 colors and 5 radii, shared as a shadcn preset code.Theme builder: 8 styles × 18 colors
  • Priority requests are answered first.Support: Standard

Team

Most popular

For freelancers, agencies & startups

$66instead of $99/mo

$789 billed yearly · ≈ $13/dev/mo

Everything in Individual, plus

  • How many people can use the license.Seats: Up to 5 seats
  • Client work, SaaS products and anything you charge for.Commercial use: Included
  • Priority requests are answered first.Support: Priority

Organization

For companies with larger teams

$132instead of $199/mo

$1,589 billed yearly

Everything in Team, plus

  • How many people can use the license.Seats: Unlimited seats
  • Secure checkout via Stripe
  • Code lives in your repo
  • Keep what you've shipped if you cancel
  • License terms

Base UI prompt FAQ

What the Base UI prompt covers and how to use it in your agent.

How does the Base UI prompt replace asChild?

Base UI has no asChild. The prompt has your agent pass render={<Button />} to a trigger, or a function of props and state that spreads the props it receives. Components used that way must take ref as a prop and spread the rest, so React 19 code needs no forwardRef.

What is nativeButton in Base UI and when does the agent set it?

nativeButton tells Base UI whether the rendered element is a real button. The prompt has the agent set nativeButton={false} when a trigger renders a div or span, set it to true when a menu item renders a button, and never render a link through Button or a trigger.

Can my own components take a render prop like Base UI's?

With useRender and mergeProps from @base-ui/react. The prompt shows the shape: a component typed with useRender.ComponentProps that merges its default props with the caller's, so it gets the same render prop, merged handlers and merged refs as Base UI's own parts.

4.9 out of 5 from 150+ developers

Rules teach it. The MCP equips it.

Every component, block and icon, installed by your AI agent on your stack. The prompts stay free.

Get Pro
  • Cancel anytime
  • Keep what you've shipped