VVibeFlui

Client-side Example

Generated UI previewTABLE + FORM

Client-side users

#ActionsEmailStatus
1
Ada Lovelaceada@example.testactive
2
Alan Turingalan@example.testactive
3
Grace Hoppergrace@example.testpending
4
Katherine Johnsonkatherine@example.testpending
5
Margaret Hamiltonmargaret@example.testactive
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.tsx

Schema 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.