npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vibeflui/core

v1.1.0

Published

AI-friendly schema-driven UI framework for React and Next.js.

Readme

VibeFlui

Generate Forms, Tables and Actions from JSON.

AI-friendly schema-driven UI framework for React & Next.js.

Generate forms, tables, actions, and admin UIs from JSON — less code, less time, fewer tokens.

npm License: Apache 2.0 TypeScript Tailwind CSS Build Coverage Sponsor

Documentation | AI Source Map | npm

VibeFlui is the project. FluiKit is the core UI component.

<FluiKit schema={schema} />

FluiKit helps you reduce React boilerplate, build admin UI faster, write less JSX, reduce the number of tokens needed by AI coding assistants, and make vibe coding workflows more reliable. Instead of asking an AI tool to generate hundreds of lines of page-specific JSX, you ask it to generate a compact schema.

Why VibeFlui?

Modern React and Next.js CRUD screens repeat the same patterns again and again.

Traditional React CRUD often requires developers or AI assistants to write:

<Table />
<TableColumn />
<Form />
<FormField />
<Modal />
<Search />
<Pagination />
<DeleteDialog />

Then the page still needs state, row mapping, nested value access, validation, loading states, empty states, action handlers, response mapping, and integration glue.

The result is familiar:

  • A lot of boilerplate.
  • Repeated code across resources.
  • More JSX to maintain.
  • Harder AI output review.
  • More tokens consumed by AI assistants.
  • Slower iteration when schemas and backend endpoints change.

The FluiKit Approach

With FluiKit, the screen starts from schema:

<FluiKit schema={userSchema} />

Or:

const userSchema = {
  resource: "users",
  table: {
    columns: ["name", "email", "role.name", "status"]
  },
  form: {
    fields: ["name", "email", "roleId"]
  },
  actions: true
}

FluiKit turns the schema into UI automatically.

Your backend still owns business logic, authentication, authorization, database writes, final validation, workflow, audit logs, and security. VibeFlui is responsible for the UI layer.

AI Token Saving

VibeFlui is designed for the AI coding era.

Traditional prompt:

Create a user management page.

Build:
- table
- pagination
- search
- modal
- create form
- edit form
- delete dialog

AI assistants usually respond with hundreds or thousands of lines of JSX, state management, table markup, form inputs, modal code, and action handlers.

FluiKit prompt:

Generate FluiKit schema for user management.

Fields:
- name
- email
- role
- status

AI only needs to generate schema:

const schema = {
  resource: "users",
  endpoint: "/api/users",
  table: {
    columns: ["name", "email", "role.name", "status"]
  },
  form: {
    fields: ["name", "email", "roleId"]
  },
  actions: true
}

Benefits:

  • Fewer tokens.
  • Less generated output.
  • Faster AI generation.
  • More consistent UI.
  • Easier code review.
  • Easier schema validation.

FluiKit is designed to reduce AI token usage by replacing repetitive JSX generation with compact schema definitions.

Why It Matters

For vibe coders and AI-assisted teams, schema-driven UI means:

  • Less prompting.
  • Less token usage.
  • Faster iterations.
  • More consistent output.
  • Easier AI generation.
  • Smaller diffs.
  • Fewer repeated UI bugs.

Features

| Feature | Supported | | --- | --- | | Form Generator | ✅ | | Table Generator | ✅ | | CRUD Actions | ✅ | | Search | ✅ | | Filter | ✅ | | Pagination | ✅ | | Nested Mapping | ✅ | | Edit Mapping | ✅ | | Identity Keys | ✅ | | Custom Actions | ✅ | | Detail View | ✅ | | Readonly Mode | ✅ | | Layout Tabs | ✅ | | Wizard Forms | ✅ | | Form Sections | ✅ | | Multi-resource Pages | ✅ | | Registry Pattern | ✅ | | JSON Schema | ✅ | | Zod Validation | ✅ | | Tailwind | ✅ | | TailAdmin | ✅ | | TypeScript | ✅ | | AI-Friendly | ✅ | | Token Saving | ✅ | | Built-in Field Validation | ✅ | | Dashboard Cards | ✅ | | FluiKitCard | ✅ | | FluiKitTab | ✅ | | Styled Confirm Dialog | ✅ | | Plugin System | ✅ | | Optional React Query Package | ✅ | | Optional UI Adapter Packages | ✅ | | OpenAPI Generator | 🚧 Roadmap | | FluiKit Studio | 🚧 Roadmap |

Current Status

@vibeflui/core is version 1.1.0.

Core currently includes schema types, runtime validation, JSON Schema, schema normalization, FluiKit rendering components, reusable FluiKitCard and FluiKitTab primitives, TanStack Table-powered tables, form rendering with tabs, wizard steps, groups, and sections, multi-resource pages through resources[], action execution, endpoint and handler integration, registry-based customization, Tailwind defaults, TailAdmin preset foundation, message resolution, inspector utilities, plugin foundations, examples, and basic unit tests.

