Skip to main content

Custom Styling with Borders

Create unique designs by customizing track and thumb borders.
import { useState } from 'react';
import { Switch } from 'react-native-switchery';

export default function BorderedSwitch() {
  const [isEnabled, setIsEnabled] = useState(false);

  return (
    <Switch
      value={isEnabled}
      onValueChange={setIsEnabled}
      variant="primary"
      trackBorderColor="#2196F3"
      trackBorderWidth={2}
      thumbBorderColor="#1976D2"
      thumbBorderWidth={2}
    />
  );
}
Add prominent borders to both track and thumb for a bold look.

Form Integration

Integrate switches seamlessly into form components with labels and validation.
import { useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { Switch } from 'react-native-switchery';

type FormData = {
  notifications: boolean;
  emailAlerts: boolean;
  darkMode: boolean;
  dataSharing: boolean;
};

export default function SettingsForm() {
  const [formData, setFormData] = useState<FormData>({
    notifications: true,
    emailAlerts: false,
    darkMode: false,
    dataSharing: false,
  });

  const updateSetting = (key: keyof FormData, value: boolean) => {
    setFormData(prev => ({ ...prev, [key]: value }));
  };

  return (
    <View style={styles.form}>
      <View style={styles.formRow}>
        <View style={styles.labelContainer}>
          <Text style={styles.label}>Push Notifications</Text>
          <Text style={styles.description}>
            Receive notifications about updates
          </Text>
        </View>
        <Switch
          value={formData.notifications}
          onValueChange={(value) => updateSetting('notifications', value)}
          variant="primary"
          accessibilityLabel="Enable push notifications"
        />
      </View>

      <View style={styles.formRow}>
        <View style={styles.labelContainer}>
          <Text style={styles.label}>Email Alerts</Text>
          <Text style={styles.description}>
            Get important updates via email
          </Text>
        </View>
        <Switch
          value={formData.emailAlerts}
          onValueChange={(value) => updateSetting('emailAlerts', value)}
          variant="info"
          accessibilityLabel="Enable email alerts"
        />
      </View>

      <View style={styles.formRow}>
        <View style={styles.labelContainer}>
          <Text style={styles.label}>Dark Mode</Text>
          <Text style={styles.description}>
            Switch to dark theme
          </Text>
        </View>
        <Switch
          value={formData.darkMode}
          onValueChange={(value) => updateSetting('darkMode', value)}
          variant="success"
          accessibilityLabel="Enable dark mode"
        />
      </View>

      <View style={styles.formRow}>
        <View style={styles.labelContainer}>
          <Text style={styles.label}>Data Sharing</Text>
          <Text style={styles.description}>
            Share usage data for analytics
          </Text>
        </View>
        <Switch
          value={formData.dataSharing}
          onValueChange={(value) => updateSetting('dataSharing', value)}
          variant="warning"
          accessibilityLabel="Enable data sharing"
        />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  form: {
    padding: 16,
    gap: 20,
  },
  formRow: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingVertical: 8,
  },
  labelContainer: {
    flex: 1,
    marginRight: 16,
  },
  label: {
    fontSize: 16,
    fontWeight: '600',
    color: '#111827',
    marginBottom: 4,
  },
  description: {
    fontSize: 14,
    color: '#6B7280',
  },
});
Use meaningful accessibilityLabel props to ensure your switches are accessible to all users.

Dynamic Styling Based on State

Adjust switch appearance dynamically based on its current state or external conditions.
import { useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { Switch } from 'react-native-switchery';

export default function DynamicSwitch() {
  const [isPremium, setIsPremium] = useState(false);
  const [isLowBattery, setIsLowBattery] = useState(true);

  // Dynamic colors based on state
  const premiumColors = isPremium
    ? { active: '#FFD700', inactive: '#FFA500', thumb: '#FFFFFF' }
    : { active: '#10B981', inactive: '#E5E7EB', thumb: '#FFFFFF' };

  return (
    <View style={styles.container}>
      {/* Premium membership with dynamic colors */}
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>Premium Membership</Text>
        <Switch
          value={isPremium}
          onValueChange={setIsPremium}
          activeColor={premiumColors.active}
          inactiveColor={premiumColors.inactive}
          thumbColor={premiumColors.thumb}
          size="large"
        />
        <Text style={styles.status}>
          {isPremium ? 'Premium Active' : 'Free Plan'}
        </Text>
      </View>

      {/* Battery saver mode with conditional rendering */}
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>Battery Saver</Text>
        <Switch
          value={isLowBattery}
          onValueChange={setIsLowBattery}
          variant={isLowBattery ? 'warning' : 'success'}
          size="default"
        />
        <Text style={[
          styles.status,
          { color: isLowBattery ? '#F59E0B' : '#10B981' }
        ]}>
          {isLowBattery ? 'Low Battery Mode' : 'Normal Mode'}
        </Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    padding: 20,
    gap: 24,
  },
  section: {
    alignItems: 'center',
    gap: 12,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: '600',
    color: '#111827',
  },
  status: {
    fontSize: 14,
    color: '#6B7280',
  },
});

