Feature Guide

Suggestion Chips for Quick Selection

January 2026 · 9 min read

Checkboxes work, but they're slow. When users need to pick multiple items from a list, chip-based selection is faster and more visual. One tap to select, another to deselect. No checkbox hunting, no wondering if you clicked the right thing.

Suggestion chips are perfect for tags, categories, features, interests - anywhere you want quick multi-select from predefined options. They work especially well on mobile where tap targets matter.

This guide covers when to use chips, how to configure them, and patterns for dynamic options and validation. For hands-on practice, try the interactive tutorial.

Basic Suggestion Chips

The simplest use case: a list of options users can tap to select. Selected chips get highlighted, tapping again deselects.

const form = builder.form;

const interestsSection = form.addSubform('interests', {
    title: 'Your Interests'
});

interestsSection.addRow(row => {
    row.addSuggestionChips('topics', {
        label: 'What topics interest you?',
        suggestions: [
            { id: 'tech', name: 'Technology' },
            { id: 'business', name: 'Business' },
            { id: 'design', name: 'Design' },
            { id: 'marketing', name: 'Marketing' },
            { id: 'finance', name: 'Finance' },
            { id: 'health', name: 'Health & Wellness' },
            { id: 'travel', name: 'Travel' },
            { id: 'food', name: 'Food & Cooking' }
        ]
    });
});

The suggestions array defines available options. Each needs an id (stored as the value) and name (displayed to users). The field value is an array of selected IDs.

Min/Max Selection Constraints

Sometimes you need users to pick a specific number of items. "Choose your top 3 skills" or "Select 1-3 categories." Use min and max to enforce this.

interestsSection.addRow(row => {
    row.addSuggestionChips('skills', {
        label: 'Select your top 3-5 skills',
        suggestions: [
            { id: 'js', name: 'JavaScript' },
            { id: 'python', name: 'Python' },
            { id: 'react', name: 'React' },
            { id: 'node', name: 'Node.js' },
            { id: 'sql', name: 'SQL' },
            { id: 'aws', name: 'AWS' },
            { id: 'docker', name: 'Docker' },
            { id: 'git', name: 'Git' }
        ],
        min: 3,
        max: 5,
        isRequired: true
    });
});

// Show validation message
interestsSection.addRow(row => {
    row.addTextPanel('skillsHint', {
        label: () => {
            const selected = interestsSection.suggestionChips('skills')?.value()?.length ?? 0;
            if (selected < 3) return `Select at least ${3 - selected} more`;
            if (selected >= 3 && selected <= 5) return `${selected} selected (3-5 required)`;
            return 'Maximum 5 selections';
        }
    });
});

The dynamic hint shows users where they stand. It updates as they select and deselect chips, guiding them to the valid range.

Pro tip

When using min/max, make sure your suggestion count makes sense. "Select 3-5 skills" from a list of 4 options forces awkward choices. Provide at least 2x your minimum for meaningful selection.

Conditional Follow-Up

Chips work well with conditional logic. Show additional fields based on what users select - especially useful for "Other" options.

const feedbackSection = form.addSubform('feedback', {
    title: 'What do you like about our product?'
});

feedbackSection.addRow(row => {
    row.addSuggestionChips('likes', {
        label: 'Select all that apply',
        suggestions: [
            { id: 'ease', name: 'Easy to use' },
            { id: 'fast', name: 'Fast performance' },
            { id: 'design', name: 'Great design' },
            { id: 'features', name: 'Feature-rich' },
            { id: 'support', name: 'Good support' },
            { id: 'price', name: 'Fair pricing' },
            { id: 'integration', name: 'Integrations' },
            { id: 'other', name: 'Other' }
        ]
    });
});

// Show text field if "Other" is selected
feedbackSection.addRow(row => {
    row.addTextbox('otherLikes', {
        label: 'What else do you like?',
        isVisible: () => feedbackSection.suggestionChips('likes')?.value()?.includes('other'),
        isRequired: () => feedbackSection.suggestionChips('likes')?.value()?.includes('other')
    });
});

// Show follow-up for specific selections
feedbackSection.addRow(row => {
    row.addTextarea('featureDetails', {
        label: 'Which features do you use most?',
        placeholder: 'Tell us about your favorite features...',
        rows: 2,
        isVisible: () => feedbackSection.suggestionChips('likes')?.value()?.includes('features')
    });
});

The "Other" pattern is common: include it as a chip, then show a text field when selected. This keeps the main options clean while allowing custom input.

Dynamic Options

Chip options can change based on other form values. A technology selector that updates based on project type, for example.

