RA Segmented Switch
A highly customizable, animated segmented switch widget for Flutter applications. Built on top of Flutter's TabBar with a beautiful pill-style indicator, offering smooth animations, extensive theming options, and seamless integration with your app's design system.

Table of Contents
- Features
- Installation
- Quick Start
- Usage Examples
- API Reference
- Customization Guide
- Migration Guide
- Contributing
- License
Features
Core Features
- Multiple Size Options: Three predefined sizes (small, medium, large) with customizable dimensions
- Flexible Icon Support: Icons on left, right, or center with full customization
- Smooth Animations: Built-in animations with customizable duration and easing curves
- Auto-Scrolling: Intelligent scrolling behavior when content exceeds available space
- Theme Integration: Seamless integration with Flutter's theme system and Material Design
Customization Features
- Custom Colors: Full control over background, indicator, text, and shadow colors
- Border Radius: Adjustable corner radius from sharp to fully rounded
- Typography: Custom text styles for selected and unselected states
- Shadow Effects: Configurable shadow color and opacity
- Responsive Design: Adapts to different screen sizes and orientations
Developer Experience
- Type Safety: Full Dart type safety with comprehensive documentation
- Performance Optimized: Efficient rendering with minimal rebuilds
- Accessibility Ready: Built-in accessibility support with proper semantics
- Easy Integration: Simple API that works with existing Flutter apps
- Robust Error Handling: Graceful handling of edge cases and invalid states
Installation
Add ra_segmented_switch
to your pubspec.yaml
file:
dependencies:
ra_segmented_switch: ^0.0.1
Then run:
flutter pub get
Import the package in your Dart code:
import 'package:ra_segmented_switch/ra_segmented_switch.dart';
Quick Start
Here's a simple example to get you started:
import 'package:flutter/material.dart';
import 'package:ra_segmented_switch/ra_segmented_switch.dart';
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: RASegmentedSwitch(
items: [
RASegmentedSwitchItem(
label: 'Home',
leftIcon: Icons.home,
),
RASegmentedSwitchItem(
label: 'Search',
leftIcon: Icons.search,
),
RASegmentedSwitchItem(
label: 'Profile',
leftIcon: Icons.person,
),
],
onTap: (index) {
print('Selected: $index');
},
),
),
),
);
}
}
Usage Examples
Basic Usage
Simple Text Segments
RASegmentedSwitch(
items: [
RASegmentedSwitchItem(label: 'Option 1'),
RASegmentedSwitchItem(label: 'Option 2'),
RASegmentedSwitchItem(label: 'Option 3'),
],
onTap: (index) {
print('Selected index: $index');
},
)
With Icons
RASegmentedSwitch(
items: [
RASegmentedSwitchItem(
label: 'Home',
leftIcon: Icons.home,
),
RASegmentedSwitchItem(
label: 'Favorites',
leftIcon: Icons.favorite,
),
RASegmentedSwitchItem(
label: 'Settings',
rightIcon: Icons.settings,
),
],
initialIndex: 0,
size: RASegmentedSwitchSize.medium,
onTap: (index) {
// Handle selection
},
)
Advanced Customization
Theme Toggle Switch
class ThemeToggle extends StatefulWidget {
@override
_ThemeToggleState createState() => _ThemeToggleState();
}
class _ThemeToggleState extends State<ThemeToggle> {
int selectedIndex = 0;
@override
Widget build(BuildContext context) {
final isDark = selectedIndex == 0;
return RASegmentedSwitch(
items: [
RASegmentedSwitchItem(
label: 'Dark',
leftIcon: Icons.dark_mode,
),
RASegmentedSwitchItem(
label: 'Light',
leftIcon: Icons.light_mode,
),
],
initialIndex: selectedIndex,
backgroundColor: isDark
? const Color(0xFF2C2C2E)
: const Color(0xFFF2F2F7),
indicatorColor: isDark
? const Color(0xFF1C1C1E)
: Colors.white,
selectedLabelStyle: TextStyle(
color: isDark ? Colors.white : Colors.black,
fontWeight: FontWeight.w600,
fontSize: 14,
),
unselectedLabelStyle: TextStyle(
color: isDark ? Colors.grey[400] : Colors.grey[600],
fontWeight: FontWeight.w400,
fontSize: 14,
),
shadowColor: isDark
? Colors.black.withOpacity(0.3)
: Colors.grey.withOpacity(0.2),
borderRadius: 12,
size: RASegmentedSwitchSize.medium,
onTap: (index) {
setState(() {
selectedIndex = index;
});
// Apply theme change
},
);
}
}
Navigation Tabs
class NavigationExample extends StatefulWidget {
@override
_NavigationExampleState createState() => _NavigationExampleState();
}
class _NavigationExampleState extends State<NavigationExample> {
int currentIndex = 0;
final List<Widget> pages = [
HomePage(),
SearchPage(),
FavoritesPage(),
ProfilePage(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Navigation Example'),
bottom: PreferredSize(
preferredSize: Size.fromHeight(60),
child: Padding(
padding: EdgeInsets.all(16),
child: RASegmentedSwitch(
items: [
RASegmentedSwitchItem(
label: 'Home',
leftIcon: Icons.home_outlined,
),
RASegmentedSwitchItem(
label: 'Search',
leftIcon: Icons.search_outlined,
),
RASegmentedSwitchItem(
label: 'Favorites',
leftIcon: Icons.favorite_outline,
),
RASegmentedSwitchItem(
label: 'Profile',
leftIcon: Icons.person_outline,
),
],
initialIndex: currentIndex,
size: RASegmentedSwitchSize.large,
backgroundColor: Theme.of(context).primaryColor.withOpacity(0.1),
indicatorColor: Theme.of(context).primaryColor,
selectedLabelStyle: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
onTap: (index) {
setState(() {
currentIndex = index;
});
},
),
),
),
),
body: IndexedStack(
index: currentIndex,
children: pages,
),
);
}
}
Scrollable Content
RASegmentedSwitch(
items: [
RASegmentedSwitchItem(label: 'All', leftIcon: Icons.all_inclusive),
RASegmentedSwitchItem(label: 'Documents', leftIcon: Icons.description),
RASegmentedSwitchItem(label: 'Images', leftIcon: Icons.image),
RASegmentedSwitchItem(label: 'Videos', leftIcon: Icons.videocam),
RASegmentedSwitchItem(label: 'Audio', leftIcon: Icons.audiotrack),
RASegmentedSwitchItem(label: 'Archives', leftIcon: Icons.archive),
RASegmentedSwitchItem(label: 'Others', leftIcon: Icons.more_horiz),
],
minScrollableItems: 4, // Scrollable when more than 4 items
size: RASegmentedSwitchSize.small,
borderRadius: 8,
onTap: (index) {
// Filter content based on selection
},
)
Custom Styling Examples
Rounded Corners
RASegmentedSwitch(
items: [
RASegmentedSwitchItem(label: 'Rounded'),
RASegmentedSwitchItem(label: 'Corners'),
],
borderRadius: 25,
backgroundColor: Colors.blue[50],
indicatorColor: Colors.blue[600],
onTap: (index) => {},
)
Sharp Edges
RASegmentedSwitch(
items: [
RASegmentedSwitchItem(label: 'Sharp'),
RASegmentedSwitchItem(label: 'Edges'),
],
borderRadius: 4,
backgroundColor: Colors.grey[200],
indicatorColor: Colors.black87,
onTap: (index) => {},
)
Custom Colors
RASegmentedSwitch(
items: [
RASegmentedSwitchItem(label: 'Custom'),
RASegmentedSwitchItem(label: 'Colors'),
],
backgroundColor: Color(0xFFE3F2FD),
indicatorColor: Color(0xFF1976D2),
shadowColor: Color(0xFF1976D2).withOpacity(0.3),
selectedLabelStyle: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
unselectedLabelStyle: TextStyle(
color: Color(0xFF1976D2),
fontWeight: FontWeight.w500,
),
onTap: (index) => {},
)
API Reference
RASegmentedSwitch
The main widget class for creating segmented switches.
Constructor
RASegmentedSwitch({
Key? key,
required List<RASegmentedSwitchItem> items,
required ValueChanged<int> onTap,
int initialIndex = 0,
RASegmentedSwitchSize size = RASegmentedSwitchSize.medium,
TextStyle? selectedLabelStyle,
TextStyle? unselectedLabelStyle,
int? minScrollableItems,
double? borderRadius,
Color? backgroundColor,
Color? indicatorColor,
Color? shadowColor,
})
Properties
Property | Type | Default | Description |
---|---|---|---|
items |
List<RASegmentedSwitchItem> |
required | List of items to display in the segmented switch |
onTap |
ValueChanged<int> |
required | Callback function called when a segment is tapped |
initialIndex |
int |
0 |
Initial selected segment index |
size |
RASegmentedSwitchSize |
medium |
Size of the segmented switch |
selectedLabelStyle |
TextStyle? |
null |
Text style for the selected segment label |
unselectedLabelStyle |
TextStyle? |
null |
Text style for unselected segment labels |
minScrollableItems |
int? |
4 |
Minimum number of items before the switch becomes scrollable |
borderRadius |
double? |
999 |
Border radius of the container (999 = fully rounded) |
backgroundColor |
Color? |
Color(0xFFD5D9E4) |
Background color of the segmented switch container |
indicatorColor |
Color? |
Color(0xFFFDFCFF) |
Color of the selection indicator |
shadowColor |
Color? |
Colors.black |
Shadow color of the selection indicator |
RASegmentedSwitchItem
Represents an individual segment in the switch.
Constructors
// Regular constructor with label and optional icons
RASegmentedSwitchItem({
required String label,
IconData? leftIcon,
IconData? rightIcon,
})
// Icon-only constructor (planned for future release)
RASegmentedSwitchItem.iconOnly({
required IconData icon,
})
Properties
Property | Type | Description |
---|---|---|
label |
String |
Text label displayed in the segment |
leftIcon |
IconData? |
Icon displayed to the left of the label |
rightIcon |
IconData? |
Icon displayed to the right of the label |
icon |
IconData? |
Icon displayed in the center (for icon-only segments) |
RASegmentedSwitchSize
Enum defining the available sizes for the segmented switch.
enum RASegmentedSwitchSize {
small, // Height: 38px
medium, // Height: 48px
large, // Height: 56px
}
Size Specifications
Size | Height | Vertical Padding | Use Case |
---|---|---|---|
small |
38px | 4px | Compact interfaces, toolbars |
medium |
48px | 8px | Standard UI elements, forms |
large |
56px | 12px | Primary navigation, prominent controls |
Customization Guide
Theming Integration
The widget automatically adapts to your app's theme, but you can override specific aspects:
// Using theme colors
RASegmentedSwitch(
items: items,
backgroundColor: Theme.of(context).colorScheme.surfaceVariant,
indicatorColor: Theme.of(context).colorScheme.primary,
selectedLabelStyle: Theme.of(context).textTheme.labelMedium?.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
fontWeight: FontWeight.bold,
),
unselectedLabelStyle: Theme.of(context).textTheme.labelMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
onTap: (index) => {},
)
Responsive Design
Make your segmented switch responsive to different screen sizes:
class ResponsiveSegmentedSwitch extends StatelessWidget {
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
return RASegmentedSwitch(
items: items,
size: screenWidth > 600
? RASegmentedSwitchSize.large
: RASegmentedSwitchSize.medium,
minScrollableItems: screenWidth > 800 ? 6 : 4,
onTap: (index) => {},
);
}
}
Animation Customization
The widget uses an AnimatedContainer
with customizable animation properties:
- Duration: 200 milliseconds
- Curve:
Curves.easeInOut
These values are optimized for the best user experience but can be indirectly influenced through the widget's state changes.
Accessibility
The widget includes built-in accessibility features:
- Semantic labels for screen readers
- Proper focus management
- Keyboard navigation support
- High contrast support
To enhance accessibility further:
RASegmentedSwitch(
items: [
RASegmentedSwitchItem(label: 'Home'), // Automatically gets semantic label
RASegmentedSwitchItem(label: 'Search'),
],
// Ensure sufficient color contrast
backgroundColor: Colors.white,
indicatorColor: Colors.blue[700],
selectedLabelStyle: TextStyle(
color: Colors.white,
fontSize: 16, // Minimum recommended size
),
onTap: (index) => {},
)
Performance Considerations
Optimization Tips
- Minimize Rebuilds: Use
const
constructors where possible - Efficient Callbacks: Keep
onTap
callbacks lightweight - Icon Caching: Use standard Material icons for better performance
- State Management: Consider using state management solutions for complex scenarios
Memory Usage
The widget is designed to be memory-efficient:
- Minimal widget tree depth
- Efficient TabController management
- Automatic disposal of resources
Migration Guide
From Version 0.0.1
This is the initial release, so no migration is needed.
Future Versions
Migration guides will be provided for future releases with breaking changes.
Troubleshooting
Common Issues
TabController Length Mismatch
Error: "Controller's length property does not match the number of tabs"
Solution: This has been fixed in version 0.0.1 with proper didUpdateWidget
implementation.
Performance Issues with Many Items
Problem: Sluggish performance with many segments
Solution: The widget automatically becomes scrollable when there are more than the specified minScrollableItems
(default: 4).
Theme Not Applied
Problem: Custom theme colors not showing
Solution: Ensure you're passing the correct theme colors:
RASegmentedSwitch(
backgroundColor: Theme.of(context).colorScheme.surface,
indicatorColor: Theme.of(context).colorScheme.primary,
// ... other properties
)
Debug Mode
Enable debug mode to see additional information:
import 'package:flutter/foundation.dart';
// In debug mode, the widget provides additional assertion checks
assert(() {
if (kDebugMode) {
print('RASegmentedSwitch: ${items.length} items loaded');
}
return true;
}());
Examples Repository
For more comprehensive examples, check out the /example
directory in the repository, which includes:
- Basic usage examples
- Theme integration demos
- Navigation implementations
- Custom styling showcases
- Performance benchmarks
Contributing
We welcome contributions! Please see our Contributing Guide for details.
Development Setup
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
Reporting Issues
Please report issues on our GitHub Issues page with:
- Flutter version
- Device/platform information
- Minimal reproduction code
- Expected vs actual behavior
Roadmap
Upcoming Features
- Icon-only segments
- Vertical orientation support
- Custom animation curves
- Gradient backgrounds
- Badge support
- Haptic feedback
Version History
See CHANGELOG.md for detailed version history.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
- Documentation: This README and inline code documentation
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Acknowledgments
- Built with Flutter's powerful widget system
- Inspired by iOS UISegmentedControl and Material Design principles
- Thanks to the Flutter community for feedback and contributions
Made with ❤️ for the Flutter community