Optional ecosystem packages ship working renderers and adapters:

  • @vibeflui/react-query
  • @vibeflui/tailadmin
  • @vibeflui/shadcn
  • @vibeflui/react-select
  • @vibeflui/mantine
  • @vibeflui/antd
  • @vibeflui/material-ui
  • @vibeflui/editors

OpenAPI Generator and FluiKit Studio are planned roadmap items, not current runtime features.

Installation

npm install @vibeflui/core
pnpm add @vibeflui/core
yarn add @vibeflui/core

Peer dependencies:

npm install react react-dom

Core dependencies include @tanstack/react-table, @iconify/react, and zod. TanStack Query is optional through @vibeflui/react-query; it is not required by core.

CSS Setup

FluiKit's Tailwind utility classes live inside the compiled @vibeflui/* package output under node_modules, not in your own source files. Tailwind only generates CSS for classes it can actually see, so you must point it at the package output — otherwise FluiKit renders with no styling.

Tailwind v4 (current)

Add an @source directive next to your @import "tailwindcss"; line, in whichever CSS file bootstraps Tailwind for your app:

/* app/globals.css */
@import "tailwindcss";
@source "../node_modules/@vibeflui";

Add one more @source line per adapter package you install, for example:

@source "../node_modules/@vibeflui/shadcn";
@source "../node_modules/@vibeflui/tailadmin";

Adjust the relative path to match where your CSS entry file actually lives in your project.

Tailwind v3

Add the package output to the content array in tailwind.config.ts:

// tailwind.config.ts
export default {
  content: [
    "./app/**/*.{ts,tsx}",
    "./node_modules/@vibeflui/**/*.{js,ts,jsx,tsx}"
  ]
}

Quick Start

Create a schema:

import type { FluiKitConfig } from "@vibeflui/core"

export const userSchema = {
  resource: "users",
  endpoint: "/api/users",
  table: {
    columns: ["name", "email", "role.name", "status"]
  },
  form: {
    fields: ["name", "email", "roleId"]
  },
  actions: true
} satisfies FluiKitConfig

Render it:

"use client"

import { FluiKit } from "@vibeflui/core"
import { userSchema } from "./user.schema"

export function UsersPage() {
  return <FluiKit schema={userSchema} />
}

What Gets Generated?

A schema can generate:

  • Table.
  • Search.
  • Filters.
  • Pagination.
  • Row number column.
  • Detail view.
  • Form tabs, wizard steps, groups, and sections.
  • Multi-resource pages.
  • Dashboard cards.
  • Create form.
  • Edit form.
  • Delete confirmation.
  • Custom row actions.
  • Toolbar actions.
  • Bulk action foundations.
  • Loading, empty, and error states.

The Add Data button is generated in the top-right toolbar when actions.create.enabled is true. The default label is add data, and you can override it in schema.

Architecture Overview

Schema
  ↓
Validator
  ↓
Normalizer
  ↓
Registry
  ↓
Provider
  ↓
Renderer
  ↓
FluiKit UI

| Layer | Responsibility | | --- | --- | | Schema | JSON-friendly resource configuration for tables, forms, fields, actions, endpoints, messages, feedback, permissions, and theme options. | | Validator | Runtime validation through Zod and editor/tool validation through JSON Schema. | | Normalizer | Expands compact AI-friendly schema into normalized FluiKit config. | | Registry | Resolves string keys for components, renderers, validators, transformers, handlers, events, options, permissions, classes, and messages. | | Provider | Keeps backend execution outside visual components through handlers > provider > endpoint. | | Renderer | Renders forms, tables, actions, modals, feedback, detail surfaces, dashboard cards, tabs, and fields. | | FluiKit UI | The generated user interface. |

Schema Example

import type { FluiKitConfig } from "@vibeflui/core"

