# Schemas

Schemas define the structure and validation rules for your UI specs.

## What is a Schema?

A schema defines the JSON structure that describes your UI. It includes:

- **Element structure** — How components are nested and referenced
- **Property types** — What props each component accepts
- **Data binding syntax** — How to reference dynamic data
- **Action format** — How user interactions are defined

## Schema-Agnostic by Design

json-render can work with any JSON schema. `@json-render/core` provides the primitives to define catalogs and renderers for any format:

- **@json-render/react** — The built-in flat element tree schema
- **[A2UI](/docs/a2ui)** — Google's Agent-to-User Interaction protocol
- **[Adaptive Cards](/docs/adaptive-cards)** — Microsoft's platform-agnostic UI format
- **AG-UI** — CopilotKit's Agent User Interaction Protocol
- **OpenAPI/Swagger** — API documentation schemas for dynamic forms
- **Custom schemas** — Design your own format tailored to your domain

See the [Custom Schema guide](/docs/custom-schema) to learn how to implement support for any schema.

## Built-in Schema

`@json-render/react` uses a flat element tree schema with a root key and elements map:

```json
{
  "root": "card-1",
  "elements": {
    "card-1": {
      "type": "Card",
      "props": { "title": "Dashboard" },
      "children": ["text-1", "button-1"]
    },
    "text-1": {
      "type": "Text",
      "props": { "content": { "$state": "/user/name" } },
      "children": []
    },
    "button-1": {
      "type": "Button",
      "props": { "label": "Click me" },
      "children": []
    }
  }
}
```

## Schema Components

### Element Structure

In the built-in schema, each element in the elements map has this structure:

```typescript
interface Element {
  type: string;                // Component type from catalog
  props: Record<string, any>;  // Component properties
  children: string[];          // Array of child element keys
  visible?: VisibilityCondition;  // Conditional display
}
```

### Data Binding Syntax

Reference dynamic data using `$state` expressions in props. The value is a JSON Pointer path into the state model:

```json
{
  "type": "Text",
  "props": {
    "content": { "$state": "/user/name" },
    "count": { "$state": "/items/count" }
  },
  "children": []
}
```

json-render also supports `$item` and `$index` expressions for lists, two-way binding via `$bindState` / `$bindItem`, and conditional props. See [Data Binding](/docs/data-binding) for the full reference.

### Action Format

Actions are defined in the catalog and referenced from components. The renderer handles action execution:

```typescript
// In your catalog
actions: {
  navigate: {
    params: z.object({ url: z.string() }),
    description: 'Navigate to a URL',
  },
  apiCall: {
    params: z.object({
      endpoint: z.string(),
      method: z.enum(['GET', 'POST', 'PUT', 'DELETE']),
    }),
    description: 'Make an API request',
  },
}
```

## Custom Schemas

`@json-render/core` is schema-agnostic. You can define any JSON structure:

```typescript

// Define your own element schema
const MyElementSchema = z.object({
  component: z.string(),
  settings: z.record(z.unknown()),
  nested: z.array(z.lazy(() => MyElementSchema)).optional(),
});

// Define your own data binding format
const BoundValue = z.object({
  literal: z.string().optional(),
  source: z.string().optional(),  // e.g., "/users/0/name"
});

// Define your own action format
const ActionSchema = z.object({
  name: z.string(),
  context: z.record(z.unknown()).optional(),
});
```

## Schema vs Catalog

The schema and catalog work together but serve different purposes:

- **Schema** — Defines the JSON structure (how elements are organized)
- **Catalog** — Defines available components and their props (what can be used)

The schema is the grammar; the catalog is the vocabulary.

## Next

Learn about [specs](/docs/specs) — the actual JSON documents that describe your UI.