Simple Table
Generated UI previewTABLE
Users
| # | Actions | Role | Status | ||
|---|---|---|---|---|---|
| 1 | Ada Lovelace | ada@example.test | admin | active | |
| 2 | Grace Hopper | grace@example.test | editor | pending |
Row numbers, the custom status renderer, and the view/edit/delete/invite row actions are all live — edits and deletes update the in-memory rows shown here.
This tutorial renders a static table with view, edit, delete, and one custom row action.
Use this when your page already has rows in memory and you want a table with common row commands.
File structure
TXT
app/
users/
page.tsx
schemas/
users/
user.table.schema.ts
components/
vibeflui/
renderers/
status/
StatusBadge.tsx
lib/
vibeflui/
examples/
user-crud-actions.ts
registry.tsxSchema file
schemas/users/user.table.schema.ts
TS
export const userTableSchema = {
resource: "users",
title: "Users",
identity: {
key: "id",
param: "id"
},
table: {
rowNumber: {
enabled: true,
label: "#"
},
columns: [
{ key: "name", label: "Name", searchable: true, sortable: true },
{ key: "email", label: "Email", type: "email", searchable: true },
{ key: "role", label: "Role" },
{ key: "status", label: "Status", renderer: "StatusBadge" }
],
search: {
enabled: true,
placeholder: "Search users..."
},
states: {
empty: {
text: "No users yet."
}
}
},
detail: {
enabled: true,
fields: ["name", "email", "role", "status"]
},
form: {
mode: "modal",
fields: [
{ name: "name", label: "Name", required: true },
{ name: "email", label: "Email", type: "email", required: true },
{ name: "role", label: "Role" },
{
name: "status",
label: "Status",
type: "select",
options: [
{ label: "Active", value: "active" },
{ label: "Pending", value: "pending" }
]
}
]
},
actions: {
create: {
enabled: false
},
view: {
enabled: true,
label: "View",
display: "icon",
icon: "mdi:eye-outline"
},
edit: {
enabled: true,
label: "Edit",
display: "icon",
icon: "mdi:pencil-outline"
},
delete: {
enabled: true,
label: "Delete",
display: "icon",
icon: "mdi:trash-can-outline",
variant: "danger",
confirm: true
},
row: [
{
key: "invite",
label: "Invite",
tooltip: "Send invite",
ariaLabel: "Send invite",
display: "icon",
icon: "mdi:send-outline",
handler: "users.invite"
}
]
},
registry: {
renderers: ["StatusBadge"],
actionHandlers: ["users.invite"]
}
} as const;Renderer component
components/vibeflui/renderers/status/StatusBadge.tsx
TSX
export function StatusBadge({ value }: { value?: unknown }) {
const tone = value === "active" ? "text-green-700" : "text-orange-700";
return <span className={tone}>{String(value ?? "-")}</span>;
}Registry file
lib/vibeflui/registry.tsx
TSX
import { createRegistry } from "@vibeflui/core";
import { StatusBadge } from "@/components/vibeflui/renderers/status/StatusBadge";
import { inviteUser } from "@/lib/vibeflui/examples/user-crud-actions";
export const vibefluiRegistry = createRegistry({
renderers: {
StatusBadge
},
actionHandlers: {
"users.invite": inviteUser
}
});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 async function inviteUser({ row }: { row?: Record<string, unknown> }) {
return {
status: true,
code: 200,
message: `Invitation sent to ${String(row?.email ?? "user")}`
};
}
export function createUserCrudActions(setUsers: Dispatch<SetStateAction<UserRow[]>>) {
return {
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: string): 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 { userTableSchema } from "@/schemas/users/user.table.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={userTableSchema}
data={users}
onUpdate={userActions.updateUser}
onDelete={userActions.deleteUser}
/>
</FluiKitProvider>
);
}What renders
The table renders row numbers, searchable columns, a custom status renderer, view/edit/delete icon actions, and an invite icon action.