VVibeFlui

FluiKitField

FluiKitField renders one normalized field.

It is used by FluiKitForm, but can also be used in custom forms or local demos.

Props

TS
type FluiKitFieldProps = {
  schema?: NormalizedFluiKitConfig;
  field: NormalizedFluiKitFieldConfig;
  value: unknown;
  values?: Record<string, unknown>;
  mode?: "create" | "edit" | "view";
  error?: string;
  className?: string;
  onChange?: (value: unknown) => void;
};

Minimal example

schemas/users/users.schema.ts

TS
export const usersSchema = {
  version: "1.0.0",
  resource: "users",
  form: {
    fields: [
      { name: "email", label: "Email", type: "email", required: true }
    ]
  }
} as const;

app/users/email-field-preview.tsx

TSX
"use client";

import { useState } from "react";
import { FluiKitField, normalizeSchema } from "@vibeflui/core";
import { usersSchema } from "@/schemas/users/users.schema";

const schema = normalizeSchema(usersSchema);

export function EmailFieldPreview() {
  const [value, setValue] = useState("");

  return (
    <FluiKitField
      schema={schema}
      field={schema.form.fields[0]}
      value={value}
      values={{ email: value }}
      onChange={(next) => setValue(String(next ?? ""))}
    />
  );
}

Render resolution

FluiKitField chooses the renderer in this order:

  1. field.adapter from registry.fieldAdapters
  2. field.component from registry.components
  3. missing custom component fallback when field.type === "custom" and no matching component is registered
  4. hidden input when field.type === "hidden"
  5. native field renderer

Native field behavior

The native renderer supports:

  • text-like inputs: text, email, password
  • numeric inputs: number, range
  • date/time inputs: date, datetime, time
  • textarea-like inputs: textarea, json, code, markdown, richtext
  • option inputs: select, multiselect, radio, radio-group, checkbox-group
  • boolean inputs: checkbox, switch
  • file inputs: file, image
  • utility inputs: color, tags, combobox, autocomplete

switch renders toggle/on-off/slide UI. checkbox remains a normal checkbox. For option lists, use checkbox-group for multiple selections and radio or radio-group for single selection.

json, code, markdown, and richtext fall back to textarea behavior unless a plugin or adapter replaces them.

Dynamic options

Options are loaded only when the field has optionLoader or optionsEndpoint.

Static field.options are used as-is. This prevents heavy select adapters from reloading on every keystroke.

schemas/users/users.schema.ts

TS
export const usersSchema = {
  version: "1.0.0",
  resource: "users",
  form: {
    fields: [
      { name: "roleId", label: "Role", type: "select", optionLoader: "users.roles" }
    ]
  },
  registry: {
    optionLoaders: ["users.roles"]
  }
} as const;

lib/vibeflui/registry.ts

TS
import { createRegistry } from "@vibeflui/core";

export const registry = createRegistry({
  optionLoaders: {
    "users.roles": async () => [
      { label: "Admin", value: "admin" },
      { label: "Editor", value: "editor" }
    ]
  }
});

app/users/role-field-preview.tsx

TSX
"use client";

import { useState } from "react";
import { FluiKitField, FluiKitProvider, normalizeSchema } from "@vibeflui/core";
import { usersSchema } from "@/schemas/users/users.schema";
import { registry } from "@/lib/vibeflui/registry";

const schema = normalizeSchema(usersSchema);

function RoleFieldPreview() {
  const [value, setValue] = useState<unknown>();

  return (
    <FluiKitField
      schema={schema}
      field={schema.form.fields[0]}
      value={value}
      values={{ roleId: value }}
      onChange={setValue}
    />
  );
}

export function RoleFieldPreviewPage() {
  return (
    <FluiKitProvider registry={registry}>
      <RoleFieldPreview />
    </FluiKitProvider>
  );
}

This example needs FluiKitProvider because optionLoader resolves from the runtime registry.

Read-only behavior

The field is read-only or disabled when:

  • mode is view
  • field.readonly is true
  • field.disabled is true

Visibility is resolved through isFieldVisible(field, values, mode).

Theme slots

When schema is passed, FluiKitField resolves:

  • fieldWrapper
  • label
  • input
  • error

Without schema, it uses built-in fallback class names.