# Field: Input Group (category: text)

URL: https://docs.forms.saastro.io/docs/input-group

<FieldPreview name="input-group" description="Interactive demo of input group field" />

## Overview

The input group field extends the standard text input with `prefix` and `suffix` text addons rendered on either side of the input. Use it for formatted inputs like currency amounts, URLs, or measurements.

Both addons are plain strings set directly on the field config — `.prefix(text)` / `.suffix(text)` in the fluent API, or the `prefix` / `suffix` keys in JSON. The input itself always renders as a text input, so the field value is a `string`.

## Usage

### With Prefix

```tsx

const config = FormBuilder.create('form')
  .addField('price', (f) =>
    f
      .type('input-group')
      .label('Price')
      .placeholder('0.00')
      .prefix('$')
      .required('Price is required'),
  )
  .addStep('main', ['price'])
  .build();
```

### With Suffix

```tsx
.addField('weight', (f) =>
  f.type('input-group')
    .label('Weight')
    .placeholder('0')
    .suffix('kg')
    .required()
)
```

### With Both

```tsx
.addField('website', (f) =>
  f.type('input-group')
    .label('Website')
    .placeholder('example')
    .prefix('https://')
    .suffix('.com')
    .optional()
)
```

### JSON Configuration

```json
{
  "type": "input-group",
  "label": "Price",
  "placeholder": "0.00",
  "prefix": "$",
  "suffix": "USD",
  "schema": { "required": true }
}
```

## The `currency` Alias

`currency` is an alias of `input-group`: both types accept the same props and render through the same component. It adds no currency formatting, masking, or locale logic — set the symbol yourself via `prefix` or `suffix`:

```tsx
.addField('amount', (f) =>
  f.type('currency')
    .label('Amount')
    .placeholder('0.00')
    .prefix('€')
    .required()
)
```

## Props

| Prop          | Type                                                  | Default | Description                                    |
| ------------- | ----------------------------------------------------- | ------- | ---------------------------------------------- |
| `type`        | `'input-group' \| 'currency'`                         | -       | Field type (required)                          |
| `label`       | `string`                                              | -       | Label text                                     |
| `placeholder` | `string`                                              | -       | Placeholder text                               |
| `helperText`  | `string`                                              | -       | Help text below field                          |
| `prefix`      | `string`                                              | -       | Text addon rendered before the input           |
| `suffix`      | `string`                                              | -       | Text addon rendered after the input            |
| `schema`      | `z.ZodType \| ValidationRules`                        | -       | Validation schema (required; fluent validation methods like `.required()` set it for you) |
| `size`        | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'`                | `'md'`  | Input size variant                             |
| `columns`     | `Partial<Record<Breakpoint, number>>`                 | -       | Grid columns by breakpoint                     |
| `disabled`    | `boolean \| function \| ConditionGroup`               | `false` | Disable the field                              |
| `hidden`      | `boolean \| function \| ConditionGroup \| Responsive` | `false` | Hide the field                                 |

## Common Patterns

### Currency Input

```tsx
.addField('amount', (f) =>
  f.type('input-group')
    .label('Amount')
    .placeholder('0.00')
    .prefix('$')
    .required('Amount is required')
)
```

### URL Input

```tsx
.addField('domain', (f) =>
  f.type('input-group')
    .label('Domain')
    .placeholder('yoursite')
    .prefix('https://')
    .suffix('.com')
    .required()
)
```

### Percentage Input

```tsx
.addField('discount', (f) =>
  f.type('input-group')
    .label('Discount')
    .placeholder('0')
    .suffix('%')
    .optional()
)
```

## Validation

The field value is a string, so string-based rules apply: `required`, `minLength`, `maxLength`, `pattern`, and `format`. For numeric input, validate the shape with a regex:

```tsx
.addField('price', (f) =>
  f.type('input-group')
    .label('Price')
    .prefix('$')
    .required('Price is required')
    .regex('^\\d+(\\.\\d{1,2})?$', 'Enter a valid amount')
)
```

## Styling

### Custom Classes

```tsx
.addField('price', (f) =>
  f.type('input-group')
    .label('Price')
    .placeholder('0.00')
    .prefix('$')
    .required()
    .classNames({
      wrapper: 'bg-muted/30 p-4 rounded-lg',
      input: 'border-2 border-primary/20 font-mono',
      label: 'text-lg font-semibold',
      error: 'text-destructive text-sm',
    })
)
```

### Size Variants

```tsx
.addField('amount', (f) =>
  f.type('input-group')
    .label('Amount')
    .prefix('$')
    .size('lg')
    .optional()
)
```

### Responsive Layout

```tsx
.addField('price', (f) =>
  f.type('input-group')
    .label('Price')
    .prefix('$')
    .columns({ default: 12, md: 4 })
    .optional()
)
```

### JSON Styling

```json
{
  "type": "input-group",
  "label": "Price",
  "prefix": "$",
  "wrapper_className": "bg-muted/30 p-4 rounded-lg",
  "input_className": "border-2 border-primary/20 font-mono",
  "label_className": "text-lg font-semibold",
  "size": "lg",
  "columns": { "default": 12, "md": 4 },
  "schema": { "required": true }
}
```

## Related Fields

- [Text](/docs/text) - Standard text input
- [Textarea](/docs/textarea) - Multi-line text

## Accessibility

- Prefix and suffix render as plain text addons next to the input; they are not associated with the input via ARIA, so keep essential context in the label or helper text
- Main input retains full accessibility
- Proper labeling maintained