const projectSection = form.addSubform('project', {
    title: 'Project Setup'
});

projectSection.addRow(row => {
    row.addDropdown('projectType', {
        label: 'What type of project?',
        options: [
            { id: 'web', name: 'Web Application' },
            { id: 'mobile', name: 'Mobile App' },
            { id: 'api', name: 'API/Backend' },
            { id: 'data', name: 'Data/Analytics' }
        ]
    });
});

// Chips change based on project type
projectSection.addRow(row => {
    row.addSuggestionChips('technologies', {
        label: 'Select technologies',
        suggestions: () => {
            const type = projectSection.dropdown('projectType')?.value();

            if (type === 'web') {
                return [
                    { id: 'react', name: 'React' },
                    { id: 'vue', name: 'Vue' },
                    { id: 'angular', name: 'Angular' },
                    { id: 'svelte', name: 'Svelte' },
                    { id: 'next', name: 'Next.js' },
                    { id: 'tailwind', name: 'Tailwind CSS' }
                ];
            }

            if (type === 'mobile') {
                return [
                    { id: 'react-native', name: 'React Native' },
                    { id: 'flutter', name: 'Flutter' },
                    { id: 'swift', name: 'Swift' },
                    { id: 'kotlin', name: 'Kotlin' },
                    { id: 'ionic', name: 'Ionic' }
                ];
            }

            if (type === 'api') {
                return [
                    { id: 'node', name: 'Node.js' },
                    { id: 'python', name: 'Python' },
                    { id: 'go', name: 'Go' },
                    { id: 'rust', name: 'Rust' },
                    { id: 'graphql', name: 'GraphQL' },
                    { id: 'rest', name: 'REST' }
                ];
            }

            if (type === 'data') {
                return [
                    { id: 'python', name: 'Python' },
                    { id: 'sql', name: 'SQL' },
                    { id: 'spark', name: 'Spark' },
                    { id: 'tensorflow', name: 'TensorFlow' },
                    { id: 'pandas', name: 'Pandas' },
                    { id: 'tableau', name: 'Tableau' }
                ];
            }

            return [];
        }
    });
});

The suggestions property accepts a function that returns options. When the project type changes, the technology chips update automatically. Users always see relevant choices.

Pricing Integration

When chips represent add-ons with costs, show prices in the labels and calculate totals based on selection.

const serviceSection = form.addSubform('service', {
    title: 'Service Configuration'
});

// Track prices for each add-on
const addonPrices: Record<string, number> = {
    'priority': 50,
    'express': 100,
    'insurance': 25,
    'tracking': 15,
    'signature': 10,
    'fragile': 20
};

serviceSection.addRow(row => {
    row.addSuggestionChips('addons', {
        label: 'Add-on Services',
        suggestions: [
            { id: 'priority', name: 'Priority Handling (+$50)' },
            { id: 'express', name: 'Express Delivery (+$100)' },
            { id: 'insurance', name: 'Insurance (+$25)' },
            { id: 'tracking', name: 'Live Tracking (+$15)' },
            { id: 'signature', name: 'Signature Required (+$10)' },
            { id: 'fragile', name: 'Fragile Handling (+$20)' }
        ]
    });
});

// Calculate add-on total
const addonsTotal = form.computedValue(() => {
    const selected = serviceSection.suggestionChips('addons')?.value() ?? [];
    return selected.reduce((sum, id) => sum + (addonPrices[id] ?? 0), 0);
});

serviceSection.addRow(row => {
    row.addPriceDisplay('addonsCost', {
        label: 'Add-ons Total',
        computedValue: () => addonsTotal(),
        isVisible: () => addonsTotal() > 0
    });
});

This pattern makes add-on selection fast and transparent. Users see exactly what each option costs, and the running total updates as they select.

See suggestion chips in our example gallery.

Chips vs Checkbox Lists

Both handle multi-select, so when do you use which?

// Checkbox List - vertical, good for longer options with descriptions
form.addRow(row => {
    row.addCheckboxList('notifications', {
        label: 'Email Notifications',
        options: [
            { id: 'weekly', name: 'Weekly digest - Summary of activity every Monday' },
            { id: 'mentions', name: 'Mentions - When someone mentions you' },
            { id: 'replies', name: 'Replies - Responses to your posts' },
            { id: 'news', name: 'Product news - Updates and new features' }
        ],
        orientation: 'vertical'
    });
});

