@@ -87,10 +87,8 @@ interface DockEntry {
8787 buttonStart? : string
8888 buttonLoading? : string
8989 }
90- /** Inline JSON spec (for type: 'json-render') */
91- spec? : JsonRenderSpec
92- /** Shared state key holding the JSON spec (for type: 'json-render') */
93- sharedStateKey? : string
90+ /** JsonRenderer handle created by ctx.createJsonRenderer() (for type: 'json-render') */
91+ ui? : JsonRenderer
9492}
9593```
9694
@@ -303,160 +301,45 @@ ctx.docks.register({
303301
304302## JSON Render Panels
305303
306- JSON render panels let you describe your UI as a JSON spec on the server side. The DevTools client renders it with built-in components powered by [ json-render ] ( https://github.com/vercel-labs/json-render ) . ** No client code needed. **
304+ JSON render panels let you describe your UI as a JSON spec on the server side — ** no client code needed. ** This is the simplest way to add a DevTools panel.
307305
308- This is the simplest way to add a DevTools panel — you only write server-side TypeScript.
309-
310- ### Basic Example
306+ Use ` ctx.createJsonRenderer() ` to create a renderer handle, then pass it as ` ui ` when registering a ` json-render ` dock entry:
311307
312308``` ts
313- import { defineJsonRenderSpec } from ' @vitejs/devtools-kit'
314-
315- ctx .docks .register ({
316- id: ' my-panel' ,
317- title: ' My Panel' ,
318- icon: ' ph:chart-bar-duotone' ,
319- type: ' json-render' ,
320- spec: defineJsonRenderSpec ({
321- root: ' root' ,
322- elements: {
323- root: {
324- type: ' Stack' ,
325- props: { direction: ' vertical' , gap: 12 },
326- children: [' heading' , ' info' ],
327- },
328- heading: {
329- type: ' Text' ,
330- props: { content: ' Hello from JSON!' , variant: ' heading' },
331- },
332- info: {
333- type: ' KeyValueTable' ,
334- props: {
335- entries: [
336- { key: ' Version' , value: ' 1.0.0' },
337- { key: ' Status' , value: ' Running' },
338- ],
339- },
309+ const ui = ctx .createJsonRenderer ({
310+ root: ' root' ,
311+ elements: {
312+ root: {
313+ type: ' Stack' ,
314+ props: { direction: ' vertical' , gap: 12 },
315+ children: [' heading' , ' info' ],
316+ },
317+ heading: {
318+ type: ' Text' ,
319+ props: { content: ' Hello from JSON!' , variant: ' heading' },
320+ },
321+ info: {
322+ type: ' KeyValueTable' ,
323+ props: {
324+ entries: [
325+ { key: ' Version' , value: ' 1.0.0' },
326+ { key: ' Status' , value: ' Running' },
327+ ],
340328 },
341329 },
342- }) ,
330+ },
343331})
344- ```
345-
346- ### Dynamic Data with Shared State
347-
348- For dynamic UIs that update over time, store the spec in a [ shared state] ( ./shared-state ) key:
349332
350- ``` ts
351333ctx .docks .register ({
352334 id: ' my-panel' ,
353335 title: ' My Panel' ,
354336 icon: ' ph:chart-bar-duotone' ,
355337 type: ' json-render' ,
356- sharedStateKey: ' my-plugin:ui' ,
357- })
358-
359- const ui = await ctx .rpc .sharedState .get (' my-plugin:ui' , {
360- initialValue: defineJsonRenderSpec ({ /* spec */ }),
361- })
362-
363- // Later, update the UI reactively:
364- ui .mutate ((draft ) => {
365- draft .elements .heading .props .content = ' Updated!'
366- })
367- ```
368-
369- ### Handling Actions via RPC
370-
371- Buttons in the spec can trigger RPC functions on the server:
372-
373- ``` ts
374- // In the spec:
375- defineJsonRenderSpec ({
376- // ...
377- elements: {
378- ' refresh-btn' : {
379- type: ' Button' ,
380- props: { label: ' Refresh' },
381- on: { press: { action: ' my-plugin:refresh' } },
382- },
383- },
338+ ui ,
384339})
385340```
386341
387- ``` ts
388- // On the server:
389- ctx .rpc .register (defineRpcFunction ({
390- name: ' my-plugin:refresh' ,
391- type: ' event' ,
392- setup : ctx => ({
393- handler : async () => {
394- // Fetch new data, then update the spec
395- ui .mutate ((draft ) => { /* ... */ })
396- },
397- }),
398- }))
399- ```
400-
401- ### Text Input with Two-Way Binding
402-
403- Use ` $bindState ` for two-way binding on text inputs, and ` $state ` to read the bound value in action params:
404-
405- ``` ts
406- defineJsonRenderSpec ({
407- // ...
408- elements: {
409- ' my-input' : {
410- type: ' TextInput' ,
411- props: {
412- placeholder: ' Type here...' ,
413- value: { $bindState: ' /inputValue' },
414- },
415- },
416- ' submit-btn' : {
417- type: ' Button' ,
418- props: { label: ' Submit' },
419- on: {
420- press: {
421- action: ' my-plugin:submit' ,
422- params: { text: { $state: ' /inputValue' } },
423- },
424- },
425- },
426- }
427- })
428- ```
429-
430- The initial state can be set in the spec:
431-
432- ``` ts
433- defineJsonRenderSpec ({
434- root: ' root' ,
435- state: { inputValue: ' ' },
436- elements: { /* ... */ },
437- })
438- ```
439-
440- ### Available Components
441-
442- | Component | Description |
443- | -----------| -------------|
444- | ` Stack ` | Flex layout container (vertical/horizontal) |
445- | ` Card ` | Container with optional title, collapsible |
446- | ` Text ` | Text display (heading, body, caption, code variants) |
447- | ` Badge ` | Status label (info, success, warning, error) |
448- | ` Button ` | Clickable button — emits ` press ` event for actions |
449- | ` Icon ` | Iconify icon by name |
450- | ` Divider ` | Visual separator with optional label |
451- | ` TextInput ` | Text input with ` $bindState ` two-way binding |
452- | ` KeyValueTable ` | Key-value pairs display |
453- | ` DataTable ` | Tabular data with columns and rows |
454- | ` CodeBlock ` | Code display with optional filename |
455- | ` Progress ` | Progress bar with label |
456- | ` Tree ` | Expandable tree view for nested objects |
457-
458- > [ !TIP]
459- > See the [ Git UI example] ( /kit/examples#git-ui ) for a complete interactive plugin using json-render with RPC actions, text input, and dynamic state.
342+ See the [ JSON Render] ( /kit/json-render ) page for the full component reference, dynamic updates, actions, state bindings, and examples.
460343
461344## Communication with Server
462345
0 commit comments