export const usersSchema = {
  version: "1.0.0",
  resource: "users",
  title: "Users",
  description: "User management",
  mode: "server",
  identity: {
    key: "id",
    param: "id"
  },
  endpoint: {
    list: "/api/users",
    create: "/api/users",
    update: {
      url: "/api/users/:id",
      method: "PATCH"
    },
    delete: "/api/users/:id"
  },
  table: {
    mode: "server",
    dataPath: "data.items",
    totalPath: "data.total",
    search: {
      enabled: true,
      placeholder: "Search users...",
      param: "q"
    },
    filters: [
      {
        key: "status",
        label: "Status",
        type: "select",
        options: [
          { label: "Active", value: "active" },
          { label: "Pending", value: "pending" },
          { label: "Suspended", value: "suspended" }
        ]
      }
    ],
    pagination: {
      enabled: true,
      pageSize: 10,
      pageSizeParam: "limit"
    },
    columns: [
      { key: "name", label: "Name", sortable: true, searchable: true },
      { key: "email", label: "Email" },
      { key: "role.name", label: "Role" },
      { key: "status", label: "Status", type: "custom", renderer: "StatusBadge" }
    ]
  },
  form: {
    mode: "modal",
    width: "2xl",
    grid: {
      columns: { base: 1, md: 2 },
      gap: "gap-4"
    },
    fields: [
      { name: "name", label: "Name", type: "text" },
      { name: "email", label: "Email", type: "email" },
      { name: "roleId", label: "Role", type: "select", valueFrom: "role.id" },
      { name: "password", label: "Password", type: "password", editValueMode: "empty" }
    ]
  },
  detail: {
    enabled: true,
    title: "User Detail",
    width: "3xl",
    grid: { columns: { base: 1, md: 2 } },
    fields: ["id", "name", "email", "role.name", "status"]
  },
  actions: {
    create: { enabled: true, label: "Add User" },
    edit: { enabled: true, label: "Edit" },
    delete: { enabled: true, label: "Delete", confirm: true },
    view: { enabled: true, label: "Detail" },
    row: [
      {
        key: "approve",
        label: "Approve",
        icon: "mdi:check-circle-outline",
        tooltip: "Approve user",
        display: "icon",
        endpoint: "/api/users/:id/approve",
        method: "POST",
        visibleWhen: {
          key: "status",
          operator: "equals",
          value: "pending"
        }
      }
    ]
  }
} satisfies FluiKitConfig

Table Mapping

Table columns can map directly to backend response fields:

table: {
  columns: [
    { key: "name", label: "Name" },
    { key: "email", label: "Email" },
    { key: "role.name", label: "Role" }
  ]
}

Nested paths are supported:

{
  key: "role.name"
}

Also supported:

{ key: "user.profile.fullName" }
{ key: "metadata.status" }

Backend response mapping is supported through dataPath and totalPath:

table: {
  dataPath: "data.items",
  totalPath: "data.total"
}

Wide tables can opt into a minimum table width and horizontal scrolling:

table: {
  minWidth: "960px",
  columns: [
    { key: "name", label: "Name", width: "40%" },
    { key: "sku", label: "SKU", width: 20 },
    { key: "status", label: "Status" }
  ]
}

minWidth keeps the wrapper constrained to max-width: 100% while the table scrolls horizontally. Column width accepts a percentage string or number. When any visible data column uses a percentage width, visible data columns without width automatically share the remaining percentage. Internal row number, action, and selection columns reserve fixed utility widths so percentage data columns are calculated from the remaining space.

In server mode, load failures keep the table wrapper and headers visible. The resolved load error message is rendered inside a full-width table body row.

Mutation Feedback

After create, update, delete, bulk, toolbar, or custom row actions, FluiKit can show success, error, or warning feedback from the backend response.

Recommended backend mutation response. status is the only required field; code and message improve tone and message resolution:

{ status: true, code: 201, message: "User created successfully.", data: { id: "usr_001" } }
{ status: false, code: 422, message: "Email is already registered." }
{ status: false, code: 500, message: "Unable to delete the user." }

status: true renders success feedback. status: false with a 4xx code renders warning feedback, and status: false with a 5xx code renders error feedback. The message value goes through the shared message pipeline before falling back to English defaults.

Feedback is modal by default and can be switched to inline alerts:

feedback: {
  mode: "inline",
  icons: {
    success: "mdi:check-circle-outline",
    error: "mdi:alert-circle-outline",
    warning: "mdi:alert-outline"
  }
}

Inline feedback uses Tailwind alert colors: green for success, red for error, and orange for warning. Feedback icons use Iconify keys; @iconify/react is already included by @vibeflui/core.

Edit Mapping

Use valueFrom when the edit form field name differs from the row path:

form: {
  fields: [
    {
      name: "roleId",
      label: "Role",
      type: "select",
      valueFrom: "role.id"
    }
  ]
}

Empty Edit Values

Use editValueMode: "empty" for fields that should not reuse row values during edit:

form: {
  fields: [
    {
      name: "password",
      label: "Password",
      type: "password",
      editValueMode: "empty"
    }
  ]
}

Identity Keys

Identity config controls how row actions resolve path params:

identity: {
  key: "id"
}

Custom param name:

identity: {
  key: "uuid",
  param: "userId"
}

Nested identity:

identity: {
  key: "user.id",
  param: "userId"
}

Custom Actions

actions: {
  row: [
    {
      key: "approve",
      label: "Approve",
      icon: "mdi:check-circle-outline",
      tooltip: "Approve user",
      display: "icon",
      handler: "approveUser",
      visibleWhen: {
        key: "status",
        operator: "equals",
        value: "pending"
      }
    }
  ]
}