Multiple Switches with Grid Layout

Manage multiple related settings with a clean, organized layout.
import { useState } from 'react';
import { View, Text, StyleSheet, ScrollView } from 'react-native';
import { Switch } from 'react-native-switchery';

type Permission = {
  id: string;
  label: string;
  description: string;
  enabled: boolean;
  variant: 'primary' | 'danger' | 'warning' | 'success' | 'info';
};

export default function PermissionsGrid() {
  const [permissions, setPermissions] = useState<Permission[]>([
    {
      id: 'camera',
      label: 'Camera',
      description: 'Access device camera',
      enabled: true,
      variant: 'primary',
    },
    {
      id: 'location',
      label: 'Location',
      description: 'Access GPS location',
      enabled: false,
      variant: 'info',
    },
    {
      id: 'microphone',
      label: 'Microphone',
      description: 'Record audio',
      enabled: true,
      variant: 'success',
    },
    {
      id: 'contacts',
      label: 'Contacts',
      description: 'Access contact list',
      enabled: false,
      variant: 'warning',
    },
    {
      id: 'storage',
      label: 'Storage',
      description: 'Read/write files',
      enabled: true,
      variant: 'primary',
    },
    {
      id: 'notifications',
      label: 'Notifications',
      description: 'Send push notifications',
      enabled: false,
      variant: 'danger',
    },
  ]);

  const togglePermission = (id: string) => {
    setPermissions(prev =>
      prev.map(perm =>
        perm.id === id ? { ...perm, enabled: !perm.enabled } : perm
      )
    );
  };

  return (
    <ScrollView style={styles.container}>
      <View style={styles.grid}>
        {permissions.map(permission => (
          <View key={permission.id} style={styles.card}>
            <View style={styles.cardHeader}>
              <Text style={styles.cardTitle}>{permission.label}</Text>
              <Switch
                value={permission.enabled}
                onValueChange={() => togglePermission(permission.id)}
                variant={permission.variant}
                size="small"
                testID={`switch-${permission.id}`}
              />
            </View>
            <Text style={styles.cardDescription}>
              {permission.description}
            </Text>
            <Text style={[
              styles.statusBadge,
              permission.enabled && styles.statusBadgeActive
            ]}>
              {permission.enabled ? 'Granted' : 'Denied'}
            </Text>
          </View>
        ))}
      </View>
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F9FAFB',
  },
  grid: {
    padding: 16,
    flexDirection: 'row',
    flexWrap: 'wrap',
    gap: 12,
  },
  card: {
    backgroundColor: '#FFFFFF',
    borderRadius: 12,
    padding: 16,
    width: '48%',
    minWidth: 150,
    borderWidth: 1,
    borderColor: '#E5E7EB',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.05,
    shadowRadius: 2,
    elevation: 1,
  },
  cardHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 8,
  },
  cardTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#111827',
  },
  cardDescription: {
    fontSize: 13,
    color: '#6B7280',
    marginBottom: 8,
  },
  statusBadge: {
    fontSize: 11,
    fontWeight: '500',
    color: '#EF4444',
    textTransform: 'uppercase',
  },
  statusBadgeActive: {
    color: '#10B981',
  },
});

Controlled with External State

Synchronize switch state with external data sources or complex state management.
import { useEffect, useState } from 'react';
import { View, Text, StyleSheet, ActivityIndicator } from 'react-native';
import { Switch } from 'react-native-switchery';

