ObjectUI
A server-driven UI engine: it renders JSON metadata from the @objectstack/spec
protocol into React interfaces built on Tailwind and Shadcn — dashboards,
kanbans, CRUDs, forms, grids.
Where to go
| Your task |
Guide |
| Build or debug a page schema — nodes, layout, plugin widgets, common traps |
guides/page-builder.md |
| An expression is not evaluating, or you need the syntax / scope / formula functions |
guides/schema-expressions.md |
Feed a page from a backend — DataSource, QueryParams, bind, mocking |
guides/data-integration.md |
| Package a custom renderer or field widget as a plugin |
guides/plugin-development.md |
Node shape, ComponentRegistry, the renderer recursion, which integration package to install |
guides/architecture.md |
Which URL a navigation item resolves to (the choice of construct is objectstack-ui's) |
guides/app-composition.md |
| Start a new project — CLI, Vite, Tailwind, config files |
guides/project-setup.md |
| Write unit / DOM / E2E tests for components, schemas and plugins |
guides/testing.md |
| Responsive behaviour, breakpoints, touch gestures |
guides/mobile.md |
| Auth, roles, tenants, client-side permission guards |
guides/auth-permissions.md |
| Multi-language text and formatters |
guides/i18n.md |
Rules — read before writing schemas. These are the non-negotiables, and they
are the anchor for every rule the guides only cue:
- JSON protocol — what is evaluated, what is read raw, why keys live on the
node and not under
props, layout responsiveness → rules/protocol.md
- Styling & Tailwind — the two published stylesheets, the
@theme /
@source split, recolouring → rules/styling.md
- Component composition →
rules/composition.md
Scope
In scope: authoring and debugging JSON schemas rendered by SchemaRenderer,
and using the @object-ui/* packages — core, components, fields, layout,
react, providers, app-shell, plugins, CLI.
Out of scope (defer to the sibling skill):
| Concern |
Skill |
| Objects, fields, validations, hooks, field conditional rules |
objectstack-data |
| App navigation, views, dashboards, pages — which construct to author |
objectstack-ui |
| CEL formulas and predicates |
objectstack-formula |
| REST/GraphQL endpoints, auth providers, route guards |
objectstack-api |
| Flows, workflows, triggers, approvals |
objectstack-automation |
| Bootstrap, plugins, kernel hooks, drivers |
objectstack-platform |
| ObjectQL query construction |
objectstack-query |
Core Principles
1. Strict adherence to @objectstack/spec
All component schemas, JSON structures and data types follow @objectstack/spec.
Do not invent schema properties — if the spec says columns, do not write
fields. Check the spec before writing any interface or type.
2. Protocol agnostic (the universal adapter)
Never hardcode objectql.find() or any specific backend call. Go through the
DataSource interface, injected via <SchemaRendererProvider dataSource={...} />.
An app may back ObjectUI with REST, GraphQL, ObjectQL or a local JSON file.
3. "Shadcn native" aesthetics
ObjectUI is serializable Shadcn. A component follows Shadcn's DOM structure
(CardHeader / CardTitle / CardContent) and always exposes className so
styles can be overridden from JSON.
4. The action system (interactivity)
Actions are data, not functions:
{
"events": {
"onClick": [
{ "action": "validate", "target": "form_1" },
{ "action": "submit", "target": "form_1" },
{ "action": "navigate", "params": { "url": "/success" } }
]
}
}
@object-ui/core dispatches these through an event bus.
5. Layout as components
Layouts are components that render children. Responsive column counts go on the
node as columns — a number, or a breakpoint object (rules/protocol.md
has the keys the renderer reads and the one it drops).
6. Type safety over magic
- No
any — use strict generics.
- Registry — map type strings (
"type": "button") to React components via
ComponentRegistry.
- No
eval() or runtime dynamic imports for component resolution.
Tech stack (strict constraints)
- Core: React 18+ (hooks), TypeScript 5.0+ (strict).
- Styling: Tailwind CSS. Required:
class-variance-authority for variants,
tailwind-merge + clsx (cn()) for overrides. Forbidden: inline styles
(style={{}}), CSS Modules, styled-components.
- UI primitives: Shadcn UI (Radix) + Lucide icons.
- State: Zustand (global), React Context (scoped).
- Testing: Vitest + React Testing Library + Playwright.
Common mistakes to avoid
- Writing large bespoke React JSX trees before defining a schema.
- Hardcoding API calls inside visual renderers.
- Introducing package coupling (a UI package depending on business logic).
- Registering components without a namespace in plugin-heavy projects.
- Expecting a
${...} on a top-level value / label to evaluate, or "fixing"
it by moving it under props — the first renders the literal, the second
renders nothing at all. Resolve the value in the host, or carry it on a text
node's content (rules/protocol.md).
- Missing the published stylesheet imports —
@object-ui/components/style.css
then @object-ui/fields/style.css, in that order. Components render but look
completely unstyled (rules/styling.md).
1---2name: objectui3description: Universal Server-Driven UI (SDUI) engine for building JSON-driven React interfaces with Shadcn design quality. Use for schema-driven page building, plugin development, expression bindings, data integration, testing, auth/permissions, i18n, mobile responsiveness and project setup with the `@object-ui/*` packages. Triggers on ObjectUI, SchemaRenderer, JSON UI schemas, SDUI, metadata-driven UIs, `@object-ui/*`. Do NOT use for server-side ObjectStack concerns (data modelling, API endpoints, automation, formulas, agents) — those belong to the `objectstack-*` skills.4license: Apache-2.05---67# ObjectUI89A server-driven UI engine: it renders JSON metadata from the `@objectstack/spec`10protocol into React interfaces built on Tailwind and Shadcn — dashboards,11kanbans, CRUDs, forms, grids.1213## Where to go1415| Your task | Guide |16|---|---|17| Build or debug a page schema — nodes, layout, plugin widgets, common traps | [`guides/page-builder.md`](./guides/page-builder.md) |18| An expression is not evaluating, or you need the syntax / scope / formula functions | [`guides/schema-expressions.md`](./guides/schema-expressions.md) |19| Feed a page from a backend — `DataSource`, `QueryParams`, `bind`, mocking | [`guides/data-integration.md`](./guides/data-integration.md) |20| Package a custom renderer or field widget as a plugin | [`guides/plugin-development.md`](./guides/plugin-development.md) |21| Node shape, `ComponentRegistry`, the renderer recursion, which integration package to install | [`guides/architecture.md`](./guides/architecture.md) |22| Which URL a navigation item resolves to (the *choice* of construct is `objectstack-ui`'s) | [`guides/app-composition.md`](./guides/app-composition.md) |23| Start a new project — CLI, Vite, Tailwind, config files | [`guides/project-setup.md`](./guides/project-setup.md) |24| Write unit / DOM / E2E tests for components, schemas and plugins | [`guides/testing.md`](./guides/testing.md) |25| Responsive behaviour, breakpoints, touch gestures | [`guides/mobile.md`](./guides/mobile.md) |26| Auth, roles, tenants, client-side permission guards | [`guides/auth-permissions.md`](./guides/auth-permissions.md) |27| Multi-language text and formatters | [`guides/i18n.md`](./guides/i18n.md) |2829**Rules — read before writing schemas.** These are the non-negotiables, and they30are the anchor for every rule the guides only cue:3132- **JSON protocol** — what is evaluated, what is read raw, why keys live on the33 node and not under `props`, layout responsiveness → [`rules/protocol.md`](./rules/protocol.md)34- **Styling & Tailwind** — the two published stylesheets, the `@theme` /35 `@source` split, recolouring → [`rules/styling.md`](./rules/styling.md)36- **Component composition** → [`rules/composition.md`](./rules/composition.md)3738## Scope3940**In scope:** authoring and debugging JSON schemas rendered by `SchemaRenderer`,41and using the `@object-ui/*` packages — core, components, fields, layout,42react, providers, app-shell, plugins, CLI.4344**Out of scope** (defer to the sibling skill):4546| Concern | Skill |47|---|---|48| Objects, fields, validations, hooks, field conditional rules | `objectstack-data` |49| App navigation, views, dashboards, pages — *which construct to author* | `objectstack-ui` |50| CEL formulas and predicates | `objectstack-formula` |51| REST/GraphQL endpoints, auth providers, route guards | `objectstack-api` |52| Flows, workflows, triggers, approvals | `objectstack-automation` |53| Bootstrap, plugins, kernel hooks, drivers | `objectstack-platform` |54| ObjectQL query construction | `objectstack-query` |5556## Core Principles5758### 1. Strict adherence to `@objectstack/spec`5960All component schemas, JSON structures and data types follow `@objectstack/spec`.61Do not invent schema properties — if the spec says `columns`, do not write62`fields`. Check the spec before writing any `interface` or `type`.6364### 2. Protocol agnostic (the universal adapter)6566Never hardcode `objectql.find()` or any specific backend call. Go through the67`DataSource` interface, injected via `<SchemaRendererProvider dataSource={...} />`.68An app may back ObjectUI with REST, GraphQL, ObjectQL or a local JSON file.6970### 3. "Shadcn native" aesthetics7172ObjectUI is serializable Shadcn. A component follows Shadcn's DOM structure73(`CardHeader` / `CardTitle` / `CardContent`) and always exposes `className` so74styles can be overridden from JSON.7576### 4. The action system (interactivity)7778Actions are data, not functions:7980<!-- os:check -->81```json82{83 "events": {84 "onClick": [85 { "action": "validate", "target": "form_1" },86 { "action": "submit", "target": "form_1" },87 { "action": "navigate", "params": { "url": "/success" } }88 ]89 }90}91```9293`@object-ui/core` dispatches these through an event bus.9495### 5. Layout as components9697Layouts are components that render children. Responsive column counts go on the98node as `columns` — a number, or a breakpoint object ([`rules/protocol.md`](./rules/protocol.md)99has the keys the renderer reads and the one it drops).100101### 6. Type safety over magic102103- **No `any`** — use strict generics.104- **Registry** — map type strings (`"type": "button"`) to React components via105 `ComponentRegistry`.106- **No `eval()` or runtime dynamic imports** for component resolution.107108## Tech stack (strict constraints)109110- **Core:** React 18+ (hooks), TypeScript 5.0+ (strict).111- **Styling:** Tailwind CSS. Required: `class-variance-authority` for variants,112 `tailwind-merge` + `clsx` (`cn()`) for overrides. Forbidden: inline styles113 (`style={{}}`), CSS Modules, styled-components.114- **UI primitives:** Shadcn UI (Radix) + Lucide icons.115- **State:** Zustand (global), React Context (scoped).116- **Testing:** Vitest + React Testing Library + Playwright.117118## Common mistakes to avoid119120- Writing large bespoke React JSX trees before defining a schema.121- Hardcoding API calls inside visual renderers.122- Introducing package coupling (a UI package depending on business logic).123- Registering components without a namespace in plugin-heavy projects.124- Expecting a `${...}` on a top-level `value` / `label` to evaluate, or "fixing"125 it by moving it under `props` — the first renders the literal, the second126 renders nothing at all. Resolve the value in the host, or carry it on a `text`127 node's `content` ([`rules/protocol.md`](./rules/protocol.md)).128- Missing the published stylesheet imports — `@object-ui/components/style.css`129 then `@object-ui/fields/style.css`, in that order. Components render but look130 completely unstyled ([`rules/styling.md`](./rules/styling.md)).