Text Field

Input Group

Text input with prefix and/or suffix text addons for formatted inputs.

stable
input group prefix suffix currency

Interactive demo of input group field

Enter the price in USD

Enter weight in kilograms

Enter your website domain

/**
 * Input Group Field Demo - Interactive examples of input group fields
 */

import { Form, FormBuilder } from '@saastro/forms';

import { FormProvider } from '@/components/FormProvider';
import { TooltipProvider } from '@/components/ui/tooltip';

const config = FormBuilder.create('input-group-demo')
  .layout('manual')
  .columns(12)
  .addField('price', (f) =>
    f
      .type('input-group')
      .label('Price')
      .placeholder('0.00')
      .helperText('Enter the price in USD')
      .required('Price is required')
      .columns({ default: 12, md: 6 }),
  )
  .addField('weight', (f) =>
    f
      .type('input-group')
      .label('Weight')
      .placeholder('0')
      .helperText('Enter weight in kilograms')
      .required()
      .columns({ default: 12, md: 6 }),
  )
  .addField('website', (f) =>
    f
      .type('input-group')
      .label('Website')
      .placeholder('yoursite')
      .helperText('Enter your website domain')
      .optional()
      .columns({ default: 12 }),
  )
  .addStep('main', ['price', 'weight', 'website'])
  .build();

export default function InputGroupDemo() {
  const handleSubmit = (data: Record<string, unknown>) => {
    console.log('Form submitted:', data);
    alert('Form submitted! Check console for data.');
  };

  return (
    <TooltipProvider>
      <FormProvider>
        <Form config={config} onSubmit={handleSubmit} className="space-y-4" />
      </FormProvider>
    </TooltipProvider>
  );
}

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

import { FormBuilder } from '@saastro/forms';

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

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

With Both

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

JSON Configuration

{
  "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:

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

Props

PropTypeDefaultDescription
type'input-group' | 'currency'-Field type (required)
labelstring-Label text
placeholderstring-Placeholder text
helperTextstring-Help text below field
prefixstring-Text addon rendered before the input
suffixstring-Text addon rendered after the input
schemaz.ZodType | ValidationRules-Validation schema (required; fluent validation methods like .required() set it for you)
size'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Input size variant
columnsPartial<Record<Breakpoint, number>>-Grid columns by breakpoint
disabledboolean | function | ConditionGroupfalseDisable the field
hiddenboolean | function | ConditionGroup | ResponsivefalseHide the field

Common Patterns

Currency Input

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

URL Input

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

Percentage Input

.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:

.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

.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

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

Responsive Layout

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

JSON Styling

{
  "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 }
}

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