FluiKitField
FluiKitField renders one normalized field.
It is used by FluiKitForm, but can also be used in custom forms or local demos.
Props
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
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
"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:
field.adapterfromregistry.fieldAdaptersfield.componentfromregistry.components- missing custom component fallback when
field.type === "custom"and no matching component is registered - hidden input when
field.type === "hidden" - 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
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
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
"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:
modeisviewfield.readonlyis truefield.disabledis true
Visibility is resolved through isFieldVisible(field, values, mode).
Theme slots
When schema is passed, FluiKitField resolves:
fieldWrapperlabelinputerror
Without schema, it uses built-in fallback class names.