Built-in table row actions use borderless Iconify icon-only buttons with tooltips, accessible labels, hover feedback, and | separators between visible icons. Delete keeps a red danger color. By default, enabled actions include detail, edit, and delete. Detail renders a read-only label/value surface with an underline layout, not disabled inputs.

When a row has more than three visible actions, FluiKit keeps up to two priority actions visible and groups the rest behind a lucide:list-collapse icon menu by default. The priority order is view/detail, update/edit, then delete; custom actions fill any remaining direct slot in declaration order. Disable grouping with table.actionGrouping: false.

Customize the visible row action hover effect with table.actionIcon:

table: {
  actionIcon: {
    hoverShape: "circle",
    hoverColor: "brand",
    hoverClassName: "hover:ring-2 hover:ring-brand-200 dark:hover:ring-brand-700"
  }
}

hoverShape can be "rounded" or "circle" and defaults to "rounded". hoverColor defaults to "gray" and supports gray, brand, blue, green, orange, red, success, warning, danger, and error. Use hoverClassName when the app needs a custom Tailwind hover class.

Actions with confirm render a styled FluiKit confirmation dialog (not a native browser prompt). Delete and danger-variant actions use a danger tone automatically. Customize the copy through confirm:

{
  key: "delete",
  label: "Delete",
  confirm: {
    title: "Delete user",
    description: "This permanently removes the user.",
    icon: "mdi:alert-circle-outline",
    confirmLabel: "Delete",
    cancelLabel: "Keep",
    secondConfirm: {
      title: "Delete user permanently",
      description: "This user has linked audit history. Confirm one more time before deleting.",
      confirmLabel: "Delete permanently",
      icon: "mdi:shield-alert-outline"
    }
  }
}

secondConfirm is optional. Use secondConfirm: true for the default second-step copy, or provide an object with custom text and icon.

Custom Components

Schema stores registry keys, not inline functions:

form: {
  fields: [
    {
      name: "avatar",
      label: "Avatar",
      type: "custom",
      component: "AvatarUploader"
    }
  ]
}

Runtime supplies the component:

import { FluiKit, FluiKitProvider } from "@vibeflui/core"
import { AvatarUploader } from "./AvatarUploader"

<FluiKitProvider
  components={{
    AvatarUploader
  }}
>
  <FluiKit schema={schema} />
</FluiKitProvider>

Custom table cells work the same way:

table: {
  columns: [
    {
      key: "status",
      label: "Status",
      type: "custom",
      renderer: "StatusBadge"
    }
  ]
}
<FluiKitProvider
  renderers={{
    StatusBadge
  }}
>
  <FluiKit schema={schema} />
</FluiKitProvider>

Registry Pattern

VibeFlui keeps schemas JSON-serializable.

Use this:

{
  renderer: "StatusBadge"
}

Not this:

{
  renderer: (row) => <StatusBadge status={row.status} />
}

Registry string keys are important because they are:

  • JSON serializable.
  • AI friendly.
  • Easier to validate.
  • Easier to export.
  • Safer to store.
  • Easier to share between tools.
  • Ready for visual builders and generators.

The runtime registry supports:

  • Components.
  • Renderers.
  • Field adapters.
  • Validators.
  • Transformers.
  • Action handlers.
  • Permission resolvers.
  • Option loaders.
  • Hooks.
  • Events.
  • Class name resolvers.
  • Message resolvers.
  • Computed resolvers.

Computed Fields

Computed fields use a computed registry key and can declare dependencies with computedFrom:

form: {
  fields: [
    { name: "title", label: "Title", type: "text" },
    { name: "slug", label: "Slug", type: "text", computed: "slugFromTitle", computedFrom: "title", readonly: true }
  ]
}
<FluiKitProvider
  computedResolvers={{
    slugFromTitle: ({ values }) => String(values.title ?? "").toLowerCase().replace(/\s+/g, "-")
  }}
>
  <FluiKit schema={schema} />
</FluiKitProvider>

computedFrom may be a string or string array. FluiKit uses it as a dependency list: the resolver runs initially, reruns when one of those dependencies changes, and preserves the previous computed value when unrelated fields change.

Message Resolvers

messages.resolver points to a registry message resolver. Resolvers receive the message key plus runtime context when available:

<FluiKitProvider
  messageResolvers={{
    resolveAdminMessage: (key, context) => {
      if (key === "confirmDeleteDescription") return `Delete ${context.row?.name}?`
      return undefined
    }
  }}
>
  <FluiKit schema={schema} />
</FluiKitProvider>

Depending on the call site, context can include schema, row, rows, values, action, mode, query, and value. Backend messages and schema defaults still follow messages.sourcePriority.

Backend Integration

