Formedible Docs
A clear docs map for forms defined in your code.
Start with the current app-local hook, choose a route by job-to-be-done, and move from field config to rendered examples, builders, parser workflows, and API details.
22+
field types
Rendered through copied shadcn-style components.
0
package lock-in
Consumer imports stay inside the app.
1
field model
Manual, builder, and AI flows share the same config.
Documentation
Core surfaces
One field model, every rendering mode
Start clean
Install surface
Bring Formedible into a TanStack Start app as generated hooks, fields, and utilities that belong to the consumer codebase.
- App-local imports
- Typed TanStack Form options
- shadcn-compatible UI primitives
Shape once
Field model
Describe field behavior with one readable model for simple forms, multi-page flows, tabs, arrays, analytics, and persistence.
- Conditional fields
- Dynamic labels
- Nested arrays and objects
Compose visually
Builder
Use the builder as a first-party app surface for field authoring, previewing, and copying production configuration.
- Real preview output
- Generated config review
- Consumer component paths
Generate safely
AI Builder
Turn structured prompts into the same field model without hiding the generated configuration from reviewers.
- Provider selection
- Reviewable output
- Same renderer path
Verify behavior
Examples
Run real Formedible examples that exercise the current app-local hook instead of stale package-era docs snippets.
- Compatibility coverage
- Accessible live forms
- No removed helpers
Clean-room shadcn install surface
The docs describe what exists in this TanStack Start app.
Formedible is documented here as copied source: the hook, field components, layouts, builder, and AI builder are imported from consumer paths. That keeps customization explicit and avoids runtime references to old package documentation.
Install the registry item
Add the built registry item to your app, then import the copied hook from your local UI package.
pnpm dlx shadcn@latest add https://formedible.dev/r/formedible-core.jsonContent model
One typed configuration powers every rendering mode.
Fields stay declarative while TanStack Form owns state and validation. The same model covers single-page forms, page flows, tabs, arrays, dynamic options, persistence, and analytics callbacks.
Typed fields over TanStack Form
Formedible renders shadcn-compatible fields while keeping TanStack Form in reach.
import { z } from 'zod';
import { useFormedible } from '@/components/ui/formedible/hooks/use-formedible';
const onboardingSchema = z.object({
name: z.string().min(2),
plan: z.enum(['starter', 'team', 'enterprise']),
needsMigration: z.boolean(),
});
type OnboardingValues = z.infer<typeof onboardingSchema>;
type WorkspaceRecord = OnboardingValues & {
slug: string;
};
const workspaceRecords: WorkspaceRecord[] = [];
function createWorkspace(values: OnboardingValues): WorkspaceRecord {
const workspace = {
...values,
slug: values.name.trim().toLowerCase().replace(/\s+/g, '-'),
};
workspaceRecords.push(workspace);
return workspace;
}
export function OnboardingForm() {
const { Form } = useFormedible<OnboardingValues>({
fields: [
{ name: 'name', type: 'text', label: 'Workspace name', required: true },
{ name: 'plan', type: 'radio', label: 'Plan', options: ['starter', 'team', 'enterprise'] },
{ name: 'needsMigration', type: 'switch', label: 'Import an existing form system?' },
],
schema: onboardingSchema,
formOptions: {
defaultValues: { name: '', plan: 'team', needsMigration: false },
onSubmit: ({ value }) => {
createWorkspace(value);
},
},
});
return <Form />;
}Customize the copied registry
The registry is copied into your app, so customize supported field types by editing the local mapping directly.
import type { ReactNode } from 'react';
import { NumberField } from '@/components/ui/formedible/fields/number-field';
import { TextField } from '@/components/ui/formedible/fields/text-field';
import type { FormedibleFieldRenderProps, FormedibleFormValues, NormalizedFieldType } from '@/components/ui/formedible/lib/types';
type FieldComponent = <TFormValues extends FormedibleFormValues>(props: FormedibleFieldRenderProps<TFormValues>) => ReactNode;
const fieldRegistry: Partial<Record<NormalizedFieldType, FieldComponent>> = {
number: NumberField,
text: TextField,
};
export function getFieldComponent<TFormValues extends FormedibleFormValues>(
type: NormalizedFieldType,
): (props: FormedibleFieldRenderProps<TFormValues>) => ReactNode {
return fieldRegistry[type] ?? TextField;
}Builder integration
The builder is a consumer component, not a separate docs artifact.
Mount the builder from the app path, let users assemble fields visually, then keep generated configuration reviewable before it is pasted into production code.
Builder shell path
The builder installs into your shadcn UI path, so you can place it beside your own navigation and persistence code.
import { FormBuilder } from '@/components/ui/formedible/builder/form-builder';
export function BuilderWorkspace() {
return (
<section aria-labelledby="builder-title" className="grid gap-6">
<div>
<p className="text-sm uppercase tracking-[0.24em] text-muted-foreground">Builder</p>
<h1 id="builder-title" className="text-3xl font-semibold tracking-tight">
Compose fields, preview output, copy configuration.
</h1>
</div>
<FormBuilder />
</section>
);
}AI-assisted generation
Prompted form generation still lands on the same source model.
Provider selection, chat, generated preview, and final renderer all live in the app. AI can accelerate drafting without creating a second incompatible form format.
AI Builder shell path
AI generation returns the same field model used by hand-written forms.
import { useState } from 'react';
import { AIBuilder } from '@/components/ui/formedible/ai/ai-builder';
import { createDefaultProviderSecrets, createDefaultProviderSettings, ProviderSelection } from '@/components/ui/formedible/ai/provider-selection';
export function AiBuilderWorkspace() {
const [providerSettings, setProviderSettings] = useState(() => createDefaultProviderSettings('openrouter'));
const [providerSecrets, setProviderSecrets] = useState(() => createDefaultProviderSecrets('openrouter'));
return (
<main className="grid gap-8 lg:grid-cols-[22rem_1fr]">
<ProviderSelection settings={providerSettings} secrets={providerSecrets} onChange={(settings, secrets) => {
setProviderSettings(settings);
setProviderSecrets(secrets);
}} />
<AIBuilder
providerSettings={providerSettings}
providerSecrets={providerSecrets}
onProviderSettingsChange={setProviderSettings}
onProviderSecretsChange={setProviderSecrets}
/>
</main>
);
}