Headless slider component. Handles drag interactions, keyboard navigation (arrow keys), and accessibility. Use builder pattern for custom styling.
- Value selection: Let users pick numbers within a range (volume, brightness, price filters)
- Settings controls: Adjust app preferences with visual feedback
- Form inputs: Numeric input with immediate visual feedback
- Custom designs: When you need sliders that match your design system perfectly
See complete examples in our GitHub repository.
import 'package:flutter/material.dart';
import 'package:naked_ui/naked_ui.dart';
class SimpleSlider extends StatefulWidget {
@override
State<SimpleSlider> createState() => _SimpleSliderState();
}
class _SimpleSliderState extends State<SimpleSlider> {
double _value = 0.5;
@override
Widget build(BuildContext context) {
return NakedSlider(
value: _value,
onChanged: (newValue) => setState(() => _value = newValue),
builder: (context, state, child) {
return Container(
width: 200,
height: 20,
child: Stack(
children: [
// Track
Container(
width: 200,
height: 4,
margin: const EdgeInsets.only(top: 8),
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(2),
),
),
// Progress
Container(
width: 200 * _value,
height: 4,
margin: const EdgeInsets.only(top: 8),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(2),
),
),
// Thumb
Positioned(
left: (200 - 20) * _value,
child: Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: state.isDragging ? Colors.blue.shade700 : Colors.blue,
shape: BoxShape.circle,
),
),
),
],
),
);
},
);
}
}Handle hover, focus, and drag states:
builder: (context, state, child) {
// Different colors for different states
Color thumbColor = Colors.blue;
if (state.isDragging) thumbColor = Colors.blue.shade800;
else if (state.isHovered) thumbColor = Colors.blue.shade600;
// Focus indicator
final borderColor = state.isFocused ? Colors.orange : Colors.transparent;
return Stack(
children: [
// Your track containers here...
// Enhanced thumb with focus ring
Positioned(
left: (200 - 24) * _value,
child: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
color: thumbColor,
shape: BoxShape.circle,
border: Border.all(color: borderColor, width: 2),
// Add shadow when dragging
boxShadow: state.isDragging ? [
BoxShadow(
color: Colors.blue.withOpacity(0.3),
blurRadius: 8,
spreadRadius: 2,
),
] : null,
),
),
),
],
);
}Row(
children: [
Icon(Icons.volume_down),
const SizedBox(width: 12),
Expanded(
child: NakedSlider(
value: _volume,
onChanged: (value) => setState(() => _volume = value),
builder: (context, state, child) {
// Your slider design with volume-specific styling
return YourSliderDesign();
},
),
),
const SizedBox(width: 12),
Icon(Icons.volume_up),
],
)NakedSlider(
value: _rating,
min: 1,
max: 5,
divisions: 4, // Creates 5 discrete steps (1, 2, 3, 4, 5)
onChanged: (value) => setState(() => _rating = value.round().toDouble()),
builder: (context, state, child) {
// Show star rating or discrete indicators
return YourStarRating();
},
)For complex designs, you can use CustomPaint for pixel-perfect control. Check out our complete CustomPaint examples in the repository.
const NakedSlider({
Key? key,
this.child,
this.builder,
required this.value,
this.min = 0.0,
this.max = 1.0,
this.onChanged,
this.onDragStart,
this.onDragEnd,
this.onHoverChange,
this.onDragChange,
this.onFocusChange,
this.enabled = true,
this.mouseCursor = SystemMouseCursors.click,
this.enableFeedback = true,
this.focusNode,
this.autofocus = false,
this.direction = Axis.horizontal,
this.divisions,
this.keyboardStep = 0.01,
this.largeKeyboardStep = 0.1,
this.semanticLabel,
this.semanticFormatterCallback,
this.excludeSemantics = false,
})Optional static child to display. Provide either child or builder.
Optional builder that receives the current NakedSliderState along with the child. Use this for reactive styling without a StatefulWidget.
The current value of the slider.
Minimum value of the slider. Defaults to 0.0.
Maximum value of the slider. Defaults to 1.0.
Called when the slider value changes.
Called when the user starts dragging the slider.
Called when the user ends dragging the slider.
Called when hover state changes. Provides the current hover state (true when hovered, false otherwise).
Called when dragging state changes. Provides the current dragging state (true when dragging, false otherwise).
Called when focus state changes. Provides the current focus state (true when focused, false otherwise).
Whether the slider is enabled. When true, the slider will respond to user interaction. Defaults to true.
Optional focus node to control focus behavior.
Whether to automatically request focus when first built. Defaults to false.
Direction of the slider. Can be horizontal (left to right) or vertical (bottom to top). Defaults to Axis.horizontal.
Number of discrete divisions. If null, the slider will be continuous.
Step size for keyboard navigation. This value is used when arrow keys are pressed to increment or decrement the slider value. Defaults to 0.01 (1% of the slider range).
Step size for large keyboard navigation. This value is used when arrow keys are pressed while holding Shift. Defaults to 0.1 (10% of the slider range).
Cursor to show when hovering. Defaults to click.
Whether to provide haptic feedback on drag start. Defaults to true.
Semantic label for screen readers.