FluiKit is backend-agnostic. It does not own your persistence, authorization, final validation, workflow, or audit log.

Execution priority:

handlers > provider > endpoint

Endpoint Mode

endpoint: {
  list: "/api/users",
  create: "/api/users",
  update: {
    url: "/api/users/:id",
    method: "PATCH"
  },
  delete: {
    url: "/api/users/:id",
    method: "DELETE"
  }
}

Handler Mode

Schema uses handler keys:

handlers: {
  create: "createUser",
  update: "updateUser",
  delete: "deleteUser"
}

Runtime provides the functions:

<FluiKitProvider
  handlers={{
    createUser: async ({ values }) => api.users.create(values),
    updateUser: async ({ identityValue, values }) => api.users.update(identityValue, values),
    deleteUser: async ({ identityValue }) => api.users.delete(identityValue)
  }}
>
  <FluiKit schema={schema} />
</FluiKitProvider>

Provider Mode

<FluiKitProvider
  providers={{
    adminApi: {
      list: async ({ query }) => api.users.list(query),
      create: async ({ values }) => api.users.create(values),
      update: async ({ identityValue, values }) => api.users.update(identityValue, values),
      delete: async ({ identityValue }) => api.users.delete(identityValue)
    }
  }}
>
  <FluiKit schema={schema} />
</FluiKitProvider>
provider: {
  type: "custom",
  name: "adminApi"
}

Supported Field Types

| Field Type | Status | | --- | --- | | text | ✅ Native | | email | ✅ Native | | password | ✅ Native | | number | ✅ Native | | textarea | ✅ Native | | select | ✅ Native | | multiselect | ✅ Native | | checkbox | ✅ Native checkbox | | checkbox-group | ✅ Native multiple selection | | radio | ✅ Native single selection | | radio-group | ✅ Native single selection | | switch | ✅ Native sliding toggle | | date | ✅ Native | | datetime | ✅ Native | | time | ✅ Native | | file | ✅ Native | | image | ✅ Native file input with image accept | | hidden | ✅ Native | | color | ✅ Native | | range | ✅ Native | | json | ✅ Monospace textarea (or Editor.js block editor via @vibeflui/editors, adapter: "editorjs" — full tool set via buildEditorJsTools, see the editors package README) | | code | ✅ Monospace textarea (CodeMirror via @vibeflui/editors) | | markdown | ✅ Textarea (live editor via @vibeflui/editors) | | richtext | ✅ Textarea (TipTap WYSIWYG via @vibeflui/editors) | | tags | ✅ Comma-separated input | | combobox | ✅ Native datalist | | autocomplete | ✅ Native datalist | | custom | ✅ Registry component |

Rich third-party controls can be supplied through fieldAdapters.

Use switch for toggle, on-off, and slide controls. Use checkbox for a normal checkbox, checkbox-group for multiple selections, and radio or radio-group for single selection from options. checkbox-toggle is not an official field type.

Field Validation

Common validation rules are built in and run automatically on submit — no custom validator required:

form: {
  fields: [
    { name: "name", label: "Name", type: "text", required: true, minLength: 2, maxLength: 80 },
    { name: "email", label: "Email", type: "email", required: true },
    { name: "age", label: "Age", type: "number", min: 18, max: 99 },
    { name: "startDate", label: "Start date", type: "date", min: "2024-01-01", max: "2024-12-31" },
    { name: "code", label: "Code", type: "text", pattern: "^[A-Z]{3}$", validationMessage: "Use 3 uppercase letters." }
  ]
}

Supported rules: required, minLength, maxLength, min, max, pattern, plus automatic format checks for email fields. min/max work with numeric fields and date-like date/datetime fields; date bounds can be ISO-style strings such as "2024-01-01" or "2024-01-01T09:00". Required fields render a * marker in the label. Messages use English defaults with {label}/{min}/{max} interpolation and can be overridden per field with validationMessage, or globally through messages.defaults.

Built-in rules run first; a registry validator key still runs afterwards for advanced or async checks. The standalone helper is also exported:

import { validateFieldValue } from "@vibeflui/core"

const error = validateFieldValue({ field, value })

Theme System

Default styling is Tailwind CSS.

Supported today:

  • Tailwind default preset.
  • TailAdmin preset foundation.
  • Dark-mode-aware default classes for tables, forms, dialogs, dashboard cards, and native inputs.
  • Custom Tailwind class slots.
  • Tailwind-aware cn() helper.

Package foundations exist for:

  • Shadcn.
  • Mantine.
  • Ant Design.
  • Material UI.
  • React Select field adapter.
  • Editors field adapters for TipTap, CodeMirror, react-md-editor, and Editor.js.

React Select and editor adapters include dark-mode-aware runtime styling. Native select/date inputs use Tailwind dark classes plus color-scheme hints so browser controls can follow the active theme.

