Switch Toggle
Animated on/off switch — lavender track, two sizes, with label and disabled states
View component →A single, reusable Flutter slider built on SliderTheme so it inherits the FlutterKit house look. The active track fills lavender, the inactive track is an inset grey, and the white thumb carries a soft shadow. It supports a continuous drag, discrete divisions with a value bubble, an optional leading label row, and a disabled state. No external packages, fully DartPad-ready.
What's Included
Use Cases
PRO TIP
Reach for divisions only when the underlying value is genuinely discrete (a 1–5 rating, 10% steps) — a continuous control like brightness should stay smooth, since snapping fights the user. Always surface the current value (a bubble while dragging or a label beside the track) so the slider is never a guessing game, and round it in onChanged before you store it so your state matches what the user sees.
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> {
double _volume = 60;
double _rating = 3;
@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: Column(
mainAxisSize: MainAxisSize.min,
children: [
AppSlider(
value: _volume,
label: 'Volume',
onChanged: (v) => setState(() => _volume = v),
),
const SizedBox(height: 24),
AppSlider(
value: _rating,
min: 1,
max: 5,
divisions: 4,
label: 'Rating',
onChanged: (v) => setState(() => _rating = v),
),
const SizedBox(height: 24),
const AppSlider(value: 40, label: 'Disabled', enabled: false),
],
),
),
),
),
);
}
}
/// FlutterKit Slider — house design system (Linear lavender, light theme).
/// One value-driven slider built on SliderTheme; continuous or discrete.
/// rohansurve.in/flutterkit/slider
class AppSlider extends StatelessWidget {
const AppSlider({
super.key,
required this.value,
this.onChanged,
this.min = 0,
this.max = 100,
this.divisions,
this.label,
this.enabled = true,
});
/// Current value, between [min] and [max].
final double value;
/// Called with the next value while dragging.
final ValueChanged<double>? onChanged;
/// Range bounds.
final double min;
final double max;
/// Discrete steps — snaps the thumb and shows a value bubble.
final int? divisions;
/// Optional leading label; the live value shows on the right.
final String? label;
/// Disabled look — muted track and thumb, 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,
valueIndicatorTextStyle: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w600,
),
thumbShape: const RoundSliderThumbShape(
enabledThumbRadius: 10,
elevation: 2,
pressedElevation: 4,
),
overlayShape: const RoundSliderOverlayShape(overlayRadius: 20),
trackShape: const RoundedRectSliderTrackShape(),
showValueIndicator: ShowValueIndicator.onlyForDiscrete,
),
child: Slider(
value: value.clamp(min, max),
min: min,
max: max,
divisions: divisions,
label: divisions != null ? value.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(
value.round().toString(),
style: TextStyle(
color: enabled ? _ink : _inkTertiary,
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
],
),
),
slider,
],
);
}
}
// ── Usage ──
// AppSlider(value: v, onChanged: (n) {}) // continuous
// AppSlider(value: v, min: 1, max: 5, divisions: 4, onChanged: (n){})// discrete
// AppSlider(value: v, label: 'Volume', onChanged: (n) {}) // labelled
// const AppSlider(value: 40, enabled: false) // disabledAnimated 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 →One enum-driven chip — basic, input, choice & status kinds, lavender selected state
View component →