AI Token Benchmark
This benchmark compares the prompt/code size for one simple UI built in two ways:
- Manual React TSX.
- VibeFlui schema plus a small page file.
The example is intentionally small: a users table, a create form surface, and a delete action surface. The goal is not to benchmark runtime performance or full CRUD persistence. The goal is to show how much UI code an AI assistant may need to read or rewrite when making screen-level changes.
Method
The character count is the exact number of characters inside the code snippets below, excluding Markdown fences and path labels.
The token count is an estimate using:
estimated tokens = ceil(total characters / 4)Actual token counts depend on the model tokenizer, language, whitespace, and surrounding prompt.
Result
| Approach | Files | Total characters | Estimated tokens |
|---|---|---|---|
| Manual React TSX | 1 | 1,869 | 468 |
| VibeFlui schema + page | 2 | 1,131 | 283 |
In this small example, the VibeFlui version uses about 39% fewer characters in the UI code that the AI needs to inspect or regenerate.
Manual React TSX
app/users/page.tsx
"use client";
import { useState } from "react";
type User = {
id: string;
name: string;
email: string;
role: "admin" | "editor" | "viewer";
};
const initialUsers: User[] = [
{ id: "u_1", name: "Alya", email: "alya@example.com", role: "admin" },
{ id: "u_2", name: "Bima", email: "bima@example.com", role: "viewer" }
];
export default function UsersPage() {
const [users, setUsers] = useState(initialUsers);
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [role, setRole] = useState<User["role"]>("viewer");
function addUser() {
setUsers((current) => [
...current,
{ id: crypto.randomUUID(), name, email, role }
]);
setName("");
setEmail("");
setRole("viewer");
}
function deleteUser(id: string) {
setUsers((current) => current.filter((user) => user.id !== id));
}
return (
<main>
<section>
<input value={name} onChange={(event) => setName(event.target.value)} />
<input value={email} onChange={(event) => setEmail(event.target.value)} />
<select value={role} onChange={(event) => setRole(event.target.value as User["role"])}>
<option value="admin">Admin</option>
<option value="editor">Editor</option>
<option value="viewer">Viewer</option>
</select>
<button onClick={addUser}>Create user</button>
</section>
<table>
<thead>
<tr><th>Name</th><th>Email</th><th>Role</th><th /></tr>
</thead>
<tbody>
{users.map((user) => (
<tr key={user.id}>
<td>{user.name}</td>
<td>{user.email}</td>
<td>{user.role}</td>
<td><button onClick={() => deleteUser(user.id)}>Delete</button></td>
</tr>
))}
</tbody>
</table>
</main>
);
}VibeFlui schema + page
schemas/users/user.resource.schema.ts
export const userResourceSchema = {
version: "1.0.0",
resource: "users",
title: "Users",
table: {
columns: ["name", "email", "role"]
},
form: {
fields: [
{ name: "name", label: "Name", required: true },
{ name: "email", label: "Email", type: "email", required: true },
{
name: "role",
label: "Role",
type: "select",
options: [
{ label: "Admin", value: "admin" },
{ label: "Editor", value: "editor" },
{ label: "Viewer", value: "viewer" }
]
}
]
},
actions: {
create: { enabled: true, label: "Create user" },
delete: { enabled: true, label: "Delete", variant: "danger", confirm: true }
}
} as const;app/users/page.tsx
"use client";
import { FluiKit } from "@vibeflui/core";
import { userResourceSchema } from "@/schemas/users/user.resource.schema";
const users = [
{ id: "u_1", name: "Alya", email: "alya@example.com", role: "admin" },
{ id: "u_2", name: "Bima", email: "bima@example.com", role: "viewer" }
];
export default function UsersPage() {
return <FluiKit schema={userResourceSchema} data={users} />;
}This VibeFlui snippet shows the screen contract only. Without onCreate/onDelete handlers (or a wired provider/endpoint), FluiKit still shows success feedback when create or delete runs — VibeFlui only owns the FE/UI layer, so with no backend call to inspect it assumes the operation completed rather than reporting an error. To persist created rows or deleted rows in local state, pass named onCreate and onDelete handlers from a helper file, as shown in the full examples.
How to read the result
The savings come from moving repeated UI structure into schema. For AI-assisted work, that means many changes can be expressed as smaller edits:
- Add a field by changing
form.fields. - Add a column by changing
table.columns. - Add a built-in action by changing
actions. - Keep custom runtime behavior in registry files when needed.
For larger CRUD screens, the difference usually becomes more visible because manually written JSX grows with every field, column, action, state branch, and conditional UI path.