Example:

theme: {
  preset: "default",
  engine: "tailwind",
  classNameMergeStrategy: "merge",
  classNames: {
    root: "space-y-6",
    tableWrapper: "rounded-lg border border-gray-200 bg-white",
    actionButton: "whitespace-nowrap"
  }
}

TailAdmin:

theme: {
  preset: "tailadmin",
  engine: "tailwind"
}

Custom adapter direction:

adapter: {
  ui: "custom",
  form: "custom",
  table: "tailwind",
  modal: "custom"
}

See the CSS Setup section above for the Tailwind v4 @source directive (or the Tailwind v3 content array) required so Tailwind generates CSS for these classes.

TypeScript

VibeFlui exports TypeScript types for schema-driven development:

import type {
  FluiKitActionConfig,
  FluiKitConfig,
  FluiKitDataProviderConfig,
  FluiKitDebugConfig,
  FluiKitFieldConfig,
  FluiKitFormConfig,
  FluiKitIdentityConfig,
  FluiKitLayoutConfig,
  FluiKitMessagesConfig,
  FluiKitComponentRegistry,
  FluiKitRegistryConfig,
  FluiKitTableConfig,
  FluiKitThemeConfig
} from "@vibeflui/core"

Use satisfies FluiKitConfig for great autocomplete while preserving literal schema types:

export const schema = {
  resource: "products",
  endpoint: "/api/products",
  table: {
    columns: ["name", "price", "status"]
  },
  form: {
    fields: ["name", "price", "status"]
  }
} satisfies FluiKitConfig

JSON Schema

VibeFlui ships a JSON Schema:

schemas/fluikit.schema.json

It is used for:

  • Validation.
  • VS Code autocomplete.
  • AI schema generation.
  • Documentation.
  • Future visual builders.
  • Future OpenAPI generation workflows.

Package export:

import schemaJson from "@vibeflui/core/schemas/fluikit.schema.json"

Zod Validation

Runtime validation is available through Zod-backed utilities:

import { validateSchema } from "@vibeflui/core"

const result = validateSchema(schema)

if (!result.valid) {
  console.error(result.errors)
}

The Zod schema is also exported:

import { fluiKitConfigSchema } from "@vibeflui/core"

const parsed = fluiKitConfigSchema.parse(schema)

Normalizer

Compact AI-friendly schemas are normalized into full FluiKit config:

import { normalizeSchema } from "@vibeflui/core"

const normalized = normalizeSchema({
  resource: "users",
  endpoint: "/api/users",
  table: {
    columns: ["name", "email", "role.name"]
  },
  form: {
    fields: ["name", "email"]
  }
})

Generate Resource Helper

import { generateResource } from "@vibeflui/core"

const usersSchema = generateResource({
  resource: "users",
  endpoint: "/api/users"
})

Dashboard Cards

Render KPI/stat cards above the table from schema. Each card resolves its value from the list response via valueFrom, or fetches its own endpoint:

const schema = {
  resource: "orders",
  endpoint: "/api/orders",
  dashboard: {
    enabled: true,
    title: "Overview",
    cards: [
      { key: "total", label: "Total Orders", valueFrom: "stats.total", format: "number", icon: "mdi:cart" },
      { key: "revenue", label: "Revenue", valueFrom: "stats.revenue", format: "currency" },
      { key: "growth", label: "Growth", valueFrom: "stats.growth", format: "percent" },
      { key: "pending", label: "Pending", endpoint: "/api/orders/pending-count" }
    ]
  },
  table: { columns: ["orderNumber", "status", "total"] }
} satisfies FluiKitConfig

Formats: number, currency, percent, text, custom. Supply card.renderer (or dashboard.renderer) registry keys for fully custom cards. <FluiKit /> renders the dashboard automatically when dashboard is present, including dashboard-only schemas and schemas that also define resources[]. Card endpoints use the configured custom provider when available, then fall back to the REST endpoint helper. <FluiKitDashboard /> can also be used standalone.

For landing pages, feature grids, and custom dashboard surfaces, use <FluiKitCard /> directly with Iconify icons:

import { FluiKitCard } from "@vibeflui/core"

<FluiKitCard
  icon="mdi:rocket-launch-outline"
  title="Developer First"
  description="Type-safe, composable, and easy to extend."
/>

Layout, Sections, and Resources

FluiKit supports schema-driven form layouts and multi-resource pages today.

Use layout.type: "tabs" to split a form into tabs:

const schema = {
  resource: "users",
  layout: {
    type: "tabs",
    tabs: [
      { key: "profile", title: "Profile", fields: ["name", "email"] },
      { key: "access", title: "Access", fields: ["role", "active"] }
    ]
  },
  form: {
    fields: ["name", "email", "role", "active"]
  }
} satisfies FluiKitConfig

