` — use `state.value` to access the underlying object. This differs from the React renderer where `state` is a plain object.
## Visibility Conditions
```typescript
// Truthiness check
{ "$state": "/user/isAdmin" }
// Auth state (use state path)
{ "$state": "/auth/isSignedIn" }
// Comparisons (flat style)
{ "$state": "/status", "eq": "active" }
{ "$state": "/count", "gt": 10 }
// Negation
{ "$state": "/maintenance", "not": true }
// Multiple conditions (implicit AND)
[
{ "$state": "/feature/enabled" },
{ "$state": "/maintenance", "not": true }
]
// Always / never
true // always visible
false // never visible
```
TypeScript helpers from `@json-render/core`:
```typescript
import { visibility } from "@json-render/core";
visibility.when("/path") // { $state: "/path" }
visibility.unless("/path") // { $state: "/path", not: true }
visibility.eq("/path", val) // { $state: "/path", eq: val }
visibility.neq("/path", val) // { $state: "/path", neq: val }
visibility.and(cond1, cond2) // { $and: [cond1, cond2] }
visibility.always // true
visibility.never // false
```
## Dynamic Prop Expressions
Any prop value can use data-driven expressions that resolve at render time. The renderer resolves these transparently before passing props to components.
```json
{
"type": "Badge",
"props": {
"label": { "$state": "/user/role" },
"color": {
"$cond": { "$state": "/user/role", "eq": "admin" },
"$then": "red",
"$else": "gray"
}
}
}
```
For two-way binding, use `{ "$bindState": "/path" }` on the natural value prop. Inside repeat scopes, use `{ "$bindItem": "field" }` instead. Components receive resolved `bindings` with the state path for each bound prop.
See [@json-render/core](../core/README.md) for full expression syntax.
## Built-in Actions
The `setState`, `pushState`, `removeState`, and `validateForm` actions are built into the Vue schema and handled automatically by `ActionProvider`. They are injected into AI prompts without needing to be declared in your catalog's `actions`. They update the state model, which triggers re-evaluation of visibility conditions and dynamic prop expressions:
```json
{
"type": "Button",
"props": { "label": "Switch Tab" },
"on": {
"press": {
"action": "setState",
"params": { "statePath": "/activeTab", "value": "settings" }
}
},
"children": []
}
```
## Component Props
When using `defineRegistry`, components receive these props via their render function:
```typescript
import type { VNode } from "vue";
interface ComponentContext {
props: P; // Typed props from the catalog (expressions resolved)
children?: VNode | VNode[]; // Rendered children (for container components)
emit: (event: string) => void; // Emit a named event (always defined)
on: (event: string) => EventHandle; // Get event handle with metadata
loading?: boolean; // Whether the parent is loading
bindings?: Record; // State paths for $bindState/$bindItem expressions
}
interface EventHandle {
emit: () => void; // Fire the event
shouldPreventDefault: boolean; // Whether any binding requested preventDefault
bound: boolean; // Whether any handler is bound
}
```
Use `emit("press")` for simple event firing. Use `on("click")` when you need to check metadata like `shouldPreventDefault` or `bound`:
```typescript
Link: ({ props, on }) => {
const click = on("click");
return h("a", {
href: props.href,
onClick: (e: MouseEvent) => {
if (click.shouldPreventDefault) e.preventDefault();
click.emit();
},
}, props.label);
},
```
### `BaseComponentProps`
For building reusable component libraries that are not tied to a specific catalog, use the catalog-agnostic `BaseComponentProps` type:
```typescript
import type { BaseComponentProps } from "@json-render/vue";
const Card = ({ props, children }: BaseComponentProps<{ title?: string }>) =>
h("div", null, [props.title, children]);
```
## Generate AI Prompts
```typescript
const systemPrompt = catalog.prompt();
// Returns detailed prompt with component/action descriptions
```
## Full Example
```typescript
import { h } from "vue";
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/vue/schema";
import { defineRegistry, Renderer, StateProvider } from "@json-render/vue";
import { z } from "zod";
const catalog = defineCatalog(schema, {
components: {
Greeting: {
props: z.object({ name: z.string() }),
description: "Displays a greeting",
},
},
actions: {},
});
const { registry } = defineRegistry(catalog, {
components: {
Greeting: ({ props }) => h("h1", null, `Hello, ${props.name}!`),
},
});
const spec = {
root: "greeting-1",
elements: {
"greeting-1": {
type: "Greeting",
props: { name: "World" },
children: [],
},
},
};
// In your App.vue:
//
//
//
```
## Key Exports
| Export | Purpose |
|--------|---------|
| `defineRegistry` | Create a type-safe component registry from a catalog |
| `Renderer` | Render a spec using a registry |
| `schema` | Element tree schema (includes built-in actions: `setState`, `pushState`, `removeState`) |
| `useStateStore` | Access state context (`state` is `ShallowRef`) |
| `useStateValue` | Get single value from state |
| `useActions` | Access actions context |
| `useAction` | Get a single action dispatch function |
| `createStateStore` | Create a framework-agnostic in-memory `StateStore` |
### Types
| Export | Purpose |
|--------|---------|
| `ComponentContext` | Typed component render function context (catalog-aware) |
| `BaseComponentProps` | Catalog-agnostic base type for reusable component libraries |
| `EventHandle` | Event handle with `emit()`, `shouldPreventDefault`, `bound` |
| `ActionProviderProps` | Props for `ActionProvider` |
| `ValidationProviderProps` | Props for `ValidationProvider` |
| `ComponentFn` | Component render function type |
| `SetState` | State setter type |
| `StateModel` | State model type |
| `StateStore` | Interface for plugging in external state management |
## Differences from `@json-render/react`
| API | React | Vue | Note |
|-----|-------|-----|------|
| `useStateStore().state` | `StateModel` | `ShallowRef` | Vue reactivity; use `state.value` |
| `children` type | `React.ReactNode` | `VNode \| VNode[]` | Platform-specific |
| `useBoundProp` | exported | exported | Same API; returns `[value, setValue]` |
| Streaming hooks | `useUIStream`, `useChatUI` | `useUIStream`, `useChatUI` | Same API; returns Vue `Ref` values |