Image Choice Fields: Visual Selection for Forms
Sometimes a picture is worth a thousand radio buttons. When users choose between travel styles, product variants, room types, or design themes, showing images beats listing text options. Image choice fields turn selection into a visual experience.
Instead of "Budget / Comfort / Luxury" as text, show actual photos of each travel style. Instead of "Modern / Traditional / Minimalist", show room interiors. Users understand instantly and choose confidently.
Basic Image Choice
At minimum, each option needs an ID, a label, and an asset reference. The asset is an image you've uploaded to FormTs.
form.addRow(row => {
row.addImageChoice('travelStyle', {
label: 'Select Your Travel Style',
options: [
{ id: 'budget', label: 'Budget', assetId: 'travel_budget' },
{ id: 'comfort', label: 'Comfort', assetId: 'travel_comfort' },
{ id: 'luxury', label: 'Luxury', assetId: 'travel_luxury' }
]
});
});Users see three cards with images and labels. Click to select. The selected option gets a visual indicator (checkmark and border highlight).
Cards with Descriptions
For more context, add descriptions. The card variant shows the description below the label - perfect for pricing tiers, service packages, or product comparisons.
form.addRow(row => {
row.addImageChoice('packageType', {
label: 'Choose Your Package',
variant: 'card',
options: [
{
id: 'starter',
label: 'Starter',
description: 'Perfect for small projects',
assetId: 'package_starter'
},
{
id: 'professional',
label: 'Professional',
description: 'Most popular choice',
assetId: 'package_pro'
},
{
id: 'enterprise',
label: 'Enterprise',
description: 'For large organizations',
assetId: 'package_enterprise'
}
]
});
});Pro tip
Keep descriptions short - one line is ideal. If you need more detail, link to a comparison page or use tooltips.
Layout Options
Control how options are arranged with the layout property.
Columns Layout
The default layout. Specify exact column count or use 'auto' to fit as many as possible based on option size.
form.addRow(row => {
row.addImageChoice('roomType', {
label: 'Select Room Type',
layout: { type: 'columns', columnsCount: 4 },
options: [
{ id: 'living', label: 'Living Room', assetId: 'room_living' },
{ id: 'bedroom', label: 'Bedroom', assetId: 'room_bedroom' },
{ id: 'kitchen', label: 'Kitchen', assetId: 'room_kitchen' },
{ id: 'bathroom', label: 'Bathroom', assetId: 'room_bathroom' }
]
});
});
// Auto-fit columns based on option size
form.addRow(row => {
row.addImageChoice('icons', {
label: 'Select Icons',
layout: { type: 'columns', columnsCount: 'auto' },
optionSize: { width: 80, height: 80 },
options: [/* many small icons */]
});
});Carousel Layout
When you have many options, a carousel lets users browse without overwhelming the page. Configure visible items, arrows, dots, and looping behavior.
form.addRow(row => {
row.addImageChoice('destination', {
label: 'Choose Your Destination',
layout: {
type: 'carousel',
visibleItems: 3,
showArrows: true,
showDots: true,
loop: true
},
options: [
{ id: 'paris', label: 'Paris', assetId: 'dest_paris' },
{ id: 'tokyo', label: 'Tokyo', assetId: 'dest_tokyo' },
{ id: 'nyc', label: 'New York', assetId: 'dest_nyc' },
{ id: 'london', label: 'London', assetId: 'dest_london' },
{ id: 'rome', label: 'Rome', assetId: 'dest_rome' },
{ id: 'sydney', label: 'Sydney', assetId: 'dest_sydney' }
]
});
});Dots indicate position and show which options are selected. Arrows navigate one item at a time. Loop mode wraps around seamlessly.
Try the interactive Image Choice tutorial.
Visual Variants
Three variants control how options look:
// Card variant - label below image (default)
row.addImageChoice('style', {
variant: 'card',
options: [...]
});
// Overlay variant - label on image
row.addImageChoice('style', {
variant: 'overlay',
options: [...]
});
// Thumbnail variant - minimal, clean look
row.addImageChoice('style', {
variant: 'thumbnail',
options: [...]
});- Card: Image on top, label (and optional description) below. Best for product selection, pricing tiers.
- Overlay: Label overlaid on the image with gradient background. Best for full-bleed photos, destinations, portfolios.
- Thumbnail: Minimal styling, images stand alone. Non-selected options are slightly faded. Best for galleries, icon selection, color swatches.
Multiple Selection
By default, image choice is single-select (like radio buttons). Switch to multiple selection to let users pick several options (like checkboxes).
form.addRow(row => {
row.addImageChoice('amenities', {
label: 'Select Amenities',
selectionMode: 'multiple',
min: 1,
max: 5,
options: [
{ id: 'wifi', label: 'WiFi', assetId: 'amenity_wifi' },
{ id: 'pool', label: 'Pool', assetId: 'amenity_pool' },
{ id: 'gym', label: 'Gym', assetId: 'amenity_gym' },
{ id: 'spa', label: 'Spa', assetId: 'amenity_spa' },
{ id: 'parking', label: 'Parking', assetId: 'amenity_parking' },
{ id: 'restaurant', label: 'Restaurant', assetId: 'amenity_restaurant' }
]
});
}); The min and max constraints enforce selection limits. Users must select at least 1 and at most 5 amenities in this example.
Option Sizing
Control image dimensions globally or per-option. Images scale to fit while maintaining aspect ratio with object-fit cover.
// Global size for all options
form.addRow(row => {
row.addImageChoice('product', {
label: 'Select Product',
optionSize: { width: 200, height: 150 },
options: [...]
});
});
// Per-option sizing
form.addRow(row => {
row.addImageChoice('featured', {
label: 'Featured Items',
options: [
{
id: 'main',
label: 'Main Product',
assetId: 'product_main',
size: { width: 300, height: 200 }
},
{
id: 'accessory1',
label: 'Accessory',
assetId: 'product_acc1',
size: { width: 150, height: 100 }
},
{
id: 'accessory2',
label: 'Accessory',
assetId: 'product_acc2',
size: { width: 150, height: 100 }
}
]
});
});Per-option sizing lets you create featured layouts where one item is larger than others - great for highlighting a recommended choice.
Conditional Options
Options can change based on other form values. Show different accommodation choices depending on the selected travel style.
const travelStyle = form.imageChoice('travelStyle');
form.addRow(row => {
row.addImageChoice('accommodation', {
label: 'Select Accommodation',
options: () => {
const style = travelStyle?.value();
if (style === 'budget') {
return [
{ id: 'hostel', label: 'Hostel', assetId: 'acc_hostel' },
{ id: 'airbnb', label: 'Airbnb', assetId: 'acc_airbnb' }
];
}
if (style === 'luxury') {
return [
{ id: 'resort', label: 'Resort', assetId: 'acc_resort' },
{ id: 'villa', label: 'Private Villa', assetId: 'acc_villa' }
];
}
return [
{ id: 'hotel3', label: '3-Star Hotel', assetId: 'acc_hotel3' },
{ id: 'hotel4', label: '4-Star Hotel', assetId: 'acc_hotel4' }
];
}
});
});The options function runs reactively. When travel style changes, accommodation options update automatically. Budget travelers see hostels; luxury travelers see resorts.
Price Integration
Connect image choice to pricing calculations. Users see the price update as they browse options.
const packageChoice = form.imageChoice('packageType');
const pricing = {
starter: 29,
professional: 79,
enterprise: 199
};
form.addRow(row => {
row.addPriceDisplay('price', {
label: 'Monthly Price',
computedValue: () => {
const pkg = packageChoice?.value();
return pkg ? pricing[pkg] : 0;
},
currency: '$',
suffix: '/month'
});
});This pattern works great for product configurators, service packages, and subscription tiers. Visual selection plus instant pricing feedback.
Disabled Options
Mark options as unavailable - out of stock, coming soon, or conditionally restricted based on user type.
form.addRow(row => {
row.addImageChoice('plan', {
label: 'Select Plan',
options: [
{ id: 'free', label: 'Free', assetId: 'plan_free' },
{ id: 'pro', label: 'Pro', assetId: 'plan_pro' },
{
id: 'enterprise',
label: 'Enterprise',
assetId: 'plan_enterprise',
isDisabled: true // Coming soon
}
]
});
});
// Dynamic disabled state
const userType = form.dropdown('userType');
row.addImageChoice('features', {
options: [
{
id: 'advanced',
label: 'Advanced Features',
assetId: 'feat_advanced',
isDisabled: () => userType?.value() !== 'premium'
}
]
});Disabled options appear faded and can't be selected. The reactive version enables/disables based on other form values.
Custom Styling
Override default styles with optionStyles, selectedOptionStyles, and per-option customStyles.
form.addRow(row => {
row.addImageChoice('theme', {
label: 'Select Theme',
optionStyles: {
'border-radius': '16px',
'box-shadow': '0 4px 12px rgba(0,0,0,0.1)'
},
selectedOptionStyles: {
'border-color': '#10b981',
'box-shadow': '0 0 0 3px rgba(16,185,129,0.3)'
},
options: [
{
id: 'light',
label: 'Light',
assetId: 'theme_light',
customStyles: { 'background': '#f8fafc' }
},
{
id: 'dark',
label: 'Dark',
assetId: 'theme_dark',
customStyles: { 'background': '#1e293b' }
}
]
});
});Global option styles apply to all options. Selected styles apply on top when an option is chosen. Per-option styles let you customize individual items.
When to Use Image Choice
Image choice works well for:
- Visual products: Clothing, furniture, vehicles, real estate
- Service tiers: When each tier has a distinct visual identity
- Style preferences: Design themes, color schemes, aesthetics
- Destinations/locations: Travel, venues, neighborhoods
- Categories with icons: Room types, amenities, features
Stick with text-based selection when:
- Options are abstract: "Yes/No", timeframes, quantities
- Many similar options: Long lists where images don't differentiate
- Speed matters most: Quick forms where visuals slow users down
- Images unavailable: Don't use placeholder images
Best Practices
Use consistent image dimensions: All options in one image choice should have the same aspect ratio. Mixed sizes look chaotic.
Optimize image size: Large images slow page load. Resize to the display size - 400px wide is usually enough for cards.
Write clear labels: Even with images, labels matter. "Beach Resort" tells users more than just showing a beach photo.
Limit visible options: More than 6-8 visible options overwhelms users. Use carousel for larger sets, or break into categories.
Test on mobile: Images that look great on desktop may be too small on phones. Consider reducing columns or using carousel on narrow screens.
Common Questions
What image formats are supported?
FormTs supports JPEG, PNG, WebP, and SVG. WebP offers the best compression for photos. SVG works well for icons and illustrations that need to scale.
Can I use external image URLs instead of uploaded assets?
Currently, images must be uploaded as assets to FormTs. This ensures reliable loading and lets us optimize delivery. External URLs could break if the source changes.
How do I handle loading states for images?
Image choice shows a placeholder while images load and an error state if loading fails. The component handles this automatically - no extra code needed.
Can users deselect in single-selection mode?
By default, yes - clicking a selected option deselects it. Set allowDeselect: false to require a selection once one is made, like traditional radio buttons.
How does keyboard navigation work?
Arrow keys move between options. Enter or Space selects. Home and End jump to first and last options. The component is fully keyboard accessible.