Skip to content

Usage Patterns

This guide covers common patterns and best practices for building applications with the Synchronicity Design System.


Wrap your app with ThemeProvider to enable theming across all components:

import { ThemeProvider } from '@synchronicity/react-native';
function App() {
return (
<ThemeProvider defaultTheme="dark">
<YourApp />
</ThemeProvider>
);
}

Use the provided hooks to access theme values in your components:

import { useColors, useThemeObject, useTheme } from '@synchronicity/react-native';
function MyComponent() {
// Get just the color palette
const colors = useColors();
// Get the full theme object
const theme = useThemeObject();
// Get theme mode and setter
const { mode, setMode } = useTheme();
return (
<View style={{ backgroundColor: colors['background'] }}>
<Text style={theme.typography['type-body-md']}>
Current theme: {mode}
</Text>
</View>
);
}

The design system supports three theme modes:

ModeDescription
lightLight background with dark text
darkDark background with light text
oledPure black background for OLED displays
const { setMode } = useTheme();
// Switch themes programmatically
setMode('dark');
setMode('light');
setMode('oled');

All form components follow the controlled component pattern:

function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [remember, setRemember] = useState(false);
return (
<View>
<Input
label="Email"
value={email}
onChangeText={setEmail}
keyboardType="email-address"
autoCapitalize="none"
/>
<Input
label="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
/>
<Checkbox
label="Remember me"
checked={remember}
onCheckedChange={setRemember}
/>
<Button onPress={handleSubmit}>
Sign In
</Button>
</View>
);
}

Show validation states using the error prop:

function ValidatedForm() {
const [email, setEmail] = useState('');
const [error, setError] = useState('');
const validate = (value: string) => {
if (!value.includes('@')) {
setError('Please enter a valid email');
} else {
setError('');
}
};
return (
<Input
label="Email"
value={email}
onChangeText={(value) => {
setEmail(value);
validate(value);
}}
error={error}
/>
);
}

Group related inputs with consistent spacing:

<View style={{ gap: 16 }}>
<Input label="First Name" value={firstName} onChangeText={setFirstName} />
<Input label="Last Name" value={lastName} onChangeText={setLastName} />
<Divider />
<Input label="Email" value={email} onChangeText={setEmail} />
</View>

Show loading state in buttons during async operations:

function SubmitButton() {
const [isLoading, setIsLoading] = useState(false);
const handlePress = async () => {
setIsLoading(true);
try {
await submitForm();
} finally {
setIsLoading(false);
}
};
return (
<Button onPress={handlePress} loading={isLoading}>
Submit
</Button>
);
}

Use skeletons while content is loading:

function ReadingCard({ loading, reading }) {
if (loading) {
return (
<Card>
<SkeletonGroup>
<Skeleton width={200} height={24} />
<Skeleton width="100%" height={16} />
<Skeleton width="80%" height={16} />
</SkeletonGroup>
</Card>
);
}
return (
<Card>
<Text>{reading.title}</Text>
<Text>{reading.description}</Text>
</Card>
);
}

Use LoadingOverlay for full-screen loading states:

function DataScreen() {
const [loading, setLoading] = useState(true);
return (
<View style={{ flex: 1 }}>
<LoadingOverlay visible={loading} message="Loading readings..." />
{/* Your content */}
</View>
);
}

Use toasts for non-blocking feedback:

import { ToastProvider, useToast } from '@synchronicity/react-native';
function App() {
return (
<ToastProvider>
<YourApp />
</ToastProvider>
);
}
function SaveButton() {
const { showToast } = useToast();
const handleSave = async () => {
try {
await saveReading();
showToast({
message: 'Reading saved successfully',
variant: 'success',
});
} catch (error) {
showToast({
message: 'Failed to save reading',
variant: 'error',
});
}
};
return <Button onPress={handleSave}>Save</Button>;
}

Use ConfirmModal for destructive actions:

function DeleteButton({ onDelete }) {
const [showConfirm, setShowConfirm] = useState(false);
return (
<>
<Button variant="destructive" onPress={() => setShowConfirm(true)}>
Delete
</Button>
<ConfirmModal
visible={showConfirm}
onClose={() => setShowConfirm(false)}
onConfirm={onDelete}
title="Delete Reading?"
message="This action cannot be undone."
confirmText="Delete"
destructive
/>
</>
);
}

Use alerts for contextual messages:

<Alert
variant="warning"
title="Unsaved Changes"
message="You have unsaved changes. Save before leaving?"
action={{ label: 'Save Now', onPress: handleSave }}
/>

Use Tabs for content switching within a screen:

function HexagramDetail() {
const [activeTab, setActiveTab] = useState('meaning');
return (
<View>
<Tabs
tabs={[
{ key: 'meaning', label: 'Meaning' },
{ key: 'lines', label: 'Lines', badge: 6 },
{ key: 'notes', label: 'Notes' },
]}
value={activeTab}
onValueChange={setActiveTab}
/>
<TabPanel value="meaning" selectedValue={activeTab}>
<MeaningContent />
</TabPanel>
<TabPanel value="lines" selectedValue={activeTab}>
<LinesContent />
</TabPanel>
<TabPanel value="notes" selectedValue={activeTab}>
<NotesContent />
</TabPanel>
</View>
);
}

Use StepIndicator for wizards:

function CastingWizard() {
const [currentStep, setCurrentStep] = useState(0);
const steps = [
{ key: 'question', label: 'Ask Question' },
{ key: 'cast', label: 'Cast Hexagram' },
{ key: 'interpret', label: 'Interpretation' },
];
return (
<View>
<StepIndicator
steps={steps}
currentStep={currentStep}
onStepPress={setCurrentStep}
/>
{currentStep === 0 && <QuestionStep onNext={() => setCurrentStep(1)} />}
{currentStep === 1 && <CastStep onNext={() => setCurrentStep(2)} />}
{currentStep === 2 && <InterpretStep />}
</View>
);
}

<List>
<ListSection header="Recent Readings">
<ListItem
title="Hexagram 1 - The Creative"
subtitle="Cast on December 13, 2025"
trailing={<ChevronRightIcon />}
onPress={() => navigate('reading', { id: 1 })}
/>
<ListItem
title="Hexagram 11 - Peace"
subtitle="Cast on December 12, 2025"
trailing={<ChevronRightIcon />}
onPress={() => navigate('reading', { id: 2 })}
/>
</ListSection>
</List>

Combine with react-native-gesture-handler for swipe actions:

import { Swipeable } from 'react-native-gesture-handler';
function SwipeableListItem({ reading, onDelete }) {
const renderRightActions = () => (
<Pressable onPress={onDelete} style={styles.deleteAction}>
<Text>Delete</Text>
</Pressable>
);
return (
<Swipeable renderRightActions={renderRightActions}>
<ListItem
title={reading.title}
subtitle={reading.date}
/>
</Swipeable>
);
}

Always provide accessibility labels:

<IconButton
icon={<MenuIcon />}
accessibilityLabel="Open menu"
accessibilityHint="Opens the navigation menu"
onPress={openMenu}
/>
<Switch
value={darkMode}
onValueChange={setDarkMode}
accessibilityLabel="Dark mode"
accessibilityState={{ checked: darkMode }}
/>
<View accessibilityRole="form" accessibilityLabel="Login form">
<Input label="Email" accessibilityLabel="Email address" />
<Input label="Password" accessibilityLabel="Password" />
<Button accessibilityLabel="Sign in">Sign In</Button>
</View>

const handlePress = useCallback(() => {
// Handle press
}, [dependency]);
<Button onPress={handlePress}>Action</Button>

All animations in Synchronicity use useNativeDriver: true where possible for optimal performance.

const HeavyComponent = lazy(() => import('./HeavyComponent'));
function Screen() {
return (
<Suspense fallback={<Spinner />}>
<HeavyComponent />
</Suspense>
);
}