Interactive demo of text input fields
/**
* Text Field Demo - Interactive example of text input fields
*/
import { Form, FormBuilder } from '@saastro/forms';
import { FormProvider } from '@/components/FormProvider';
import { TooltipProvider } from '@/components/ui/tooltip';
const config = FormBuilder.create('text-demo')
.layout('manual')
.columns(12)
.addField('name', (f) =>
f
.type('text')
.label('Your Name')
.placeholder('Enter your full name')
.helperText('Your name as it appears on official documents')
.required('Name is required')
.minLength(2, 'Name must be at least 2 characters')
.columns({ default: 12, md: 6 }),
)
.addField('email', (f) =>
f
.type('email')
.label('Email Address')
.placeholder('[email protected]')
.helperText("We'll never share your email")
.required('Email is required')
.email('Please enter a valid email address')
.columns({ default: 12, md: 6 }),
)
.addField('phone', (f) =>
f
.type('tel')
.label('Phone Number')
.placeholder('+1 (555) 123-4567')
.helperText('Optional - for urgent matters only')
.optional()
.columns({ default: 12 }),
)
.addStep('main', ['name', 'email', 'phone'])
.build();
export default function TextDemo() {
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 text field family is the most basic input in @saastro/forms. Six field types share the same renderer — the field’s type is passed straight through to the native <input> element’s type attribute:
type | Native input | Notes |
|---|---|---|
text | <input type="text"> | Standard single-line text |
email | <input type="email"> | Email keyboard on mobile; add .email() for format validation |
tel | <input type="tel"> | Telephone keypad on mobile; no built-in format validation |
url | <input type="url"> | URL keyboard on mobile; add .url() for format validation |
password | <input type="password"> | Masks the entered value |
number | <input type="number"> | Numeric keyboard; the value is a string at runtime (see Number type) |
Usage
JSON Configuration
{
"type": "text",
"label": "Your Name",
"placeholder": "Enter your name",
"schema": {
"required": true,
"minLength": 2
}
}
FormBuilder API
import { FormBuilder } from '@saastro/forms';
const config = FormBuilder.create('contact-form')
.addField('name', (f) =>
f
.type('text')
.label('Your Name')
.placeholder('Enter your name')
.required('Name is required')
.minLength(2, 'Name must be at least 2 characters'),
)
.addStep('main', ['name'])
.build();
Email Type
Use type: 'email' together with the .email() validation rule for format validation:
.addField('email', (f) =>
f.type('email')
.label('Email Address')
.placeholder('[email protected]')
.required()
.email('Please enter a valid email')
)
Tel Type
Use type: 'tel' for phone number inputs:
.addField('phone', (f) =>
f.type('tel')
.label('Phone Number')
.placeholder('+1 (555) 123-4567')
.required()
)
URL Type
Use type: 'url' together with the .url() validation rule:
.addField('website', (f) =>
f.type('url')
.label('Website')
.placeholder('https://example.com')
.url('Please enter a valid URL')
)
Password Type
Use type: 'password' for masked input. Combine it with one of the built-in validation presets (password-simple, password-medium, password-strong):
.addField('password', (f) =>
f.type('password')
.label('Password')
.required('Password is required')
.preset('password-strong')
)
Number Type
Use type: 'number' for numeric input. Because it renders a native number input, the field value is a string at runtime. Use z.coerce.number() for validation and the toNumber transform to receive a number in the submit payload:
import { z } from 'zod';
.addField('quantity', (f) =>
f.type('number')
.label('Quantity')
.placeholder('0')
.schema(z.coerce.number().min(1, 'Enter at least 1'))
.transform('toNumber')
)
Props
| Prop | Type | Default | Description |
|---|---|---|---|
type | 'text' | 'email' | 'tel' | 'url' | 'password' | 'number' | - | Input type (required) |
label | string | - | Label shown above the input |
placeholder | string | - | Placeholder text |
schema | z.ZodType | ValidationRules | - | Validation: a Zod schema or serializable JSON rules (required — fluent methods like .required() or .email() populate it for you) |
hideLabel | boolean | false | Hide the label visually |
helperText | string | - | Help text shown below input |
tooltip | string | - | Tooltip text for additional info |
icon | ReactNode | - | Icon element to display |
size | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Input size variant |
disabled | boolean | function | ConditionGroup | false | Disable the input |
hidden | boolean | function | ConditionGroup | Responsive | false | Hide the field |
readOnly | boolean | function | ConditionGroup | false | Make input read-only |
columns | Partial<Record<Breakpoint, number>> | - | Grid columns by breakpoint |
autocomplete | string | - | HTML autocomplete attribute |
Default placeholders. When
placeholderis omitted, the library falls back to built-in English defaults ('Enter an email...'for'Enter a phone number...'fortel,'Enter a value...'for the rest). Set aplaceholderper field or override the defaults globally withsetDefaultMessages— see the Internationalization guide.
Validation
Declarative (JSON-serializable)
{
"schema": {
"required": true,
"requiredMessage": "This field is required",
"minLength": 2,
"minLengthMessage": "Minimum 2 characters",
"maxLength": 100,
"pattern": "^[a-zA-Z]+$",
"patternMessage": "Only letters allowed"
}
}
Zod Schema
import { z } from 'zod';
.addField('name', (f) =>
f.type('text')
.label('Name')
.schema(z.string().min(2).max(100))
)
Fluent Validation API
.addField('name', (f) =>
f.type('text')
.label('Name')
.required('Name is required')
.minLength(2, 'Too short')
.maxLengthValidation(100, 'Too long')
)
Styling
Custom Classes
.addField('name', (f) =>
f.type('text')
.label('Name')
.classNames({
wrapper: 'col-span-6',
input: 'border-blue-500',
label: 'text-blue-700',
error: 'text-red-600',
})
)
Responsive Layout
.addField('name', (f) =>
f.type('text')
.label('Name')
.columns({ default: 12, md: 6, lg: 4 })
)
Per-field column spans take effect when the form uses manual layout mode (e.g. FormBuilder.create('id').layout('manual').columns(12)). In auto mode the grid is adaptive and per-field columns are ignored — see the Layout guide.
Related Fields
- Textarea - Multi-line text input
- Input Group - Text input with prefix/suffix addons
Accessibility
- Renders through your injected
Inputcomponent with the nativetypeattribute, so browser behaviors (email keyboard on mobile, password masking, numeric keypad) work out of the box - The field container receives
data-invalidwhen validation fails - Validation errors render through the injected
FieldErrorcomponent; helper text throughFieldDescription - Label association and ARIA attributes (
htmlFor,aria-invalid,aria-describedby) come from the injected component set — shadcn/ui’s form primitives wire these automatically