Text Field

Input Group

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

View as Markdown
stable
inputgroupprefixsuffixcurrency

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

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

.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