Skip to content

puffinsoft/syntux

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

133 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

syntux is the generative UI library for the web.

You give it a value and it designs the UI to display it.


demo.mp4

syntux is designed to display data. Do not let this fact intimidate you - that is simply a testament to how token-efficient it is.

For instance, if you provide an array value with 10,000 items, it will cost you the same as one with 10 items. That is how efficient syntux is.

Features

  • Streamable - display UI as you generate.
  • 🎨 Custom Components - use your own React components.
  • 💾 Cacheable - reuse generated UIs with new values.
  • 🔄 Reactive - update the UI programmatically.

How does it work? syntux generates a JSON-DSL to represent the UI, known as the React Interface Schema. The specifics are in the FAQ below.


API

syntux is built for React and Next.js.

One component is all you need:

const valueToDisplay = {
    "username": "John",
    "email": "[email protected]",
    "age": 22
}

<GeneratedUI
    model={anthropic('claude-sonnet-4-5')}
    value={valueToDisplay}
    hint="UI should look like..."   
/>

syntux takes the value into consideration and designs a UI to best display it. value can be anything; an object, array or primitive.

Tip

If you are passing in a large array as a value, or an object with untrusted input, use the skeletonize property. See the explanation.

Installation

In the root of your project:

$ npx getsyntux@latest

This will automatically install the required components in the lib/getsyntux folder.

We use the Vercel AI SDK to provide support for all LLM providers. To install the model providers:

$ npm i ai
$ npm i @ai-sdk/anthropic (if you're using Claude)

Examples

Basic Example

Generate a simple UI with a hint:

import { GeneratedUI } from "@/lib/getsyntux/GeneratedUI";
import { createAnthropic } from "@ai-sdk/anthropic";

/* this example uses Claude, but all models are supported! */
const anthropic = createAnthropic({ apiKey: ... })

export default function Home(){
    const valueToDisplay = { ... };
    return <GeneratedUI model={anthropic("claude-sonnet-4-5")} value={valueToDisplay} hint="UI should look like..." />
}

Caching

Cache generated UI based on a user ID:

const cache: Map<number, string> = new Map(); // user id → UI schema

export default function Home() {
  const userID = 10;
  const valueToDisplay = { ... };

  return (
    <GeneratedUI
      cached={cache.get(userID)}
      onGenerate={(result) => {
        cache.set(userID, result);
      }}

      model={anthropic("claude-sonnet-4-5")}
      value={valueToDisplay}
    />
  );
}

Custom components

Use your own components, or someone else's (a library):

import { CustomOne, CustomTwo } from "@/my_components";

export default function Home() {
  const valueToDisplay = { ... };

  return (
    <GeneratedUI
      components={[{
          name: "Button",
          props: "{ color: string, text: string }",
          component: CustomOne,
        }, {
          name: "Input",
          props: "{ initial: string, disabled: boolean }",
          component: CustomTwo,
          context: "Creates an input field with an (initial) value. Can be disabled.",
        }]}

        model={anthropic("claude-sonnet-4-5")}
        value={valueToDisplay}
    />
  );
}

Note: the components array above can be generated automatically with npx getsyntux generate-defs <component.tsx>. See the documentation.

Make sure components are marked with "use client".

Update value (static)

Use the useSyntux hook to retrieve and update the value inside a custom component:

"use client";

export default function CustomComponent() {
  const { value, setValue } = useSyntux();

  return (
    <button
      onClick={() => {
        const newArr = [...value];
        newArr.push({ ... });
        setValue(newArr);
      }}
    >
      Add value
    </button>
  );
}

Regenerate UI (dynamic)

Provide the rerender prop a server action.

The server action is already provided. However, you will need to configure it (i.e., add an API key etc,.)

import { rerenderAction } from "@/lib/getsyntux/RerenderHandler"; // preinstalled

<GeneratedUI 
    model={anthropic("claude-sonnet-4-5")}
    value={valueToDisplay}
    rerender={rerenderAction}
/>

Inside a custom component, use the useSyntux hook and provide a hint to regenerate the UI:

"use client";

export default function CustomComponent() {
  const { value, setValue } = useSyntux();

  return (
    <button
      onClick={() => {
        setValue(value, {
            regenerate: true, // if false, treated as static
            hint: "Change the style to be more..."
        })
      }}
    >
      Update UI!
    </button>
  );
}

The new user interface will be streamed.

Customize animation

By default, new elements fade in from below when mounted.

This motion cannot yet be customized. However, the duration and offset can, using the animate property:

<GeneratedUI
    model={anthropic("claude-sonnet-4-5")}
    value={valueToDisplay}
    animate={{
        offset: 10, // pixels
        duration: 100 // ms
    }}
/>

In order to disable the animation, set offset to 0 or duration to 0.


FAQ

How expensive is generation?

syntux is highly optimized to save tokens. See here for a cost estimation table and an explanation.

How does generation work? (Does it generate source code?)

Generated UIs must be secure, reusable and cacheable.

As such, syntux does not generate source code. It generates a schema for the UI, known as a "React Interface Schema" (RIS). See the question below to get a better understanding.

This schema is tailored to the value that you provide. It is then hydrated by syntux and rendered.

How does caching work?

The generated UI is determined by the React Interface Schema (see the above question).

Thus, if the same schema is provided, the same UI will be generated.

For simplicity, the schema is simply a string. It is up to you how you wish to store it; in memory, in a file, in a database etc,.

Use the onGenerate and cached properties to retrieve/provide a cached schema respectively.

What about state? Can state be generated?

Generating state is an anti-pattern and leads to poorly performing, insecure applications.

If you need to handle state, wrap non-stateful components in stateful ones, then pass those as custom components to syntux.

What does the React Interface Schema look like?

It's a list of JSON objects, each delimited by a newline. Each object contains information about the element/component, props, and an id and parentId.

The RIS does not hardcode values. It binds to properties of the value and has built-in iterators (with the type field), making it reusable and token-efficient (for arrays).

Originally (pre-v0.2.x), the schema was a deep JSON tree. However, post-v0.2.x it was switched to a flat JSON list, as this allows for the UI to be built progressively (streamed).

As such, the id and parentId fields are used to construct the tree as-you-go.

Below is an example:

{"id":"loop_1", "parentId":"root", "type":"__ForEach__", "props":{"source":"authors"}}
{"id":"card_1", "parentId":"loop_1", "type":"div", "props":{"className":"card"}, "content": {"$bind": "$item.name"}}

To get a better understanding, or to implement your own parser, see the spec.


syntux is open source software, licensed under the MIT license.