Use layout.type: "wizard" for step-by-step forms:

layout: {
  type: "wizard",
  steps: [
    { key: "basic", title: "Basic", fields: ["name", "email"] },
    { key: "security", title: "Security", fields: ["password", "role"] }
  ]
}

Use form.sections when you want grouped fieldsets without changing the whole page layout:

form: {
  sections: [
    { key: "basic", title: "Basic Information", fields: ["name", "email"] },
    { key: "settings", title: "Settings", fields: ["role", "active"] }
  ],
  fields: ["name", "email", "role", "active"]
}

Use resources[] for a multi-resource page:

const adminSchema = {
  resource: "admin",
  title: "Admin Console",
  layout: { type: "tabs" },
  dashboard: {
    enabled: true,
    cards: [{ key: "usersTotal", label: "Users", valueFrom: "meta.usersTotal" }]
  },
  table: { columns: ["name", "email", "status"] },
  form: { fields: ["name", "email", "status"] },
  resources: [rolesSchema, permissionsSchema]
} satisfies FluiKitConfig

When resources[] is present, layout.type: "tabs" renders the root table/form as the first tab when the root has table/form UI, then appends child resources. layout.type: "sections" or "page" stacks the root resource and child resources as sections. Dashboard cards render above either layout.

layout.className applies to the root layout container regardless of layout.type:

layout: { className: "space-y-8" }

For a single-resource screen, layout.sections or layout.tabs entries marked table: true or form: true place the resource table or an always-visible create form into that section/tab:

layout: {
  type: "sections",
  sections: [
    { key: "list", title: "All Users", table: true },
    { key: "add", title: "Add User", form: true }
  ]
}

Entries that only declare fields remain form layout metadata. If table.enabled is false but create action and form rendering are enabled, <FluiKit /> renders a standalone create trigger so modal, drawer, page, or inline form surfaces remain reachable.

For custom pages outside schema rendering, use the exported <FluiKitTab /> primitive:

import { FluiKitTab } from "@vibeflui/core"

<FluiKitTab
  defaultValue="view"
  items={[
    { key: "view", label: "View", icon: "mdi:view-dashboard-outline", content: <Preview /> },
    { key: "code", label: "Code", icon: "mdi:code-json", content: <Code /> }
  ]}
/>

Readonly Mode

Use readonly mode when a resource should only display data:

const schema = {
  resource: "auditLogs",
  mode: "readonly",
  dataMode: "server",
  endpoint: "/api/audit-logs",
  table: {
    columns: ["actor.name", "event", "metadata.status", "createdAt"]
  },
  actions: true
}

Readonly mode hides generated action UI, hides Add Data, disables bulk selection, and no-ops create/update/delete/custom mutation execution even if actions are forced.

Viewing isn't a mutation, so it's the one exception: set actions.view.enabled: true to keep the view/detail action available under readonly mode while create/edit/delete stay forced off.

const schema = {
  resource: "auditLogs",
  mode: "readonly",
  detail: { enabled: true, fields: ["actor.name", "event", "metadata.status"] },
  actions: {
    view: { enabled: true }
  },
  table: {
    columns: ["actor.name", "event", "metadata.status", "createdAt"]
  }
}

Plugin System

Core includes plugin foundations through:

import { createFluiKitPlugin } from "@vibeflui/core"

Plugins can bundle registry values, providers, messages, theme presets, adapter metadata, and inspector panel metadata.

const plugin = createFluiKitPlugin({
  name: "@vibeflui/plugin-example",
  version: "1.0.0",
  components: { AvatarUploader },
  renderers: { StatusBadge },
  providers: { adminApi },
  messages: {
    optionLoadError: "Unable to load options."
  }
})
<FluiKitProvider plugins={[plugin]}>
  <FluiKit schema={schema} />
</FluiKitProvider>

Current official package foundations:

| Package | Purpose | | --- | --- | | @vibeflui/react-query | TanStack Query-compatible provider plugin (cache, invalidation, mutation lifecycle). | | @vibeflui/tailadmin | TailAdmin theme preset + StatusBadge renderer and Switch field adapter. | | @vibeflui/shadcn | Shadcn theme preset + StatusBadge renderer and Switch field adapter. | | @vibeflui/react-select | React Select field adapter (react-select) for select/multiselect/tags. | | @vibeflui/mantine | Mantine field adapter wrapping Mantine inputs, select, switch, radio, and checkbox controls. | | @vibeflui/antd | Ant Design field adapter wrapping antd inputs, select, switch, radio, and checkbox controls. | | @vibeflui/material-ui | Material UI field adapter wrapping MUI TextField, select, switch, radio, and checkbox controls. | | @vibeflui/editors | Rich-text (TipTap), code (CodeMirror), markdown (react-md-editor), and block editor (Editor.js) field adapters. |

