Client-side Example
Generated UI previewTABLE + FORM
Client-side users
| # | Actions | Status | ||
|---|---|---|---|---|
| 1 | Ada Lovelace | ada@example.test | active | |
| 2 | Alan Turing | alan@example.test | active | |
| 3 | Grace Hopper | grace@example.test | pending | |
| 4 | Katherine Johnson | katherine@example.test | pending | |
| 5 | Margaret Hamilton | margaret@example.test | active |
Page 1 of 2
Sorted by name, 5 rows per page, all backed by React state — refreshing the page resets it to this sample data, as described below.
This tutorial keeps data in React state and lets FluiKit render the table, form, actions, and feedback.
Use client-side mode for demos, local previews, admin tools, and prototypes. For production, keep final validation and persistence on the backend.
File structure
TXT
app/
users/
page.tsx
schemas/
users/
client-users.schema.ts
components/
vibeflui/
renderers/
status/
StatusBadge.tsx
lib/
vibeflui/
examples/
user-crud-actions.ts
registry.tsxSchema file
schemas/users/client-users.schema.ts
TS
export const clientUsersSchema = {
resource: "users",
title: "Client-side users",
mode: "static",
identity: {
key: "id",
param: "id"
},
table: {
columns: [
{ key: "name", label: "Name", searchable: true, sortable: true },
{ key: "email", label: "Email", searchable: true },
{ key: "status", label: "Status", renderer: "StatusBadge" }
],
search: {
enabled: true
},
sort: {
defaultKey: "name",
defaultDirection: "asc"
},
pagination: {
enabled: true,
pageSize: 5
}
},
form: {
mode: "modal",
fields: [
{ name: "name", label: "Name", required: true },
{ name: "email", label: "Email", type: "email", required: true },
{
name: "status",
type: "select",
defaultValue: "active",
options: [
{ label: "Active", value: "active" },
{ label: "Pending", value: "pending" }
]
}
]
},
actions: {
create: { enabled: true, label: "Create user" },
edit: { enabled: true },
delete: { enabled: true, confirm: true, variant: "danger" }
},
registry: {
renderers: ["StatusBadge"]
}
} as const;Renderer component
components/vibeflui/renderers/status/StatusBadge.tsx
TSX
export function StatusBadge({ value }: { value?: unknown }) {
const className = value === "active" ? "text-green-700" : "text-orange-700";
return <span className={className}>{String(value ?? "-")}</span>;
}Registry file
lib/vibeflui/registry.tsx
TSX
import { createRegistry } from "@vibeflui/core";
import { StatusBadge } from "@/components/vibeflui/renderers/status/StatusBadge";
export const vibefluiRegistry = createRegistry({
renderers: {
StatusBadge
}
});Reusable action helpers
lib/vibeflui/examples/user-crud-actions.ts
TS
import type { Dispatch, SetStateAction } from "react";
import type { FluiKitSubmitContext } from "@vibeflui/core";
export type UserRow = {
id: string;
name: string;
email: string;
status: string;
};
export const initialUsers: UserRow[] = [
{ id: "usr_1", name: "Ada Lovelace", email: "ada@example.test", status: "active" },
{ id: "usr_2", name: "Grace Hopper", email: "grace@example.test", status: "pending" }
];
export function createUserCrudActions(setUsers: Dispatch<SetStateAction<UserRow[]>>) {
return {
createUser: async (values: Record<string, unknown>) => {
const user = mapValuesToUser(values);
setUsers((current) => [...current, user]);
return { status: true, code: 200, message: "User created" };
},
updateUser: async (values: Record<string, unknown>, context: FluiKitSubmitContext) => {
const id = String(context.row?.id ?? "");
const nextUser = mapValuesToUser(values, id);
setUsers((current) => current.map((user) => (user.id === id ? nextUser : user)));
return { status: true, code: 200, message: "User updated" };
},
deleteUser: async (row: Record<string, unknown>) => {
const id = String(row.id ?? "");
setUsers((current) => current.filter((user) => user.id !== id));
return { status: true, code: 200, message: "User deleted" };
}
};
}
function mapValuesToUser(values: Record<string, unknown>, id = crypto.randomUUID()): UserRow {
return {
id,
name: String(values.name ?? ""),
email: String(values.email ?? ""),
status: String(values.status ?? "active")
};
}Page file
app/users/page.tsx
TSX
"use client";
import { useState } from "react";
import { FluiKit, FluiKitProvider } from "@vibeflui/core";
import { clientUsersSchema } from "@/schemas/users/client-users.schema";
import { vibefluiRegistry } from "@/lib/vibeflui/registry";
import { createUserCrudActions, initialUsers } from "@/lib/vibeflui/examples/user-crud-actions";
export default function ClientUsersPage() {
const [rows, setRows] = useState(initialUsers);
const userActions = createUserCrudActions(setRows);
return (
<FluiKitProvider registry={vibefluiRegistry}>
<FluiKit
schema={clientUsersSchema}
data={rows}
onCreate={userActions.createUser}
onUpdate={userActions.updateUser}
onDelete={userActions.deleteUser}
/>
</FluiKitProvider>
);
}Limitations
Client-side data resets on page refresh. It is excellent for local demos, but production apps should still validate, authorize, persist, and audit on the backend.