---
title: "Toolkit UI Primitives"
description: "How to use @agent-native/toolkit UI primitives, the local app-adapter pattern, and shared shell hooks without locking apps out of customization."
---

# Toolkit UI Primitives

Toolkit UI primitives live under `@agent-native/toolkit/ui/*`. They are the
shared shadcn-style building blocks used by templates and reusable kits.

If your company already has a React component library, register it through the
semantic design-system bridge instead of replacing feature code. The bridge is
styling-runtime agnostic, supports partial adapters with per-control fallback,
and lets shared Toolkit and Core-owned feature views consume the same company
components. See [Custom Design Systems](/docs/custom-design-system) for the 17
semantic contracts, build-time theme tokens, conformance tests, and adapter
recipes.

<WireframeBlock id="doc-block-toolkit-ui-primitives">
  <Screen
    surface="desktop"
    html={
      "<div style='min-height:460px;box-sizing:border-box;padding:26px;display:flex;flex-direction:column;gap:16px'><h2 style='margin:0'>UI primitives</h2><div style='display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px'><div class='wf-card' style='display:flex;flex-direction:column;gap:10px'><small class='wf-muted'>Buttons</small><div style='display:flex;gap:8px'><button>Cancel</button><button class='primary'>Save</button></div></div><div class='wf-card' style='display:flex;flex-direction:column;gap:10px'><small class='wf-muted'>Input</small><div class='wf-box' style='display:flex;align-items:center;gap:8px'><span data-icon='search'></span><span class='wf-muted'>Search...</span></div></div><div class='wf-card' style='display:flex;flex-direction:column;gap:10px'><small class='wf-muted'>Select</small><div class='wf-box' style='display:flex;align-items:center;gap:8px'><div style='flex:1'>Editor</div><span data-icon='chevronDown'></span></div></div><div class='wf-card' style='display:flex;flex-direction:column;gap:10px'><small class='wf-muted'>Badges</small><div style='display:flex;gap:6px'><span class='wf-pill accent'>Active</span><span class='wf-pill'>Draft</span></div></div><div class='wf-card' style='display:flex;flex-direction:column;gap:8px'><small class='wf-muted'>Dialog</small><strong>Delete file?</strong><small class='wf-muted'>This cannot be undone.</small><div style='display:flex;gap:8px;justify-content:flex-end'><button>Cancel</button><button class='primary'>Delete</button></div></div><div class='wf-card' style='display:flex;flex-direction:column;gap:6px'><small class='wf-muted'>Menu</small><div style='display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:8px;background:var(--wf-accent-soft)'><span data-icon='edit'></span> Rename</div><div style='display:flex;align-items:center;gap:8px;padding:6px 8px'><span data-icon='user'></span> Share</div></div></div></div>"
    }
  />
</WireframeBlock>

Reusable package code can import them directly:

```tsx
import { Button } from "@agent-native/toolkit/ui/button";
import { Dialog, DialogContent } from "@agent-native/toolkit/ui/dialog";
```

Apps that render Toolkit UI must also import the Toolkit stylesheet from
`app/global.css` so Tailwind generates classes used only inside package
components, such as dropdown and popover z-index and animation utilities:

```css filename="app/global.css"
@import "tailwindcss";
@import "@agent-native/toolkit/styles.css";
```

## App adapters {#app-adapters}

Template app code should import a local adapter instead of a Toolkit
primitive directly:

```tsx
import { Button } from "@/components/ui/button";
```