Each adapter package depends on its UI library through optional peer dependencies, so installing @vibeflui/core never pulls in TanStack Query, React Select, Mantine, Ant Design, or Material UI. Register an adapter, then reference it from schema:

import { FluiKit, FluiKitProvider } from "@vibeflui/core"
import { mantinePlugin } from "@vibeflui/mantine"

<FluiKitProvider plugins={[mantinePlugin]}>
  <FluiKit schema={schema} />
</FluiKitProvider>
form: {
  fields: [
    { name: "role", label: "Role", type: "select", adapter: "mantine", options: [/* ... */] }
  ]
}

Roadmap integrations include deeper plugin packages, OpenAPI generation, more UI adapter packs, and FluiKit Studio.

AI Coding Examples

Prompt:

Generate FluiKit schema for User CRUD.

Fields:
- name
- email
- role
- status

Use /api/users.

Prompt:

Generate FluiKit schema for Product CRUD.

Fields:
- name
- sku
- price
- stock
- category
- status

Use nested category.name in table and categoryId in form.

Prompt:

Generate FluiKit schema from this OpenAPI endpoint shape.

GET /api/orders returns data.items and data.total.
Identity key is order.id.
Table columns: orderNumber, customer.name, status, total.
Form fields: customerId, status, notes.

Instead of generating a full JSX page, AI generates a smaller schema that FluiKit renders consistently.

Token Efficiency

Traditional AI workflow:

Prompt
  ↓
Generate JSX
  ↓
Generate Table
  ↓
Generate Form
  ↓
Generate Actions
  ↓
Review a large diff

FluiKit workflow:

Prompt
  ↓
Generate Schema
  ↓
FluiKit
  ↓
UI

Less tokens. Less code. Faster iterations. More consistent results.

Next.js Usage

FluiKit components are client components. In the Next.js App Router, keep server-only work in Server Components, server modules, route handlers, or backend APIs, then render FluiKit from a small client component.

// app/users/UsersClient.tsx
"use client"

import { FluiKit } from "@vibeflui/core"
import { usersSchema } from "./users.schema"

export function UsersClient({ response }: { response: unknown }) {
  return <FluiKit schema={usersSchema} response={response} />
}

Repository examples include:

examples/nextjs-basic
examples/nextjs-server-table
examples/nextjs-tailadmin
examples/nextjs-custom-handler
examples/nextjs-full-feature
examples/nextjs-ecosystem
examples/nextjs-custom-theme
examples/ecosystem-packages

Release Checks

The repository includes local checks for the current package contract:

npm run typecheck
npm run typecheck:ecosystem
npm run test
npm run build:ecosystem
npm pack --dry-run

Current unit coverage is a basic Node test suite for schema generation, validation, normalization, inspector debug output, nested path utilities, form value mapping, endpoint/action/message resolution, endpoint execution, plugin conflicts, and exported examples.

For AI Assistants

llms.txt is a single, self-contained schema reference written for LLMs. Paste it into Cursor, Claude, ChatGPT, or any AI coding tool so the assistant generates valid FluiKit schemas instead of hand-written CRUD JSX. It is also shipped inside the npm package at @vibeflui/core/llms.txt.

For web-based AI retrieval, start with the public AI Source Map:

Documentation

Contributing

Contributions are welcome.

Good first contributions include:

  • Bug reports.
  • Documentation improvements.
  • Schema examples.
  • Field adapter examples.
  • Theme preset improvements.
  • Plugin package experiments.
  • Pull requests for small, focused fixes.

Before opening a pull request, run:

npm run check

For larger changes, please explain how the change preserves the core principles:

  • Schema-first.
  • AI-first.
  • Backend-agnostic.
  • Type-safe.
  • Registry-based customization.
  • Tailwind-first.
  • Minimal JSX.

❤️ Support VibeFlui

If VibeFlui saves you development time or AI tokens, consider supporting the project.

Your sponsorship helps fund:

  • New features.
  • Documentation.
  • Bug fixes.
  • Plugin ecosystem.
  • OpenAPI Generator.
  • FluiKit Studio.
  • Community support.

Support links:

Crypto — BNB Smart Chain (BEP20), one address for BTCB / ETH / SOL / USDT / USDC (BEP20-wrapped/bridged tokens):

0x353BcD84747B800283011c77EB3e1a6a4ED732A6

⚠️ Only send by explicitly selecting the BEP20 / BNB Smart Chain network in your wallet or exchange — native BTC, native ETH-mainnet, or native SOL sent directly to this address will be permanently lost. See SPONSORSHIP.md for details.

Contributors

Thanks to everyone who reports issues, writes examples, improves docs, builds adapters, and helps shape VibeFlui.

License

Apache License 2.0. See LICENSE.