Skip to main content

UI Adapters

A UIAdapter maps field types to React components and controls how labels, errors, and descriptions are rendered. It is completely independent of state management.

Built-in UI Adapters

ExportLibraryDescription
RavenShadcnUIAdapterShadCN / RadixTailwind-based accessible components
RavenAntDUIAdapterAnt DesignAnt Design 5.x components

UIAdapter Interface

interface UIAdapter {
/** Field type → component mapping */
components: Partial<Record<FieldType | string, ComponentType<UIFieldProps>>>;

/** Optional wrapper that renders label + error + description */
FormItem?: ComponentType<UIFormItemProps>;

/**
* Field types that render inline (bypassing FormItem wrapping).
* Defaults to ['checkbox', 'switch'].
*/
inlineTypes?: Array<FieldType | string>;

/** Fallback component for unmapped field types */
fallback?: ComponentType<UIFieldProps>;
}

UIFieldProps

interface UIFieldProps {
value: unknown;
onChange: (value: unknown) => void;
onBlur: () => void;
placeholder?: string;
disabled?: boolean;
error?: string;
options?: Array<{ label: string; value: string }>;
min?: number;
max?: number;
// ...plus any extra field-level props passed through schema
}

UIFormItemProps

interface UIFormItemProps {
label?: string;
error?: string;
description?: string;
required?: boolean;
children: React.ReactNode;
}

Creating a Custom UIAdapter

Use createUIAdapter to register your own components:

import {
createUIAdapter,
type UIFieldProps,
type UIFormItemProps,
} from "raven-form-engine";

// Your custom input component
function MyInput({ value, onChange, onBlur, placeholder, error }: UIFieldProps) {
return (
<input
value={String(value ?? "")}
placeholder={placeholder}
className={error ? "error" : ""}
onChange={(e) => onChange(e.target.value)}
onBlur={onBlur}
/>
);
}

// Your form item wrapper
function MyFormItem({
label,
error,
description,
required,
children,
}: UIFormItemProps) {
return (
<div className="form-item">
{label && (
<label>
{label}
{required && " *"}
</label>
)}
{description && <small>{description}</small>}
{children}
{error && <p className="error">{error}</p>}
</div>
);
}

// Register the adapter
export const MyUIAdapter = createUIAdapter({
components: {
text: MyInput,
email: MyInput,
password: MyInput,
// ... add more field types
},
FormItem: MyFormItem,
inlineTypes: ["checkbox", "switch"],
});

Swapping UI per Form

You can override the global UI adapter on a single form without touching the form adapter:

<RavenForm
schema={schema}
onSubmit={handleSubmit}
ui={MyUIAdapter} // only this form uses a different UI
/>

Adapter Validation Utilities

import { validateUIAdapter } from "raven-form-engine";

// In development, logs warnings for missing required fields
validateUIAdapter(myUIAdapter);