VVibeFlui

Custom Fields and Adapters

VibeFlui supports two extension paths for field rendering:

  • field.component from registry.components
  • field.adapter from registry.fieldAdapters

Field adapter

Adapters are recommended when the field still behaves like a form control.

components/vibeflui/fields/RoleSelectField.tsx

TSX
import type { FluiKitRuntimeComponentProps } from "@vibeflui/core";

export function RoleSelectField({ value, options = [], onChange }: FluiKitRuntimeComponentProps) {
  return (
    <select value={String(value ?? "")} onChange={(event) => onChange?.(event.target.value)}>
      <option value="">Select role</option>
      {options.map((option) => {
        const item = option as { label?: string; value?: string | number | boolean };

        return (
          <option key={String(item.value)} value={String(item.value)}>
            {String(item.label ?? item.value ?? "")}
          </option>
        );
      })}
    </select>
  );
}

lib/vibeflui/registry.tsx

TSX
import { createRegistry } from "@vibeflui/core";
import { RoleSelectField } from "@/components/vibeflui/fields/RoleSelectField";

export const registry = createRegistry({
  fieldAdapters: {
    "users.roleSelect": RoleSelectField
  }
});
TS
export const usersSchema = {
  resource: "users",
  form: {
    fields: [
      {
        name: "roleId",
        label: "Role",
        type: "select",
        adapter: "users.roleSelect",
        optionLoader: "users.roles"
      }
    ]
  },
  registry: {
    fieldAdapters: ["users.roleSelect"],
    optionLoaders: ["users.roles"]
  }
} as const;

Custom component

Use component for richer bespoke UI.

components/vibeflui/fields/AvatarUploader.tsx

TSX
type AvatarUploaderProps = {
  value?: unknown;
  onChange?: (value: File | undefined) => void;
};

export function AvatarUploader({ onChange }: AvatarUploaderProps) {
  return (
    <input
      type="file"
      accept="image/*"
      onChange={(event) => onChange?.(event.target.files?.[0])}
    />
  );
}
TSX
import { createRegistry } from "@vibeflui/core";
import { AvatarUploader } from "@/components/vibeflui/fields/AvatarUploader";

export const registry = createRegistry({
  components: {
    AvatarUploader
  }
});
TS
{
  name: "avatar",
  type: "image",
  component: "AvatarUploader"
}

Missing custom component

When field.type is custom or field.component is set but the component is not registered, VibeFlui renders a dashed missing-component placeholder.

Props

Adapters and custom components receive:

  • schema
  • field
  • value
  • values
  • mode
  • options
  • onChange