Skip to content

Switcher

Switcher provides an elegant toggle switch component with iOS-like animations and visual feedback. Built with React Native Reanimated, it offers smooth spring animations, customizable colors, and multiple size variants for versatile use cases.

import { Switcher, SwitcherLabel } from 'rnc-theme';
const [isEnabled, setIsEnabled] = useState(false);
<Switcher
value={isEnabled}
onValueChange={setIsEnabled}
/>
PropTypeDefaultDescription
valueboolean-Current switch state (controlled)
onValueChange(value: boolean) => void-Callback when switch state changes
sizeComponentSize'md'Switch size (xs, sm, md, lg, xl)
variantComponentVariant'primary'Visual style variant
disabledbooleanfalseDisable switch interactions
trackColor{ false?: string; true?: string }-Custom track colors for off/on states
thumbColorstring'#FFFFFF'Custom thumb color
animatedbooleantrueEnable smooth animations
styleStyleProp<ViewStyle>-Additional container styles
PropTypeDefaultDescription
childrenReact.ReactNode-Label content to display
position'left' | 'right''right'Label position relative to switch
styleStyleProp<ViewStyle>-Additional label styles
VariantDescriptionDefault ColorUse Case
primaryPrimary brand colortheme.colors.primaryMain settings, preferences
secondarySecondary brand colortheme.colors.secondarySecondary options
outlineOutlined styletheme.colors.primaryAlternative styling
filledFilled varianttheme.colors.primaryForm inputs
ghostSubtle stylingtheme.colors.primaryMinimal interfaces
successSuccess/confirmationtheme.colors.successPositive actions
errorError/danger statetheme.colors.errorDangerous toggles
warningWarning statetheme.colors.warningCaution settings
infoInformation statetheme.colors.infoInfo toggles
destructiveDestructive actionstheme.colors.errorDelete, disable actions
SizeWidthHeightThumb SizeUse Case
xs24px14px10pxCompact lists, tight spaces
sm28px16px12pxDense interfaces
md44px26px22pxStandard use (default)
lg52px32px28pxProminent settings
xl60px36px32pxLarge touch targets
const SettingsScreen = () => {
const [settings, setSettings] = useState({
notifications: true,
darkMode: false,
autoBackup: true,
biometric: false,
analytics: false
});
const updateSetting = (key: string) => (value: boolean) => {
setSettings(prev => ({ ...prev, [key]: value }));
};
return (
<VStack spacing="lg" padding="xl">
<VStack spacing="md">
<Text variant="h3">General</Text>
<HStack justify="space-between" align="center">
<VStack>
<Text variant="body">Push Notifications</Text>
<Text variant="caption" color="textSecondary">
Receive alerts and updates
</Text>
</VStack>
<Switcher
value={settings.notifications}
onValueChange={updateSetting('notifications')}
variant="primary"
/>
</HStack>
<HStack justify="space-between" align="center">
<VStack>
<Text variant="body">Dark Mode</Text>
<Text variant="caption" color="textSecondary">
Use dark theme
</Text>
</VStack>
<Switcher
value={settings.darkMode}
onValueChange={updateSetting('darkMode')}
variant="secondary"
/>
</HStack>
</VStack>
<VStack spacing="md">
<Text variant="h3">Privacy & Security</Text>
<HStack justify="space-between" align="center">
<VStack>
<Text variant="body">Biometric Login</Text>
<Text variant="caption" color="textSecondary">
Use fingerprint or face unlock
</Text>
</VStack>
<Switcher
value={settings.biometric}
onValueChange={updateSetting('biometric')}
variant="success"
/>
</HStack>
<HStack justify="space-between" align="center">
<VStack>
<Text variant="body">Analytics</Text>
<Text variant="caption" color="textSecondary">
Help improve the app
</Text>
</VStack>
<Switcher
value={settings.analytics}
onValueChange={updateSetting('analytics')}
variant="warning"
size="sm"
/>
</HStack>
</VStack>
</VStack>
);
};
<VStack spacing="lg" align="center" padding="xl">
<VStack spacing="md" align="center">
<Text variant="caption">Extra Small (xs)</Text>
<Switcher size="xs" value={true} onValueChange={() => {}} />
</VStack>
<VStack spacing="md" align="center">
<Text variant="caption">Small (sm)</Text>
<Switcher size="sm" value={true} onValueChange={() => {}} />
</VStack>
<VStack spacing="md" align="center">
<Text variant="caption">Medium (md) - Default</Text>
<Switcher size="md" value={true} onValueChange={() => {}} />
</VStack>
<VStack spacing="md" align="center">
<Text variant="caption">Large (lg)</Text>
<Switcher size="lg" value={true} onValueChange={() => {}} />
</VStack>
<VStack spacing="md" align="center">
<Text variant="caption">Extra Large (xl)</Text>
<Switcher size="xl" value={true} onValueChange={() => {}} />
</VStack>
</VStack>
const VariantShowcase = () => {
const [states, setStates] = useState({
primary: true,
secondary: true,
success: true,
warning: false,
error: false,
info: true
});
return (
<VStack spacing="lg" padding="xl">
<Text variant="h3">Switch Variants</Text>
<VStack spacing="md">
{Object.entries(states).map(([variant, value]) => (
<HStack key={variant} justify="space-between" align="center">
<Text variant="body" style={{ textTransform: 'capitalize' }}>
{variant}
</Text>
<Switcher
variant={variant}
value={value}
onValueChange={(newValue) =>
setStates(prev => ({ ...prev, [variant]: newValue }))
}
/>
</HStack>
))}
</VStack>
</VStack>
);
};
const CustomSwitcher = () => {
const [customStates, setCustomStates] = useState({
gradient: true,
neon: false,
minimal: true
});
return (
<VStack spacing="lg" padding="xl">
{/* Gradient Style */}
<HStack justify="space-between" align="center">
<Text>Gradient Switch</Text>
<Switcher
value={customStates.gradient}
onValueChange={(value) =>
setCustomStates(prev => ({ ...prev, gradient: value }))
}
trackColor={{
false: '#E5E7EB',
true: '#3B82F6'
}}
thumbColor="#FFFFFF"
/>
</HStack>
{/* Neon Style */}
<HStack justify="space-between" align="center">
<Text>Neon Switch</Text>
<Switcher
value={customStates.neon}
onValueChange={(value) =>
setCustomStates(prev => ({ ...prev, neon: value }))
}
trackColor={{
false: '#1F2937',
true: '#10B981'
}}
thumbColor="#F59E0B"
size="lg"
/>
</HStack>
{/* Minimal Style */}
<HStack justify="space-between" align="center">
<Text>Minimal Switch</Text>
<Switcher
value={customStates.minimal}
onValueChange={(value) =>
setCustomStates(prev => ({ ...prev, minimal: value }))
}
trackColor={{
false: '#F3F4F6',
true: '#6B7280'
}}
thumbColor="#374151"
size="sm"
/>
</HStack>
</VStack>
);
};
const UserPreferencesForm = () => {
const [preferences, setPreferences] = useState({
emailNotifications: true,
smsNotifications: false,
marketingEmails: false,
weeklyDigest: true,
instantAlerts: true
});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = async () => {
setIsSubmitting(true);
try {
await saveUserPreferences(preferences);
// Show success message
} catch (error) {
// Handle error
} finally {
setIsSubmitting(false);
}
};
const updatePreference = (key: string) => (value: boolean) => {
setPreferences(prev => ({ ...prev, [key]: value }));
};
return (
<VStack spacing="xl" padding="xl">
<Text variant="h2">Notification Preferences</Text>
<VStack spacing="lg">
<VStack spacing="md">
<Text variant="h4">Email Settings</Text>
<HStack justify="space-between" align="center">
<SwitcherLabel position="left">
<VStack>
<Text variant="body">Email Notifications</Text>
<Text variant="caption" color="textSecondary">
Receive important updates via email
</Text>
</VStack>
</SwitcherLabel>
<Switcher
value={preferences.emailNotifications}
onValueChange={updatePreference('emailNotifications')}
variant="primary"
disabled={isSubmitting}
/>
</HStack>
<HStack justify="space-between" align="center">
<SwitcherLabel position="left">
<VStack>
<Text variant="body">Marketing Emails</Text>
<Text variant="caption" color="textSecondary">
Promotional content and offers
</Text>
</VStack>
</SwitcherLabel>
<Switcher
value={preferences.marketingEmails}
onValueChange={updatePreference('marketingEmails')}
variant="secondary"
disabled={isSubmitting}
/>
</HStack>
</VStack>
<VStack spacing="md">
<Text variant="h4">Mobile Settings</Text>
<HStack justify="space-between" align="center">
<SwitcherLabel position="left">
<Text variant="body">SMS Notifications</Text>
</SwitcherLabel>
<Switcher
value={preferences.smsNotifications}
onValueChange={updatePreference('smsNotifications')}
variant="info"
disabled={isSubmitting}
/>
</HStack>
<HStack justify="space-between" align="center">
<SwitcherLabel position="left">
<Text variant="body">Instant Alerts</Text>
</SwitcherLabel>
<Switcher
value={preferences.instantAlerts}
onValueChange={updatePreference('instantAlerts')}
variant="warning"
disabled={isSubmitting}
/>
</HStack>
</VStack>
</VStack>
<Button
fullWidth
variant="primary"
loading={isSubmitting}
onPress={handleSubmit}
>
<ButtonText>Save Preferences</ButtonText>
</Button>
</VStack>
);
};
const AdvancedSwitcherDemo = () => {
const [masterToggle, setMasterToggle] = useState(false);
const [childToggles, setChildToggles] = useState([false, false, false]);
// Update child toggles when master changes
useEffect(() => {
if (!masterToggle) {
setChildToggles([false, false, false]);
}
}, [masterToggle]);
const updateChildToggle = (index: number) => (value: boolean) => {
const newToggles = [...childToggles];
newToggles[index] = value;
setChildToggles(newToggles);
// Update master if all children are off
if (!value && newToggles.every(toggle => !toggle)) {
setMasterToggle(false);
}
// Enable master if any child is on
else if (value) {
setMasterToggle(true);
}
};
return (
<VStack spacing="lg" padding="xl">
<Text variant="h3">Hierarchical Toggles</Text>
{/* Master Toggle */}
<Card padding="md">
<HStack justify="space-between" align="center">
<VStack>
<Text variant="body" weight="bold">Master Control</Text>
<Text variant="caption" color="textSecondary">
Controls all sub-features
</Text>
</VStack>
<Switcher
value={masterToggle}
onValueChange={setMasterToggle}
variant="primary"
size="lg"
/>
</HStack>
</Card>
{/* Child Toggles */}
<VStack spacing="md" style={{ paddingLeft: 16 }}>
{['Feature A', 'Feature B', 'Feature C'].map((feature, index) => (
<HStack key={feature} justify="space-between" align="center">
<Text
variant="body"
color={masterToggle ? 'text' : 'textSecondary'}
>
{feature}
</Text>
<Switcher
value={childToggles[index]}
onValueChange={updateChildToggle(index)}
variant="secondary"
disabled={!masterToggle}
size="sm"
/>
</HStack>
))}
</VStack>
</VStack>
);
};
<Switcher
value={enabled}
onValueChange={setEnabled}
animated={true} // Default spring animation
/>

The Switcher component includes built-in accessibility features:

<Switcher
value={notifications}
onValueChange={setNotifications}
// Automatically includes:
// - accessible={true}
// - accessibilityRole="switch"
// - accessibilityState={{ checked: value }}
// - accessibilityLabel derived from SwitcherLabel
/>

Visual Hierarchy

  • Use consistent switch sizes within the same interface section
  • Group related switches with clear visual separation
  • Provide descriptive labels that clearly explain the toggle’s purpose

User Experience

  • Always provide immediate visual feedback when state changes
  • Use appropriate variants to indicate the importance or type of setting
  • Consider the impact of the toggle and use warning/error variants for potentially destructive actions

Performance

  • Use React.memo for switch lists that don’t need frequent re-renders
  • Avoid creating new functions in render for onValueChange handlers
  • Consider debouncing for switches that trigger expensive operations