Slider Fields for Budget Ranges and Quantities
Some values are easier to set with a slider than a text field. Quantities, budgets, durations, team sizes - these are approximate by nature. A slider lets users explore options intuitively while seeing how their choice affects pricing in real time.
Sliders work best when users don't have an exact number in mind. "About 20 hours per week" is easier to set with a slider than typing "20" into a box. And when each adjustment instantly updates a price, users can find their sweet spot fast.
Basic Slider Setup
At minimum, a slider needs min and max values. The user drags to select any value in that range.
form.addRow(row => {
row.addSlider('quantity', {
label: 'Quantity',
min: 1,
max: 100,
defaultValue: 10
});
});By default, sliders show the current value as the user drags. The step is 1 unless you specify otherwise.
Units and Steps
Most sliders represent something concrete: square feet, hours, dollars, people. Adding a unit label makes the value meaningful. Steps control the granularity.
form.addRow(row => {
row.addSlider('squareFeet', {
label: 'Square Footage',
min: 500,
max: 5000,
step: 100,
defaultValue: 1500,
unit: 'sq ft'
});
});
form.addRow(row => {
row.addSlider('duration', {
label: 'Project Duration',
min: 1,
max: 12,
defaultValue: 3,
unit: 'months'
});
}); The unit property displays after the value: "1500 sq ft" or "3 months". Setting step: 100 means the slider snaps to 500, 600, 700... instead of every integer.
Pro tip
Choose step sizes that make sense for the domain. Square footage in 100s, budgets in 1000s, hours in 5s or 10s. Too granular feels tedious; too coarse feels limiting.
Budget Ranges
Budget is often a range, not a single number. Two sliders - minimum and maximum - let users express "between X and Y". The max slider's minimum can depend on the min slider's value.
const budgetSection = form.addSubform('budget', {
title: 'Your Budget'
});
budgetSection.addRow(row => {
row.addSlider('minBudget', {
label: 'Minimum Budget',
min: 1000,
max: 50000,
step: 1000,
defaultValue: 5000,
unit: '$'
});
});
const minBudget = budgetSection.slider('minBudget');
budgetSection.addRow(row => {
row.addSlider('maxBudget', {
label: 'Maximum Budget',
min: () => (minBudget?.value() || 1000) + 1000,
max: 100000,
step: 1000,
defaultValue: () => (minBudget?.value() || 5000) + 10000,
unit: '$'
});
}); The maximum budget slider's min is reactive - it stays at least $1,000 above the minimum budget. Its default value also updates to stay $10,000 above. Users can't create an invalid range.
Price Integration
Sliders shine when connected to pricing. Change the hours, watch the price update. Change the weeks, price updates again. Instant feedback helps users find the configuration that fits their budget.
const serviceSection = form.addSubform('service', {
title: 'Service Configuration'
});
serviceSection.addRow(row => {
row.addSlider('hours', {
label: 'Hours per Week',
min: 5,
max: 40,
step: 5,
defaultValue: 20,
unit: 'hrs'
});
});
serviceSection.addRow(row => {
row.addSlider('weeks', {
label: 'Number of Weeks',
min: 1,
max: 52,
defaultValue: 4,
unit: 'weeks'
});
});
const hours = serviceSection.slider('hours');
const weeks = serviceSection.slider('weeks');
const hourlyRate = 75;
serviceSection.addRow(row => {
row.addPriceDisplay('totalPrice', {
label: 'Total Cost',
computedValue: () => {
const h = hours?.value() || 0;
const w = weeks?.value() || 0;
return h * w * hourlyRate;
},
currency: '$',
suffix: () => {
const h = hours?.value() || 0;
const w = weeks?.value() || 0;
return ' (' + (h * w) + ' total hours)';
}
});
});Both sliders feed into a computed price. The suffix shows total hours for context: "20 hours × 4 weeks = 80 total hours at $6,000".
See sliders in action in our calculator gallery.
Volume Discounts
Sliders work great for quantity selection with tiered pricing. Order more, pay less per unit. Show the discount tier and per-unit price updating as users drag.
const orderSection = form.addSubform('order', {
title: 'Order Quantity'
});
orderSection.addRow(row => {
row.addSlider('quantity', {
label: 'Units',
min: 10,
max: 1000,
step: 10,
defaultValue: 100,
unit: 'units'
});
});
const quantity = orderSection.slider('quantity');
// Volume discount tiers
const getDiscount = (qty: number): number => {
if (qty >= 500) return 0.25; // 25% off
if (qty >= 250) return 0.15; // 15% off
if (qty >= 100) return 0.10; // 10% off
return 0;
};
const basePrice = 50; // per unit
orderSection.addRow(row => {
row.addTextPanel('discountTier', {
label: 'Volume Discount',
computedValue: () => {
const qty = quantity?.value() || 0;
const discount = getDiscount(qty);
if (discount === 0) return 'Order 100+ units for 10% off';
return (discount * 100) + '% discount applied';
}
});
});
orderSection.addRow(row => {
row.addPriceDisplay('unitPrice', {
label: 'Price per Unit',
computedValue: () => {
const qty = quantity?.value() || 0;
const discount = getDiscount(qty);
return basePrice * (1 - discount);
},
currency: '$',
originalPrice: () => {
const qty = quantity?.value() || 0;
return getDiscount(qty) > 0 ? basePrice : undefined;
}
});
});
orderSection.addRow(row => {
row.addPriceDisplay('totalPrice', {
label: 'Total',
computedValue: () => {
const qty = quantity?.value() || 0;
const discount = getDiscount(qty);
return qty * basePrice * (1 - discount);
},
currency: '$',
variant: 'large'
});
}); As quantity increases past 100, 250, and 500, the discount tier changes. The originalPrice on the price display shows the base price crossed out when a discount applies - visual proof that ordering more saves money.
Team Size Planning
For staffing or resource allocation, multiple sliders can build up a total. Each role gets its own slider, and a computed field shows the aggregate.
const teamSection = form.addSubform('team', {
title: 'Team Size'
});
teamSection.addRow(row => {
row.addSlider('developers', {
label: 'Developers',
min: 1,
max: 20,
defaultValue: 3,
unit: 'people'
});
});
teamSection.addRow(row => {
row.addSlider('designers', {
label: 'Designers',
min: 0,
max: 10,
defaultValue: 1,
unit: 'people'
});
});
teamSection.addRow(row => {
row.addSlider('managers', {
label: 'Project Managers',
min: 0,
max: 5,
defaultValue: 1,
unit: 'people'
});
});
const devs = teamSection.slider('developers');
const designers = teamSection.slider('designers');
const managers = teamSection.slider('managers');
const totalTeam = form.computedValue(() => {
return (devs?.value() || 0) +
(designers?.value() || 0) +
(managers?.value() || 0);
});
teamSection.addRow(row => {
row.addTextPanel('teamSummary', {
label: 'Total Team Size',
computedValue: () => totalTeam() + ' people'
});
});Users adjust each role independently and see the total team size update. This pattern works for any additive configuration: ingredients in a recipe, components in a system, staff for an event.
Capacity and Recommendations
Sliders can drive recommendations. Select a guest count and see venue size suggestions and staffing requirements update automatically.
const eventSection = form.addSubform('event', {
title: 'Event Capacity'
});
eventSection.addRow(row => {
row.addSlider('expectedGuests', {
label: 'Expected Guests',
min: 50,
max: 500,
step: 10,
defaultValue: 150,
unit: 'people'
});
});
const guests = eventSection.slider('expectedGuests');
// Venue recommendations based on capacity
eventSection.addRow(row => {
row.addTextPanel('venueSize', {
label: 'Recommended Venue',
computedValue: () => {
const count = guests?.value() || 0;
if (count <= 100) return 'Small venue (up to 100)';
if (count <= 200) return 'Medium venue (100-200)';
if (count <= 350) return 'Large venue (200-350)';
return 'Extra large venue (350+)';
}
});
});
// Staffing recommendation
eventSection.addRow(row => {
row.addTextPanel('staffing', {
label: 'Recommended Staff',
computedValue: () => {
const count = guests?.value() || 0;
const servers = Math.ceil(count / 20);
const bartenders = Math.ceil(count / 50);
return servers + ' servers, ' + bartenders + ' bartenders';
}
});
});The guest count slider controls everything. Venue recommendations adjust at certain thresholds. Staffing calculations (1 server per 20 guests, 1 bartender per 50) provide concrete planning guidance.
Hiding the Value Display
Sometimes you want a slider without showing the exact number - like a satisfaction scale where the position matters more than the digit.
// Show slider without the live value display
form.addRow(row => {
row.addSlider('satisfaction', {
label: 'How satisfied are you?',
min: 1,
max: 10,
defaultValue: 5,
showValue: false // Hides the number display
});
}); With showValue: false, users see only the slider track and handle. Good for subjective ratings where "high" vs "low" matters more than "7 out of 10".
When to Use Sliders
Sliders work well for:
- Approximate values: "Around 20 hours" not "exactly 17"
- Bounded ranges: When min and max are known and finite
- Exploration: When users want to see how changes affect outcomes
- Continuous values: Where intermediate values make sense
Sliders don't work well for:
- Precise entry: Phone numbers, order IDs, exact amounts
- Wide ranges: 1 to 1,000,000 is too broad to drag meaningfully
- Discrete options: If only 3 values are valid, use radio buttons
- Mobile accessibility: Sliders can be fiddly on touch screens
Best Practices
Show the value: Unless there's a specific reason to hide it, display the current value. Users want confirmation of what they selected.
Use appropriate steps: Match the domain. $1,000 steps for budgets in the tens of thousands. 5-minute steps for time. 10-unit steps for quantities.
Set sensible defaults: The default should be a reasonable starting point, not the minimum. Users often accept defaults, so make them useful.
Add units: "500" means nothing. "500 sq ft" is clear. Always include units for physical quantities.
Connect to outcomes: Sliders are most valuable when they affect something visible - a price, a recommendation, a capacity calculation. The instant feedback is the point.
Common Questions
Can I have a slider with a non-linear scale?
The slider component uses a linear scale by default. For logarithmic or exponential scales, you could map the slider value through a function when reading it - the slider shows 1-100 but your code interprets that as $1,000 to $1,000,000 on a log scale.
How do I handle very large ranges?
Break them into segments with appropriate step sizes. A budget slider from $1,000 to $100,000 might use $1,000 steps. For truly huge ranges, consider a dropdown with ranges ('$10k-$25k', '$25k-$50k') instead of a slider.
Are sliders accessible?
Sliders can be used with keyboard arrow keys to increment/decrement values. The value display helps screen reader users understand the current selection. For critical inputs, consider offering a text field alternative.
Can I have a dual-handle range slider?
Currently, use two separate sliders for min and max values, with the max slider's minimum bound to the min slider's value. This achieves the same result with clearer UX - each slider has a single clear purpose.