Mobile-First Form Design
Over 60% of web traffic is mobile. Yet most forms are still designed on desktop and "adapted" for phones. The result? Tiny touch targets, horizontal scrolling, and keyboards that fight against the input type. Users give up.
Mobile-first means starting with the phone experience and expanding to desktop, not the other way around. A form that works well on a 375px screen will work fine on a 1920px monitor. The reverse is rarely true.
This guide covers the patterns that make forms work on mobile: proper layouts, correct input types, tap-friendly controls, and the details that separate frustrating forms from effortless ones.
Single Column Layout
The most important mobile form rule: one column. Side-by-side fields that look fine on desktop become cramped and hard to tap on phones.
// Single column layout - best for mobile
form.addRow(row => {
row.addTextbox('name', { label: 'Full Name', isRequired: true });
});
form.addRow(row => {
row.addEmail('email', { label: 'Email', isRequired: true });
});
form.addRow(row => {
row.addTextbox('phone', { label: 'Phone', isRequired: true });
});Each field gets its own row. Full width. Maximum tap target. No horizontal scrolling. This is the safest default for any form.
When Two Columns Work
Some field pairs make sense together: first/last name, city/state. FormTs automatically stacks multi-column rows on small screens.
// Two columns on desktop, stacks to single on mobile
form.addRow(row => {
row.addTextbox('firstName', { label: 'First Name' }, '1fr');
row.addTextbox('lastName', { label: 'Last Name' }, '1fr');
});
// This automatically stacks on screens under 600px The 1fr columns share space equally on desktop and stack vertically below 600px by default. You don't need media queries.
Pro tip
Use two columns sparingly. Even when it stacks, the mental model of "these fields go together" should be obvious. First/last name works. Email/phone doesn't - they're unrelated fields.
Custom Breakpoints
Sometimes the default 600px breakpoint isn't right. Maybe your form is in a narrow sidebar, or your fields need more room. Customize it per section.
// Control when columns stack with mobileBreakpoint
const contactSection = form.addSubform('contact', {
title: 'Contact Information',
mobileBreakpoint: 480 // Stack columns below 480px instead of default 600px
});
contactSection.addRow(row => {
row.addTextbox('firstName', { label: 'First Name' }, '1fr');
row.addTextbox('lastName', { label: 'Last Name' }, '1fr');
});
// For multi-page forms
const pages = form.addPages('wizard');
const page1 = pages.addPage('contact', {
mobileBreakpoint: 500 // Custom breakpoint for this page
});The Right Input Type
Mobile keyboards adapt to input type. Use the wrong field and users type email addresses on a standard keyboard. Use the right field and they get the @ symbol front and center.
Email Fields
// Email field shows email keyboard on mobile
form.addRow(row => {
row.addEmail('email', {
label: 'Email Address',
placeholder: 'you@example.com',
isRequired: true
});
});
// This renders as <input type="email"> which:
// - Shows @ and .com on mobile keyboards
// - Enables browser autofill for email
// - Validates email format automaticallyThe email field does three things: shows the right keyboard, enables browser autofill, and validates format automatically. Never use a plain textbox for email.
Phone Numbers
// Phone field with pattern for numeric input
form.addRow(row => {
row.addTextbox('phone', {
label: 'Phone Number',
placeholder: '555-123-4567',
pattern: '^[0-9()-]+$'
});
});
// For numeric-only inputs, use Integer
form.addRow(row => {
row.addInteger('zipCode', {
label: 'ZIP Code',
placeholder: '12345',
min: 10000,
max: 99999
});
}); For phone numbers, a textbox with pattern validation works. For purely numeric inputs like ZIP codes, use addInteger to show the number pad.
Numeric Inputs
// Integer shows numeric keyboard
form.addRow(row => {
row.addInteger('quantity', {
label: 'Quantity',
min: 1,
max: 100,
defaultValue: 1
});
});
// Decimal for prices and measurements
form.addRow(row => {
row.addDecimal('amount', {
label: 'Amount',
min: 0,
step: 0.01,
placeholder: '0.00'
});
});
// Money field with currency formatting
form.addRow(row => {
row.addMoney('budget', {
label: 'Budget',
currency: '$',
min: 0
});
});Integer, Decimal, and Money fields all show numeric keyboards on mobile. Pick the right one based on whether you need whole numbers, decimals, or currency formatting.
Dates and Times
// Native datepicker works great on mobile
form.addRow(row => {
row.addDatepicker('moveDate', {
label: 'Preferred Date',
minDate: () => new Date().toISOString().split('T')[0],
isRequired: true
});
});
// Time picker for appointments
form.addRow(row => {
row.addTimepicker('preferredTime', {
label: 'Preferred Time',
minTime: '09:00',
maxTime: '17:00'
});
});Native date and time pickers work beautifully on mobile. The OS provides a familiar interface - spinning wheels on iOS, calendar on Android. Don't fight it with custom pickers.
See mobile-optimized form examples.
Dropdowns vs. Radio Buttons
Dropdowns hide options behind a tap. Radio buttons show everything upfront. The right choice depends on how many options you have.
// Dropdown: compact, good for 5+ options
form.addRow(row => {
row.addDropdown('state', {
label: 'State',
options: [
{ id: 'ca', name: 'California' },
{ id: 'tx', name: 'Texas' },
{ id: 'ny', name: 'New York' },
// ... many more
]
});
});
// Radio buttons: better for 2-4 options
form.addRow(row => {
row.addRadioButton('contactMethod', {
label: 'Preferred Contact Method',
options: [
{ id: 'email', name: 'Email' },
{ id: 'phone', name: 'Phone' },
{ id: 'text', name: 'Text Message' }
],
orientation: 'vertical' // Stack vertically on mobile
});
}); Rule of thumb: 2-4 options = radio buttons (visible, one tap). 5+ options = dropdown (compact, searchable on some platforms). Radio buttons with orientation: 'vertical' stack for easy mobile tapping.
Touch-Friendly Ratings
Ratings need large touch targets. A 16px star is nearly impossible to tap accurately on a phone. Make them big.
// Large touch targets for ratings
form.addRow(row => {
row.addStarRating('rating', {
label: 'How was your experience?',
size: 'lg', // Large stars for easy tapping
maxStars: 5,
isRequired: true
});
});
// Emoji ratings with big touch targets
form.addRow(row => {
row.addEmojiRating('satisfaction', {
label: 'How do you feel?',
preset: 'satisfaction',
size: 'lg', // Larger emojis
showLabels: true
});
});Emoji and Thumb Ratings
Emoji and thumb ratings are inherently mobile-friendly - they're visual, fast, and hard to miss.
// Thumb rating - perfect for mobile
form.addRow(row => {
row.addThumbRating('helpful', {
label: 'Was this helpful?',
size: 'lg', // Big thumbs
showLabels: true,
upLabel: 'Yes',
downLabel: 'No'
});
}); The size: 'lg' option makes these controls large enough for comfortable tapping. Always use it on mobile-first forms.
Text Input Patterns
Auto-Expanding Textareas
Fixed-height textareas force users to scroll within a tiny box. Auto-expand grows the field as they type.
// Auto-expanding textarea
form.addRow(row => {
row.addTextarea('message', {
label: 'Your Message',
placeholder: 'Type your message here...',
rows: 3, // Initial height
autoExpand: true, // Grows as user types
maxLength: 500
});
});Start with a reasonable height (3 rows) and let it grow. Users see all their text without a scrollbar-within-a-scrollbar nightmare.
Suggestion Chips
When users need to select from predefined options, chips are more tap-friendly than typing. One tap vs. hunting for keys.
// Suggestion chips - tap-friendly selection
form.addRow(row => {
row.addSuggestionChips('interests', {
label: 'What are you interested in?',
suggestions: [
{ id: 'pricing', name: 'Pricing' },
{ id: 'features', name: 'Features' },
{ id: 'demo', name: 'Demo' },
{ id: 'support', name: 'Support' }
],
max: 3
});
});Checkbox Lists
Checkboxes need vertical orientation on mobile. Horizontal lists get cramped and labels truncate.
// Checkbox list with vertical orientation
form.addRow(row => {
row.addCheckboxList('services', {
label: 'Select Services',
options: [
{ id: 'cleaning', name: 'House Cleaning' },
{ id: 'laundry', name: 'Laundry' },
{ id: 'organizing', name: 'Organizing' }
],
orientation: 'vertical' // Stacked for easy tapping
});
});Sliders for Ranges
Sliders are naturally touch-friendly - drag to select. They work better than number inputs for ranges where precision isn't critical.
// Slider with visible value - great for mobile
form.addRow(row => {
row.addSlider('budget', {
label: 'Budget Range',
min: 100,
max: 5000,
step: 100,
defaultValue: 1000,
showValue: true, // Shows current value
unit: '$'
});
});Always show the current value. Users can't see where their thumb is precisely positioned on a small screen.
The Submit Button
The submit button should be impossible to miss. Full width, prominent color, clear label.
// Configure submit button
form.configureSubmitButton({
label: 'Submit Request' // Clear action label
});
// The submit button is automatically:
// - Full width on mobile
// - Large enough for easy tapping
// - Styled for visibilityFormTs buttons are automatically sized appropriately and full-width on mobile. Use action-oriented labels: "Send Message" not "Submit", "Get Quote" not "Continue".
Complete Example
Here's a mobile-first contact form that follows all these patterns:
export function mobileForm(form: FormTs) {
form.setTitle(() => 'Contact Us');
// Single column, stacks naturally
form.addRow(row => {
row.addTextbox('name', {
label: 'Name',
isRequired: true
});
});
form.addRow(row => {
row.addEmail('email', {
label: 'Email',
isRequired: true
});
});
form.addRow(row => {
row.addTextbox('phone', {
label: 'Phone'
});
});
// Radio for few options
form.addRow(row => {
row.addRadioButton('reason', {
label: 'Reason for Contact',
options: [
{ id: 'quote', name: 'Get a Quote' },
{ id: 'question', name: 'Ask a Question' },
{ id: 'support', name: 'Support' }
],
orientation: 'vertical'
});
});
// Auto-expanding message field
form.addRow(row => {
row.addTextarea('message', {
label: 'Message',
rows: 3,
autoExpand: true
});
});
form.configureSubmitButton({ label: 'Send Message' });
}Testing in the Editor
You don't need to deploy to test mobile layouts. The FormTs editor has built-in responsive preview that lets you see exactly how your form behaves at different screen sizes.
Resize Handle
Drag the resize handle on the right edge of the preview pane to smoothly adjust the form width. Watch columns stack, fields resize, and layouts adapt in real-time. This is the fastest way to find breakpoint issues - drag slowly and watch for awkward transitions.
Device Presets
Use the viewport buttons to jump between common device sizes:
- Mobile - 375px width (iPhone size)
- Tablet - 768px width (iPad portrait)
- Desktop - 1024px width (laptop)
- Auto - Full available width (fills the preview pane)
Test at each preset to ensure your form works across the full range of devices. Pay special attention to the mobile view - if it works there, it'll work everywhere.
Pro tip
Start designing in mobile view. Build the form at 375px width first, then switch to desktop to add multi-column layouts where they make sense. This forces mobile-first thinking.
Testing on Real Devices
The editor preview is great for layout testing, but real devices reveal issues that simulators miss:
- Tap targets - Can you tap each field without accidentally hitting another?
- Keyboards - Does each field show the right keyboard type?
- Scrolling - Can you scroll the page without fighting the form?
- Autofill - Does the browser offer to fill email, phone, name?
- Orientation - Does it work in both portrait and landscape?
- Submit - Is the submit button visible without scrolling on short forms?
Test on both iOS and Android. They handle keyboards, datepickers, and scrolling differently. What works on one might feel broken on the other.
Common Questions
What's the minimum touch target size?
Apple recommends 44x44 points, Google recommends 48x48 dp. In practice, aim for at least 44px height for any tappable element. FormTs controls with size: 'lg' meet this requirement. Don't make users precision-tap tiny checkboxes.
Should I disable zoom on forms?
No. Never disable zoom. Users with vision issues need it. iOS Safari zooms automatically when font size is under 16px - use 16px or larger for input text to prevent this auto-zoom while keeping accessibility.
How do I handle long forms on mobile?
Break them into pages. Multi-page forms with progress indicators feel shorter than one long scroll. Each page should be completable without scrolling on most phones. Use addPages() to create wizard-style flows.
What about autocomplete/autofill?
FormTs fields support browser autofill automatically for common types (email, phone, name). The browser recognizes field types and offers to fill them. Don't disable autofill - it's a huge UX win on mobile where typing is slow.
Should forms look identical on mobile and desktop?
No. The goal is equivalent functionality, not identical appearance. Desktop can show more context, use multi-column layouts, display help text inline. Mobile should be streamlined - show only what's essential, stack everything vertically, maximize tap targets.