export default function ControlledSwitch() {
  const [isLoading, setIsLoading] = useState(false);
  const [autoSave, setAutoSave] = useState(false);
  const [lastSaved, setLastSaved] = useState<Date | null>(null);

  // Simulate API call when switch changes
  const handleToggle = async (value: boolean) => {
    setIsLoading(true);
    
    try {
      // Simulate API delay
      await new Promise(resolve => setTimeout(resolve, 1000));
      
      // Update state only after successful "save"
      setAutoSave(value);
      setLastSaved(new Date());
    } catch (error) {
      // Handle error - switch reverts to previous state
      console.error('Failed to save setting:', error);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <View style={styles.container}>
      <View style={styles.header}>
        <View style={styles.labelContainer}>
          <Text style={styles.label}>Auto-Save</Text>
          <Text style={styles.description}>
            Automatically save changes
          </Text>
        </View>
        <View style={styles.switchContainer}>
          {isLoading && (
            <ActivityIndicator
              size="small"
              color="#2196F3"
              style={styles.loader}
            />
          )}
          <Switch
            value={autoSave}
            onValueChange={handleToggle}
            disabled={isLoading}
            variant="success"
            size="default"
          />
        </View>
      </View>
      
      {lastSaved && (
        <Text style={styles.timestamp}>
          Last saved: {lastSaved.toLocaleTimeString()}
        </Text>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    padding: 20,
    backgroundColor: '#FFFFFF',
    borderRadius: 12,
    borderWidth: 1,
    borderColor: '#E5E7EB',
  },
  header: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  labelContainer: {
    flex: 1,
  },
  label: {
    fontSize: 16,
    fontWeight: '600',
    color: '#111827',
    marginBottom: 4,
  },
  description: {
    fontSize: 14,
    color: '#6B7280',
  },
  switchContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    gap: 12,
  },
  loader: {
    marginRight: 8,
  },
  timestamp: {
    marginTop: 12,
    fontSize: 12,
    color: '#9CA3AF',
    fontStyle: 'italic',
  },
});
Always handle asynchronous operations gracefully by showing loading states and keeping the switch disabled during updates.

Testing with TestID

Implement switches with proper test identifiers for automated testing.
import { useState } from 'react';
import { View, StyleSheet } from 'react-native';
import { Switch } from 'react-native-switchery';

export default function TestableSwitch() {
  const [settings, setSettings] = useState({
    feature1: false,
    feature2: true,
    feature3: false,
  });

  return (
    <View style={styles.container}>
      <Switch
        value={settings.feature1}
        onValueChange={(value) => 
          setSettings(prev => ({ ...prev, feature1: value }))
        }
        testID="switch-feature-1"
        accessibilityLabel="Enable Feature 1"
        accessibilityHint="Toggles the first feature on or off"
      />

      <Switch
        value={settings.feature2}
        onValueChange={(value) => 
          setSettings(prev => ({ ...prev, feature2: value }))
        }
        testID="switch-feature-2"
        accessibilityLabel="Enable Feature 2"
        accessibilityHint="Toggles the second feature on or off"
      />

      <Switch
        value={settings.feature3}
        onValueChange={(value) => 
          setSettings(prev => ({ ...prev, feature3: value }))
        }
        disabled={true}
        testID="switch-feature-3"
        accessibilityLabel="Enable Feature 3"
        accessibilityHint="This feature is currently unavailable"
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    padding: 20,
    gap: 16,
  },
});

Testing Example

import { render, fireEvent } from '@testing-library/react-native';
import TestableSwitch from './TestableSwitch';

describe('TestableSwitch', () => {
  it('should toggle switch when pressed', () => {
    const { getByTestId } = render(<TestableSwitch />);
    const switch1 = getByTestId('switch-feature-1');
    
    fireEvent.press(switch1);
    expect(switch1.props.accessibilityState.checked).toBe(true);
  });

  it('should not toggle disabled switch', () => {
    const { getByTestId } = render(<TestableSwitch />);
    const switch3 = getByTestId('switch-feature-3');
    
    expect(switch3.props.accessibilityState.disabled).toBe(true);
    fireEvent.press(switch3);
    expect(switch3.props.accessibilityState.checked).toBe(false);
  });
});
Combine testID with accessibility props to make your components both testable and accessible.

Build docs developers (and LLMs) love