Every template exposes these adapters under `@/components/ui/*`, and local
hook adapters under `@/hooks/*` (see [Shell hooks](#shell-hooks) below). Most
adapters are intentionally tiny — a one-line re-export:

```tsx filename="app/components/ui/button.tsx"
export * from "@agent-native/toolkit/ui/button";
```

<WireframeBlock id="doc-block-toolkit-app-adapters">
  <Screen
    surface="desktop"
    html={
      "<div style='min-height:460px;box-sizing:border-box;padding:28px;display:flex;flex-direction:column;gap:16px'><h2 style='margin:0'>One stable import path</h2><div style='display:grid;grid-template-columns:1fr auto 1fr auto 1fr;align-items:center;gap:12px'><div class='wf-card' style='display:flex;flex-direction:column;gap:8px'><span class='wf-pill'>Toolkit</span><strong>@agent-native/toolkit/ui/button</strong><small class='wf-muted'>Default behavior and accessibility</small></div><span data-icon='chevronRight' style='justify-self:center'></span><div class='wf-card' style='display:flex;flex-direction:column;gap:8px;border:1.6px solid var(--wf-accent);background:var(--wf-accent-soft)'><span class='wf-pill accent'>App adapter</span><strong>@/components/ui/button</strong><small class='wf-muted'>Re-export, swap once to restyle</small></div><span data-icon='chevronRight' style='justify-self:center'></span><div class='wf-card' style='display:flex;flex-direction:column;gap:8px'><span class='wf-pill'>App screens</span><div style='display:flex;align-items:center;gap:8px'><span data-icon='settings'></span> Settings</div><div style='display:flex;align-items:center;gap:8px'><span data-icon='user'></span> Share dialog</div><div style='display:flex;align-items:center;gap:8px'><span data-icon='search'></span> Command menu</div></div></div></div>"
    }
  />
</WireframeBlock>

This pattern gives apps a stable customization point. Reusable Toolkit code can
ship once, while app owners can replace `Button`, `Input`, or `Dialog` locally
without rewriting every callsite: when an app wants its own button, it
replaces that one adapter file, and the rest of the app keeps importing
`@/components/ui/button`.

**Import rule:** inside app code, import from the local adapter
(`@/components/ui/button`); inside Toolkit or other reusable packages, import
directly from `@agent-native/toolkit/ui/*`. Reusable kit components that
render app chrome should prefer reading overridable primitives from
`ToolkitProvider` instead of importing an adapter directly — see
[Provider overrides](#provider-overrides) below.

### Provider overrides {#provider-overrides}

Reusable Toolkit UI should read overridable primitives from `ToolkitProvider`
instead of hard-coding a visual system. Templates wire their local `Button` into
that provider so future kits automatically pick up app customization.

```tsx filename="app/components/ui/toolkit-provider.tsx"
import { ToolkitProvider } from "@agent-native/toolkit/provider";

import { Button } from "@/components/ui/button";

export function AppToolkitProvider({
  children,
}: {
  children: React.ReactNode;
}) {
  return <ToolkitProvider components={{ Button }}>{children}</ToolkitProvider>;
}
```

If an app override wants to keep Toolkit's default button styling as its base,
render `ButtonBase` from `@agent-native/toolkit/ui/button`. Do not render
Toolkit's `Button` from inside the override; `ButtonBase` is the explicit escape
hatch, while Toolkit's recursion guard is only a fallback.

Existing template code that used direct Toolkit imports has been migrated to
local adapters, and new template code should follow the same pattern. Toolkit
still exports every primitive directly, so external, non-template packages can
keep importing `@agent-native/toolkit/ui/*` without adopting local adapters.
Core also keeps old imports working through its own re-export shims for code
written before Toolkit adapters existed.

For new customization, prefer `designSystem={designSystem}` over the legacy
`components={{ Button }}` override. The design-system adapter uses semantic
props such as intent, emphasis, pending state, controlled values, and controlled
open state rather than shadcn variant names. When both APIs provide the button,
`designSystem.components.ActionButton` wins. A missing or failed custom control
falls back to Toolkit’s default adapter without disabling the rest of the
registered system.

## Data grid kit {#data-grid}

`@agent-native/toolkit/data-grid` provides a Core-free, spreadsheet-like grid
surface. The app supplies rows, typed columns, editor slots, selection state,
and persistence callbacks. The kit provides keyboard navigation, active-cell
behavior, optional selection, column sizing, and the scroll surface.

<WireframeBlock id="doc-block-toolkit-data-grid">
  <Screen
    surface="desktop"
    html={
      "<div style='min-height:420px;box-sizing:border-box;padding:22px;display:flex;flex-direction:column;gap:12px'><div style='display:flex;align-items:center;gap:8px'><h2 style='margin:0'>Projects</h2><span class='wf-pill'>3 rows</span><div style='flex:1'></div><button><span data-icon='search'></span> Filter</button></div><div class='wf-card' style='padding:0;overflow:hidden'><div style='display:grid;grid-template-columns:1.5fr 1fr 1fr 1fr;border-bottom:1px solid var(--wf-line);font-weight:600'><div style='padding:10px 12px'>Name</div><div style='padding:10px 12px'>Owner</div><div style='padding:10px 12px'>Status</div><div style='padding:10px 12px'>Updated</div></div><div style='display:grid;grid-template-columns:1.5fr 1fr 1fr 1fr;border-bottom:1px solid var(--wf-line)'><div style='padding:10px 12px'>Website refresh</div><div style='padding:10px 12px'>Maya</div><div style='padding:10px 12px'><span class='wf-pill accent'>In progress</span></div><div style='padding:10px 12px'>Today</div></div><div style='display:grid;grid-template-columns:1.5fr 1fr 1fr 1fr;border-bottom:1px solid var(--wf-line)'><div style='padding:10px 12px'>Q3 launch</div><div style='padding:10px 12px'>Kai</div><div style='padding:10px 12px'><span class='wf-pill'>Planning</span></div><div style='padding:10px 12px'>Yesterday</div></div><div style='display:grid;grid-template-columns:1.5fr 1fr 1fr 1fr'><div style='padding:10px 12px;border:2px solid var(--wf-accent);border-radius:4px'>Onboarding</div><div style='padding:10px 12px'>Steve</div><div style='padding:10px 12px'><span class='wf-pill'>Review</span></div><div style='padding:10px 12px'>Aug 8</div></div></div><small class='wf-muted'>Toolkit handles grid interaction. The app provides rows, columns, cell editors, and saved preferences.</small></div>"
    }
  />
</WireframeBlock>

The mock highlights an active cell. The app still owns the row data, property editors, actions, and saved view preferences.

The grid does not fetch data, call actions, resolve credentials, or persist
edits. Use `renderHeader`, `renderBody`, `renderRow`, and `renderFooter` when an
app needs to keep its own headers, grouping, row actions, or summary rows.

| Export                       | Purpose                                                                |
| ---------------------------- | ---------------------------------------------------------------------- |
| `<DataGrid>`                 | Render rows and columns with shared grid mechanics.                    |
| `DataGridColumn<Row>`        | Describe a typed column, including values, widths, cells, and editors. |
| `clampDataGridColumnWidth()` | Clamp an app-owned width to a column's configured bounds.              |
| `dataGridColumnTemplate()`   | Build the shared CSS grid template for custom slots.                   |

The smallest adapter only supplies rows, columns, and stable row IDs:

```tsx
import { DataGrid, type DataGridColumn } from "@agent-native/toolkit/data-grid";

type Row = { id: string; title: string };

const columns: readonly DataGridColumn<Row>[] = [
  {
    id: "title",
    label: "Title",
    getValue: (row) => row.title,
  },
];

<DataGrid rows={rows} columns={columns} getRowId={(row) => row.id} />;
```

Make selection and column widths controlled when the app needs to save them in
its own state or database view preferences:

```tsx
<DataGrid
  rows={rows}
  columns={columns}
  getRowId={(row) => row.id}
  selection="multiple"
  selectedRowIds={selectedRowIds}
  onSelectedRowIdsChange={setSelectedRowIds}
  columnWidths={columnWidths}
  onColumnWidthsChange={setColumnWidths}
/>
```

Content uses this surface in its database table view. `DatabaseView.tsx` keeps
Content-specific property rendering, grouping, row drag and drop, selection
actions, and row previews in the app while the Toolkit component supplies the
shared grid mechanics. Use the [Content Developer Guide](/docs/template-content-developers#data-grid) when changing that adapter.

## Shell hooks {#shell-hooks}

Toolkit includes small shell helpers and hooks that are useful across apps:

<WireframeBlock id="doc-block-toolkit-shell-hooks">
  <Screen
    surface="desktop"
    html={
      "<div style='min-height:460px;box-sizing:border-box;padding:24px;display:grid;grid-template-columns:200px 1fr;gap:16px'><aside class='wf-card' style='display:flex;flex-direction:column;gap:6px'><strong style='margin-bottom:4px'>Analytics</strong><div style='padding:6px 9px;border-radius:8px;background:var(--wf-accent-soft)'>Dashboards</div><div style='padding:6px 9px;border-radius:8px'>Reports</div><div style='padding:6px 9px;border-radius:8px'>Settings</div></aside><main style='position:relative;display:flex;flex-direction:column;gap:14px'><div class='wf-card' style='display:flex;align-items:center;gap:10px'><h2 style='margin:0'>Dashboards</h2><div style='flex:1'></div><button><span data-icon='send'></span> Export</button><button class='primary'><span data-icon='plus'></span> Create</button></div><div class='wf-card' style='flex:1;display:flex;flex-direction:column;gap:8px'><div style='height:11px;width:70%;background:var(--wf-line);border-radius:4px'></div><div style='height:11px;width:90%;background:var(--wf-line);border-radius:4px'></div><div style='height:11px;width:80%;background:var(--wf-line);border-radius:4px'></div></div><div class='wf-card' style='display:flex;align-items:center;gap:8px;border-color:var(--wf-accent);position:absolute;right:0;bottom:0;width:230px'><span data-icon='check'></span><div style='flex:1'>Saved changes</div><span data-icon='x'></span></div></main></div>"
    }
  />
</WireframeBlock>

```tsx
import { useSetHeaderActions } from "@agent-native/toolkit/app-shell";
import { useIsMobile } from "@agent-native/toolkit/hooks/use-mobile";
import { useToast } from "@agent-native/toolkit/hooks/use-toast";
```

Apps wrap these through the same local-adapter pattern as UI primitives (see
[App adapters](#app-adapters) above):

```ts filename="app/hooks/use-mobile.ts"
export * from "@agent-native/toolkit/hooks/use-mobile";
```

Keep shell helpers in Toolkit when they are reusable and do not own app data.
Keep app-specific navigation, command entries, and domain state in the template.

Core remains responsible for framework-wide shell behavior such as app providers,
agent chat, app state, auth, and runtime sync.

## What's next

- [**Agent-Native Toolkit**](/docs/agent-native-toolkit) — the hub page linking
  every kit and foundation piece.
- [**Sharing Kit**](/docs/toolkit-sharing) — another kit that splits Toolkit UI
  from Core-owned runtime and access checks.
- [**Collaboration Kit**](/docs/toolkit-collaboration) — the same Toolkit/Core
  split applied to realtime editing and presence.
- [**Component API**](/docs/components) — the generated reference for public
  React exports, including the primitives covered here.
