A range slider that shows users what they are filtering
reading · 00%

A range slider that shows users what they are filtering

A custom Retool filter control with the data distribution built in.

Published
Reading4 min
CategoryRetool
Words882

A recruiting app filters candidates by years of experience. Most sit between 0 and 20, a handful run up to 45, and the slider spans 0 to 45. A standard range slider sets the bounds, and shows nothing about where the candidates actually are. Is the 20 to 45 range busy or nearly empty? The user finds out by filtering and counting rows.

We built a range slider with the distribution drawn on it, so the filter shows its own data. It is a Retool custom component written in TypeScript, published on GitHub under the MIT license.

What the histogram changes

With the distribution drawn above the handles, the experience filter answers the question at a glance: 20 to 45 years is close to empty. Uneven data is common, so the component offers three scales. On a logarithmic scale, a bar of 12,500 and a bar of 1 both stay visible.

Features

  • Histogram. The data's distribution drawn directly on the slider.

  • Scales. Linear, logarithmic, or square root, for skewed data.

  • Range selection. Drag the handles, with live feedback.

  • Click to select. Click a histogram bar to select its range.

  • Flexible input. Accepts buckets as an array of objects or an object of arrays, straight from a query.

  • Custom formatting. A JavaScript function controls how values are displayed.

  • Negative values. Optionally drawn below the axis for data that crosses zero.

  • Tooltips. Reposition themselves so they never get cut off.

  • Styling. Colors, label, and step size are all properties.

Install and deploy

You need Node.js 20 or later and an API access token with read and write scopes for Custom Component Libraries.

Shell
git clone https://github.com/StackdropCO/custom-range-slider-retool-component.git
cd custom-range-slider-retool-component
npm install
npx retool-ccl login

Develop with live sync into your Retool organization, then deploy an immutable version when it is ready:

Shell
npm run dev
npm run deploy

In the app's Custom Component settings, switch from dev to the version you just published. For self-hosted or multi-instance setups, npx retool-ccl sync distributes the library across instances.

Set up the slider

Drag the Custom Range Slider onto the canvas and set its range properties. Each accepts a static value or a binding.

  • min and max, the ends of the slider (defaults 0 and 100).

  • defaultStart and defaultEnd, the range selected on load.

  • step, the increment.

  • label, shown above the slider.

For the experience filter, that means min 0, max 45, step 1, and a label of "Years of experience".

Feed it the distribution

distributionData takes buckets with a minimum, a maximum, and a count. A query can build them. This Postgres query groups candidates into 5-year buckets:

SQL
select
  floor(years_experience / 5) * 5     as "min",
  floor(years_experience / 5) * 5 + 5 as "max",
  count(*)                            as "count"
from candidates
group by 1, 2
order by 1;

The component accepts buckets in either of two shapes. These are the examples from the repository:

JSON
[
  { "min": 0, "max": 10, "count": 5 },
  { "min": 10, "max": 20, "count": 8 },
  { "min": 20, "max": 30, "count": 3 }
]
JSON
{
  "bucket_index": [0, 1, 2],
  "bucket_min": [0, 10, 20],
  "bucket_max": [10, 20, 30],
  "count": [5, 8, 3]
}

Bind it with {{ getExperienceBuckets.dataArray }}.

Choose a bucket width that matches how people think about the value. Five-year bands suit experience. Price bands might be 10 or 100, depending on the catalog. Buckets much narrower than the slider's step add noise without helping anyone choose. Set histogramScale to logarithmic when one bucket dwarfs the rest, and turn on showNegativeValues if the data crosses zero.

Format the values

formatterFunction takes a JavaScript function as a string. This one labels years:

JavaScript
(v) => {
  if (v === 0) return `${v}`;
  if (v === 1) return '1 year';

  return `${v} years`;
}

Currency and dates follow the same pattern. The repository's examples:

JavaScript
// Currency
v => `$${v.toFixed(2)}`

// Unix timestamps as dates, with moment
v => moment.unix(v).format('MMM DD')

Use the selected range

The component exposes selectedRange as { start, end } and fires rangeChanged when it moves. Point the filtered query at it:

SQL
select *
from candidates
where years_experience between {{ customRangeSlider1.selectedRange.start }}
                           and {{ customRangeSlider1.selectedRange.end }}
order by years_experience;

Then add a rangeChanged handler that runs that query. In JavaScript, the same values destructure directly:

JavaScript
const { start: startValue, end: endValue } = customRangeSlider1.selectedRange;

Before production

  • Pin the app to a published version of the library, never the dev version.

  • Test with your real distribution, including the empty and skewed ranges.

  • Check the formatter on the smallest and largest values the data can hold.

  • Make sure the bucket query and the filtered query read the same table with the same conditions, so the histogram matches the rows.

What is next

On the roadmap, not committed: histograms generated from a plain number[], finer control over bar spacing and styling, optional statistics such as the mean and median, and more scale types.

Our view

We recommend a histogram slider wherever a numeric filter covers skewed data, because users then pick ranges that contain rows. For evenly spread data, Retool's built-in Range Slider is the simpler choice, because it already matches the app's theme.

Issues and pull requests are welcome. More components are in awesome-retool-components, and what is a custom Retool component covers when building one is worth it.

Filed underRetool
Worth passing along? Share this note.
LinkedInX
Newsletter

Get the next one in your inbox

We write when something is worth reading. Subscribing means you are happy to hear from us when that happens. Leave any time. Privacy policy.

Vaggelis Kapetanakis

Keep readingRetool
Related notes

Still here? Good sign

More notes from real builds, written up for the next person who needs one.

All articles
ContactLet's chat
Let's chat

Tell us what
you're building

Describe the process that is slowing your team down: the inbox, the spreadsheet, the portal nobody owns. We'll come back with a concrete approach, a rough timeline, and, where it helps, a working prototype within weeks.

What happens next
  1. We read every messageAn architect reads what you sent before anyone replies.
  2. You hear back in ≤ 2 daysA short reply with first questions and a suggested next step.
  3. We scope and prototypeA scoping call, then a working surface, not a slide deck.
Where are you in the process?