// Suggestion Chips - horizontal, good for short labels
form.addRow(row => {
    row.addSuggestionChips('tags', {
        label: 'Quick Tags',
        suggestions: [
            { id: 'urgent', name: 'Urgent' },
            { id: 'review', name: 'Needs Review' },
            { id: 'blocked', name: 'Blocked' },
            { id: 'done', name: 'Done' },
            { id: 'wip', name: 'In Progress' }
        ]
    });
});

Use Suggestion Chips When:

  • Labels are short (1-3 words)
  • Quick selection matters more than detailed descriptions
  • You want a more visual, modern feel
  • Mobile users are a priority (better tap targets)
  • Options might wrap to multiple lines

Use Checkbox Lists When:

  • Options need longer descriptions
  • You want a traditional form feel
  • Options should stay in a fixed vertical order
  • Individual options have their own required status

Alignment Options

Chips can be left-aligned, centered, or right-aligned. Choose based on your layout and content.

// Left-aligned (default) - works for most cases
form.addRow(row => {
    row.addSuggestionChips('leftAligned', {
        label: 'Categories',
        suggestions: [...],
        alignment: 'left'
    });
});

// Center-aligned - good for short lists, emphasis
form.addRow(row => {
    row.addSuggestionChips('centered', {
        label: 'Choose Your Plan',
        suggestions: [
            { id: 'starter', name: 'Starter' },
            { id: 'pro', name: 'Professional' },
            { id: 'enterprise', name: 'Enterprise' }
        ],
        alignment: 'center'
    });
});

// Right-aligned - rare, but useful for RTL or specific layouts
form.addRow(row => {
    row.addSuggestionChips('rightAligned', {
        label: 'Quick Actions',
        suggestions: [...],
        alignment: 'right'
    });
});

Left-aligned is the default and works for most cases. Center-aligned works well for short lists or when you want emphasis. Right-aligned is rare but useful for specific layouts.

Default Values

Pre-select common or recommended options to speed up form completion.

// Pre-select common options
form.addRow(row => {
    row.addSuggestionChips('features', {
        label: 'Included Features',
        suggestions: [
            { id: 'ssl', name: 'SSL Certificate' },
            { id: 'cdn', name: 'CDN' },
            { id: 'backup', name: 'Daily Backups' },
            { id: 'support', name: '24/7 Support' },
            { id: 'analytics', name: 'Analytics' },
            { id: 'api', name: 'API Access' }
        ],
        // Pre-select standard features
        defaultValue: ['ssl', 'cdn', 'backup']
    });
});

Default selections should represent what most users want. Don't pre-select upsells or premium features - that feels manipulative. Pre-select genuinely common choices.

Accessibility Considerations

Chips need proper accessibility support:

  • Keyboard navigation: Tab to the chip group, arrow keys to move between chips, Space/Enter to toggle
  • Screen readers: Each chip announces as "checkbox, [name], [checked/unchecked]"
  • Focus indicators: Clear visual focus state when navigating with keyboard
  • Color contrast: Selected vs unselected state must be distinguishable without color alone

FormTs handles these automatically. The chips render as proper accessible controls with ARIA attributes.

Common Use Cases

Interest/Topic Selection

Newsletter signups, content preferences, profile customization. "What topics interest you?" with 5-10 options.

Feature Selection

Product configuration, plan comparison, service add-ons. "Select features to include" with pricing.

Tagging

Issue trackers, content management, categorization. Quick tag assignment from predefined options.

Skill/Experience

Job applications, freelancer profiles, team matching. "Select your skills" with optional constraints.

Feedback Reasons

Support tickets, cancellation surveys, NPS follow-up. "What influenced your rating?" with multiple selections.

Common Questions

Can users add custom chips not in the suggestions?

Not with the built-in suggestion chips component. If you need custom input, add an 'Other' chip that reveals a text field when selected. This keeps the chip interface clean while allowing custom values.

How many chips is too many?

More than 10-12 chips can feel overwhelming. If you have more options, consider grouping them into categories with separate chip groups, or using a searchable multi-select instead. The point of chips is quick selection, which breaks down with too many options.

Can I style selected chips differently?

Yes, through CSS. Selected chips have a distinct state class. You can customize colors, borders, and backgrounds for both selected and unselected states. Consider your brand colors but maintain clear visual distinction between states.

Do chips work well in narrow containers?

Yes, chips wrap naturally to fit container width. On mobile or in narrow sidebars, they'll stack into multiple rows. The tap targets remain large enough for easy selection. Just ensure the container isn't so narrow that chips wrap awkwardly mid-word.

Build with Suggestion Chips

Make multi-select fast and visual with chip-based selection.