# AkiForm Builder

It builds on top of [Akiform](/akinon-ui/ui-kit/components/akiform.md) and React Hook Form.

### <mark style="color:red;">Installation</mark>

```bash
pnpm add @akinon/akiform-builder
```

### <mark style="color:red;">Basic Usage</mark>

```tsx
import { AkiformBuilder, field, type FormField } from '@akinon/akiform-builder';
import { akival } from '@akinon/akival';

interface UserForm {
  name: string;
  email: string;
  role: string;
}

const formFields: FormField<UserForm>[] = [
  field<UserForm>()
    .key('name')
    .type('text')
    .label('Name')
    .placeholder('Enter name')
    .validation(akival.string().required())
    .build(),
  field<UserForm>()
    .key('email')
    .type('text')
    .label('Email')
    .placeholder('Enter email')
    .validation(akival.string().email().required())
    .build(),
  field<UserForm>()
    .key('role')
    .type('select')
    .label('Role')
    .placeholder('Select role')
    .options([
      { label: 'Admin', value: 'admin' },
      { label: 'User', value: 'user' },
      { label: 'Guest', value: 'guest' }
    ])
    .build()
];

export const UserFormPage = () => {
  const handleSubmit = (values: UserForm) => {
    console.log('Form values:', values);
  };

  return (
    <AkiformBuilder
      fields={formFields}
      layout="vertical"
      onSubmit={handleSubmit}
    />
  );
};
```

### <mark style="color:red;">Props</mark>

| Prop                 | Type                                     | Description                        |
| -------------------- | ---------------------------------------- | ---------------------------------- |
| `fields`             | `FormField<T>[]`                         | Array of field definitions         |
| `onSubmit`           | `(values: T) => void`                    | Form submission handler            |
| `layout`             | `'vertical' \| 'horizontal' \| 'inline'` | Form layout                        |
| `layoutOptions`      | `LayoutOptions`                          | Label/wrapper column configuration |
| `showResetButton`    | `boolean`                                | Show reset button                  |
| `onReset`            | `FormEventHandler`                       | Reset handler                      |
| `controlled`         | `boolean`                                | Enable controlled mode             |
| `values`             | `T`                                      | Controlled form values             |
| `onValueChange`      | `(values: T) => void`                    | Value change callback              |
| `submitButtonProps`  | `ButtonProps`                            | Submit button customization        |
| `resetButtonProps`   | `ButtonProps`                            | Reset button customization         |
| `fieldErrors`        | `ApiFieldError[]`                        | API field errors to display        |
| `onFieldErrorsClear` | `() => void`                             | Clear field errors callback        |

### <mark style="color:red;">Field Builder API</mark>

Use the `field()` builder for type-safe field definitions:

```tsx
import { field } from '@akinon/akiform-builder';

field<FormValues>()
  .key('fieldName')           // Field key (required)
  .type('text')               // Field type (required)
  .label('Label')             // Display label
  .placeholder('Placeholder') // Input placeholder
  .defaultValue('default')    // Default value
  .validation(schema)         // Akival validation schema
  .config({ disabled: false, visible:true }) // Field configuration
  .tooltip('Help text')       // Tooltip content
  .help('Help message')       // Help text below field
  .labelDescription('Extra')  // Label description
  .build()
```

### <mark style="color:red;">Field Types</mark>

#### Text

```tsx
field<FormValues>()
  .key('name')
  .type('text')
  .label('Name')
  .placeholder('Enter name')
  .build()
```

#### Number

```tsx
field<FormValues>()
  .key('age')
  .type('number')
  .label('Age')
  .placeholder('Enter age')
  .build()
```

#### Select

```tsx
field<FormValues>()
  .key('country')
  .type('select')
  .label('Country')
  .placeholder('Select country')
  .options([
    { label: 'Turkey', value: 'TR' },
    { label: 'United States', value: 'US' },
    { label: 'Germany', value: 'DE' }
  ])
  .build()
```

#### Date

```tsx
field<FormValues>()
  .key('birthDate')
  .type('date')
  .label('Birth Date')
  .placeholder('Select date')
  .build()
```

#### Date with Time

```tsx
field<FormValues>()
  .key('scheduledAt')
  .type('date')
  .label('Scheduled At')
  .showTime(true)
  .build()
```

#### Checkbox

```tsx
field<FormValues>()
  .key('acceptTerms')
  .type('checkbox')
  .label('I accept the terms and conditions')
  .build()
```

#### Textarea

```tsx
field<FormValues>()
  .key('description')
  .type('textarea')
  .label('Description')
  .placeholder('Enter description')
  .build()
```

#### Custom

```tsx
field<FormValues>()
  .key('custom')
  .type('custom')
  .label('Custom Field')
  .options([{ // The `options` array is only used by Akifilter to resolve selected values to human-readable labels in the applied filters bar.
    label: "Phone",
    value: "phone"
  }]) 
  .render(({ field, control, formValues }) => (
    <MyCustomComponent
      control={control}
      name={field.key}
      formValues={formValues}
    />
  ))
  .build()
```

### <mark style="color:red;">Layout Fields</mark>

#### Section

Group fields into collapsible sections:

```tsx
field<FormValues>()
  .key('personalInfo')
  .type('section')
  .label('Personal Information')
  .collapsible(true)
  .defaultExpanded(true)
  .fields([
    field<FormValues>().key('firstName').type('text').label('First Name').build(),
    field<FormValues>().key('lastName').type('text').label('Last Name').build()
  ])
  .build()
```

#### Row and Column

Create multi-column layouts:

```tsx
field<FormValues>()
  .key('addressRow')
  .type('row')
  .rowProps({ gutter: 16 })
  .columnFields([
    field<FormValues>()
      .key('cityCol')
      .type('column')
      .columnProps({ span: 12 })
      .fields([
        field<FormValues>().key('city').type('text').label('City').build()
      ])
      .build(),
    field<FormValues>()
      .key('zipCol')
      .type('column')
      .columnProps({ span: 12 })
      .fields([
        field<FormValues>().key('zipCode').type('text').label('Zip Code').build()
      ])
      .build()
  ])
  .build()
```

#### Field Array

Dynamic repeatable fields:

```tsx
field<FormValues>()
  .key('items')
  .type('fieldArray')
  .label('Items')
  .defaultExpanded(true)
  .fields([
    field<FormValues>().key('name').type('text').label('Item Name').build(),
    field<FormValues>().key('quantity').type('number').label('Quantity').build()
  ])
  .build()
```

### <mark style="color:red;">Conditional Fields</mark>

Use `.config()` for dynamic visibility and disabled states:

```tsx
field<FormValues>()
  .key('otherReason')
  .type('textarea')
  .label('Other Reason')
  .config({
    // Show only when reason is 'other'
    visible: (formValues) => formValues.reason === 'other',
    // Disable when form is locked
    disabled: (formValues) => formValues.isLocked
  })
  .build()
```

### <mark style="color:red;">Controlled Mode</mark>

For external state management:

```tsx
const [formValues, setFormValues] = useState<FormValues>({
  name: '',
  email: ''
});

<AkiformBuilder
  fields={fields}
  controlled
  values={formValues}
  onValueChange={setFormValues}
  onSubmit={handleSubmit}
/>
```

### <mark style="color:red;">API Field Errors</mark>

Display server-side validation errors:

```tsx
const [fieldErrors, setFieldErrors] = useState<ApiFieldError[]>([]);

const handleSubmit = async (values: FormValues) => {
  try {
    await api.submit(values);
  } catch (error) {
    setFieldErrors([
      { field: 'email', message: 'Email already exists' }
    ]);
  }
};

<AkiformBuilder
  fields={fields}
  fieldErrors={fieldErrors}
  onFieldErrorsClear={() => setFieldErrors([])}
  onSubmit={handleSubmit}
/>
```

### <mark style="color:red;">Complete Example</mark>

```tsx
import { AkiformBuilder, field, type FormField } from '@akinon/akiform-builder';
import { akival } from '@akinon/akival';

interface ProductForm {
  name: string;
  sku: string;
  price: number;
  category: string;
  description: string;
  isActive: boolean;
  publishDate: Date | null;
}

const productFormFields: FormField<ProductForm>[] = [
  field<ProductForm>()
    .key('basicInfo')
    .type('section')
    .label('Basic Information')
    .collapsible(true)
    .defaultExpanded(true)
    .fields([
      field<ProductForm>()
        .key('name')
        .type('text')
        .label('Product Name')
        .placeholder('Enter product name')
        .validation(akival.string().required('Product name is required'))
        .build(),
      field<ProductForm>()
        .key('sku')
        .type('text')
        .label('SKU')
        .placeholder('Enter SKU')
        .validation(akival.string().required('SKU is required'))
        .build()
    ])
    .build(),
  field<ProductForm>()
    .key('pricingRow')
    .type('row')
    .rowProps({ gutter: 16 })
    .columnFields([
      field<ProductForm>()
        .key('priceCol')
        .type('column')
        .columnProps({ span: 12 })
        .fields([
          field<ProductForm>()
            .key('price')
            .type('number')
            .label('Price')
            .placeholder('0.00')
            .validation(akival.number().min(0).required())
            .build()
        ])
        .build(),
      field<ProductForm>()
        .key('categoryCol')
        .type('column')
        .columnProps({ span: 12 })
        .fields([
          field<ProductForm>()
            .key('category')
            .type('select')
            .label('Category')
            .placeholder('Select category')
            .options([
              { label: 'Electronics', value: 'electronics' },
              { label: 'Clothing', value: 'clothing' },
              { label: 'Books', value: 'books' }
            ])
            .build()
        ])
        .build()
    ])
    .build(),
  field<ProductForm>()
    .key('description')
    .type('textarea')
    .label('Description')
    .placeholder('Enter product description')
    .build(),
  field<ProductForm>()
    .key('publishDate')
    .type('date')
    .label('Publish Date')
    .showTime(true)
    .build(),
  field<ProductForm>()
    .key('isActive')
    .type('checkbox')
    .label('Product is active')
    .build()
];

export const ProductFormPage = () => {
  const handleSubmit = (values: ProductForm) => {
    console.log('Product saved:', values);
  };

  return (
    <AkiformBuilder
      fields={productFormFields}
      layout="vertical"
      onSubmit={handleSubmit}
      showResetButton
      submitButtonProps={{ children: 'Save Product' }}
      resetButtonProps={{ children: 'Reset' }}
    />
  );
};
```

### <mark style="color:red;">Best Practices</mark>

1. **Type your forms** - Always define TypeScript interfaces for form values
2. **Use** [**Akival**](/akinon-ui/ui-kit/utilities/akival.md) **for validation** - Consistent validation with meaningful error messages
3. **Group related fields** - Use sections for better organization
4. **Use controlled mode sparingly** - Only when external state sync is required
5. **Handle API errors** - Display server-side validation with `fieldErrors`

### <mark style="color:red;">Related</mark>

* [Akiform](/akinon-ui/ui-kit/components/akiform.md) - Component-based form building with React Hook Form integration
* [Akival](/akinon-ui/ui-kit/utilities/akival.md) - Validation library for form validation


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.akinon.com/akinon-ui/ui-kit/components/akiform-builder.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
