VVibeFlui

Simple Form and Table

Generated UI previewFORM + TABLE

Users

#ActionsEmailRoleStatus
1
Ada Lovelaceada@example.testadminactive
2
Grace Hoppergrace@example.testeditorpending
Page 1 of 1

Create, edit, and delete all run against local React state here — try adding a user with the modal form, then editing or deleting the row it creates.

This tutorial combines one table, one create/edit form, and delete behavior with local client-side data.

Use this for local demos, admin prototypes, or offline examples where the data can live in component state.

Lifecycle

  1. The table displays local rows.
  2. The create action opens the form.
  3. The edit action opens the same form with selected row values.
  4. Save creates or updates local state.
  5. Delete confirms, then removes the row.

File structure

TXT
app/
  users/
    page.tsx
schemas/
  users/
    user.resource.schema.ts
components/
  vibeflui/
    renderers/
      status/
        StatusBadge.tsx
lib/
  vibeflui/
    examples/
      user-crud-actions.ts
    registry.tsx

Schema file

schemas/users/user.resource.schema.ts

TS
export const userResourceSchema = {
  resource: "users",
  title: "Users",
  identity: {
    key: "id",
    param: "id"
  },
  table: {
    columns: [
      { key: "name", label: "Name", searchable: true, sortable: true },
      { key: "email", label: "Email", searchable: true },
      { key: "role", label: "Role" },
      { key: "status", label: "Status", renderer: "StatusBadge" }
    ],
    search: {
      enabled: true
    },
    pagination: {
      enabled: true,
      pageSize: 10
    }
  },
  form: {
    mode: "modal",
    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" }
        ]
      },
      {
        name: "status",
        label: "Status",
        type: "select",
        defaultValue: "active",
        options: [
          { label: "Active", value: "active" },
          { label: "Pending", value: "pending" },
          { label: "Suspended", value: "suspended" }
        ]
      }
    ]
  },
  actions: {
    create: {
      enabled: true,
      label: "Create user"
    },
    edit: {
      enabled: true,
      label: "Edit"
    },
    delete: {
      enabled: true,
      label: "Delete",
      variant: "danger",
      confirm: true
    }
  },
  registry: {
    renderers: ["StatusBadge"]
  }
} as const;

Renderer component

components/vibeflui/renderers/status/StatusBadge.tsx

TSX
export function StatusBadge({ value }: { value?: unknown }) {
  return <span>{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;
  role: string;
  status: string;
};

export const initialUsers: UserRow[] = [
  { id: "usr_1", name: "Ada Lovelace", email: "ada@example.test", role: "admin", status: "active" },
  { id: "usr_2", name: "Grace Hopper", email: "grace@example.test", role: "editor", 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 nextValues = mapValuesToUser(values, id);
      setUsers((current) =>
        current.map((user) => (user.id === id ? nextValues : 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 ?? ""),
    role: String(values.role ?? "viewer"),
    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 { userResourceSchema } from "@/schemas/users/user.resource.schema";
import { vibefluiRegistry } from "@/lib/vibeflui/registry";
import { createUserCrudActions, initialUsers } from "@/lib/vibeflui/examples/user-crud-actions";

export default function UsersPage() {
  const [users, setUsers] = useState(initialUsers);
  const userActions = createUserCrudActions(setUsers);

  return (
    <FluiKitProvider registry={vibefluiRegistry}>
      <FluiKit
        schema={userResourceSchema}
        data={users}
        onCreate={userActions.createUser}
        onUpdate={userActions.updateUser}
        onDelete={userActions.deleteUser}
      />
    </FluiKitProvider>
  );
}

What renders

The page renders a local CRUD screen with a searchable table, modal create/edit form, custom status renderer, and delete confirmation. The table data updates from React state, while the schema stays declarative and the mutation functions stay in reusable helpers.