Slider
Themed value slider — lavender track, optional value bubble, discrete steps & disabled
View component →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.
What's Included
Use Cases
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.
Copy this into your Flutter project. No external packages required.
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)// disabledThemed value slider — lavender track, optional value bubble, discrete steps & disabled
View component →Animated on/off switch — lavender track, two sizes, with label and disabled states
View component →Tristate checkbox — unchecked, checked & indeterminate, with label, disabled & error states
View component →