SELECTION CONTROLS

Range Slider

A single, reusable Flutter range slider built on SliderTheme so it inherits the FlutterKit house look. Two white thumbs drag a lavender selected band between an inset-grey rest, with continuous or discrete divisions, an optional label row showing the live low–high values, and a disabled state. No external packages, fully DartPad-ready.

rangesliderfilter
Preview6 variants

Mid band

Narrow

Wide

What's Included

  • Two draggable thumbs selecting a low and high bound
  • Lavender selected band with an inset-grey inactive track
  • Continuous drag or discrete divisions with value bubbles
  • Optional label row showing the live low – high values
  • Disabled look — muted track and thumbs, drag ignored
  • One AppRangeSlider wrapping SliderTheme for the house theme

Use Cases

  • Price range filters on a shop or marketplace screen
  • Age or distance ranges in a search form
  • Time-of-day windows for availability or scheduling
  • Any min–max numeric constraint captured on one track

PRO TIP

Clamp the two thumbs so they can never cross — RangeSlider does this for you, but make sure your own state keeps start <= end before you store it. Surface the live low and high values next to the track so the band is never ambiguous, and use divisions only when the bounds are genuinely discrete (price buckets, star counts) so a smooth range like distance stays smooth.

Complete Dart Code

Copy this into your Flutter project. No external packages required.

dart
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  RangeValues _price = const RangeValues(20, 80);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        backgroundColor: const Color(0xFFFAFAFB),
        body: Center(
          child: Padding(
            padding: const EdgeInsets.all(24),
            child: AppRangeSlider(
              values: _price,
              label: 'Price range',
              onChanged: (v) => setState(() => _price = v),
            ),
          ),
        ),
      ),
    );
  }
}

/// FlutterKit Range Slider — house design system (Linear lavender, light).
/// Two-thumb range built on SliderTheme; continuous or discrete.
/// rohansurve.in/flutterkit/range-slider

class AppRangeSlider extends StatelessWidget {
  const AppRangeSlider({
    super.key,
    required this.values,
    this.onChanged,
    this.min = 0,
    this.max = 100,
    this.divisions,
    this.label,
    this.enabled = true,
  });

  /// Current low/high bounds.
  final RangeValues values;

  /// Called with the next range while dragging.
  final ValueChanged<RangeValues>? onChanged;

  /// Range limits.
  final double min;
  final double max;

  /// Discrete steps — snaps the thumbs and shows value bubbles.
  final int? divisions;

  /// Optional label; the live low – high values show on the right.
  final String? label;

  /// Disabled look — muted track and thumbs, drag ignored.
  final bool enabled;

  // ── House tokens — docs/flutterkit-design.md §1 ──
  static const Color _accent = Color(0xFF5E6AD2);
  static const Color _surfaceInset = Color(0xFFEEEFF2);
  static const Color _surfaceMuted = Color(0xFFF4F5F7);
  static const Color _ink = Color(0xFF1C1E26);
  static const Color _inkMuted = Color(0xFF51555E);
  static const Color _inkTertiary = Color(0xFFB4B8BF);

  @override
  Widget build(BuildContext context) {
    final Color active = enabled ? _accent : _surfaceMuted;
    final Color thumb = enabled ? Colors.white : const Color(0xFFF7F7F8);

    final slider = SliderTheme(
      data: SliderThemeData(
        trackHeight: 5,
        activeTrackColor: active,
        inactiveTrackColor: _surfaceInset,
        thumbColor: thumb,
        overlayColor: const Color(0x1F5E6AD2),
        valueIndicatorColor: _accent,
        rangeThumbShape: const RoundRangeSliderThumbShape(
          enabledThumbRadius: 10,
          elevation: 2,
        ),
        overlayShape: const RoundSliderOverlayShape(overlayRadius: 18),
        rangeTrackShape: const RoundedRectRangeSliderTrackShape(),
        showValueIndicator: ShowValueIndicator.onlyForDiscrete,
      ),
      child: RangeSlider(
        values: RangeValues(
          values.start.clamp(min, max),
          values.end.clamp(min, max),
        ),
        min: min,
        max: max,
        divisions: divisions,
        labels: divisions != null
            ? RangeLabels(
                values.start.round().toString(),
                values.end.round().toString(),
              )
            : null,
        onChanged: enabled ? onChanged : null,
      ),
    );

    if (label == null) return slider;

    return Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 4),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(
                label!,
                style: TextStyle(
                  color: enabled ? _inkMuted : _inkTertiary,
                  fontSize: 13,
                  fontWeight: FontWeight.w500,
                ),
              ),
              Text(
                values.start.round().toString() +
                    ' – ' +
                    values.end.round().toString(),
                style: TextStyle(
                  color: enabled ? _ink : _inkTertiary,
                  fontSize: 13,
                  fontWeight: FontWeight.w600,
                ),
              ),
            ],
          ),
        ),
        slider,
      ],
    );
  }
}

// ── Usage ──
// AppRangeSlider(values: v, onChanged: (n) {})                     // continuous
// AppRangeSlider(values: v, divisions: 10, onChanged: (n) {})      // discrete
// AppRangeSlider(values: v, label: 'Price', onChanged: (n) {})     // labelled
// const AppRangeSlider(values: RangeValues(20, 80), enabled: false)// disabled

How to use this widget

  1. 1Copy the complete Dart code above
  2. 2Create a new file — app_range_slider.dart in your Flutter project
  3. 3Use AppRangeSlider(values: v, onChanged: (n) {}) — switch the look with the min/max, divisions, label, and enabled parameters

Related components

Selection Controls

Slider

Themed value slider — lavender track, optional value bubble, discrete steps & disabled

View component →
Selection Controls

Switch Toggle

Animated on/off switch — lavender track, two sizes, with label and disabled states

View component →
Selection Controls

Checkbox

Tristate checkbox — unchecked, checked & indeterminate, with label, disabled & error states

View component →