Skip to main content
Version: v0.8.0

Theming

Pickers ship with a polished light and dark theme that follow the OS appearance automatically. Change any token for the whole app with PickersProvider, or for one component with its theme prop.

PickersProvider​

App.tsx
import { PickersProvider } from '@elik56a/react-native-pickers';

export default function App() {
return (
<PickersProvider
colorScheme="auto"
theme={{
colors: { primary: '#FF5A5F', primaryMuted: 'rgba(255,90,95,0.12)' },
radius: { md: 8, sheet: 16 },
typography: { fontFamily: 'Inter-Regular', fontFamilyMedium: 'Inter-SemiBold' },
}}
darkTheme={{
colors: { primary: '#FF8A8E', background: '#000000', sheet: '#111111' },
}}
>
<Navigation />
</PickersProvider>
);
}
PropTypeDefaultDescription
colorScheme'auto' | 'light' | 'dark''auto''auto' follows useColorScheme().
themeDeepPartial<PickerTheme>—Overrides merged onto lightTheme. Also applied to dark mode when darkTheme is not given.
darkThemeDeepPartial<PickerTheme>—Overrides merged onto darkTheme.
childrenReactNoderequired
Light overrides leak into dark mode

If you pass only theme, the same overrides are applied on top of the dark base too. That's right for brand colours and fonts, but wrong for backgrounds. Pass darkTheme whenever you override surface colours.

Providers can be nested; the innermost one wins. That's an easy way to force one area dark:

Result
Loading...
Live Editor
import { useState } from 'react';
import { View } from 'react-native';
import { PickersProvider, DatePicker } from '@elik56a/react-native-pickers';

export default function Example() {
  const [date, setDate] = useState<Date | null>(new Date());
  return (
    <PickersProvider colorScheme="dark">
      <View style={{ backgroundColor: '#181612', borderRadius: 20, padding: 8 }}>
        <DatePicker value={date} onChange={setDate} />
      </View>
    </PickersProvider>
  );
}

Per-component theme​

Every component accepts theme, merged on top of the provider's theme for that component only:

Result
Loading...
Live Editor
import { useState } from 'react';
import { View } from 'react-native';
import { TimePicker } from '@elik56a/react-native-pickers';

export default function Example() {
  const [t, setT] = useState(new Date());
  return (
    <View style={{ gap: 16 }}>
      <TimePicker value={t} onChange={setT} theme={{ colors: { indicator: 'rgba(16,185,129,0.15)' }, typography: { sizes: { lg: 24 } } }} />
      <TimePicker value={t} onChange={setT} theme={{ colors: { indicator: 'rgba(244,63,94,0.12)', text: '#E11D48' }, radius: { md: 0 } }} />
    </View>
  );
}
Memoize theme objects

Pass a constant or useMemo'd object rather than an inline literal, so the merged theme isn't recomputed every render. It matters most on wheels.

Tokens​

interface PickerTheme {
dark: boolean;
colors: {
background: string; // picker surfaces
surface: string; // search inputs, chips, pressed rows
sheet: string; // bottom sheet background
text: string;
textSecondary: string; // labels, unselected wheel rows, placeholders
textDisabled: string;
primary: string; // selected day, check marks, buttons, accents
onPrimary: string; // text on primary
primaryMuted: string; // range band, selected list row, chips
border: string;
backdrop: string; // sheet overlay
indicator: string; // wheel selection band
handle: string; // sheet grab handle
hover: string; // tint while the pointer is over anything pressable
ripple: string; // ripple that spreads from the touch point on press
};
radius: { sm: number; md: number; lg: number; full: number; sheet: number };
spacing: { xs: number; sm: number; md: number; lg: number; xl: number };
typography: {
fontFamily?: string;
fontFamilyMedium?: string; // used for medium and bold weights; falls back to fontFamily
sizes: { xs: number; sm: number; md: number; lg: number; xl: number };
weights: { regular: TextStyle['fontWeight']; medium: TextStyle['fontWeight']; bold: TextStyle['fontWeight'] };
};
animation: { duration: number }; // ms: month slide, sheet open (+60) / close
}

Defaults​

TokenLightDark
colors.background#FFFFFF#181612
colors.surface#F4F2EE#24211B
colors.sheet#FFFFFF#1D1B16
colors.text#17150F#F5F2EC
colors.textSecondary#6E685E#A39D92
colors.textDisabled#BDB7AC#534E45
colors.primary#C8460D#FF7A3D
colors.onPrimary#FFFFFF#17150F
colors.primaryMutedrgba(200,70,13,0.12)rgba(255,122,61,0.18)
colors.border#E8E4DC#2F2B24
colors.backdroprgba(23,21,15,0.45)rgba(0,0,0,0.6)
colors.indicatorrgba(23,21,15,0.06)rgba(255,255,255,0.07)
colors.handle#D8D3C9#3D3930
colors.hoverrgba(23,21,15,0.05)rgba(255,255,255,0.06)
colors.ripplergba(23,21,15,0.18)rgba(255,255,255,0.2)
TokenValue (both)
radiussm 8 · md 12 · lg 16 · full 999 · sheet 24
spacingxs 4 · sm 8 · md 12 · lg 16 · xl 24
typography.sizesxs 11 · sm 13 · md 16 · lg 19 · xl 22
typography.weightsregular '400' · medium '500' · bold '600'
animation.duration240

Where each colour is used​

TokenUsed by
primarySelected day, today ring, calendar arrows, check marks, chips text, Done button, category underline, tap-to-type ring
primaryMutedRange band, selected select row, chips background, selected skin tone
indicatorWheel selection band
surfaceSearch inputs, skin-tone button
hoverEvery pressable while the pointer is over it: field, rows, days, wheel rows, emoji, buttons
rippleThe same elements on press, on every platform, except the select field and wheel rows
textSecondaryUnselected wheel rows, weekday names, placeholders, descriptions, Cancel
textDisabledDisabled days / rows, outside-month days

Hover and ripple​

Everything you can press (the select field, list and dropdown rows, calendar days, wheel rows, emoji, sheet buttons) tints while a pointer is over it. Items also ripple from the touch point when pressed; the select field and wheel rows don't, since the field opens a list and a wheel row scrolls into place. Hover only happens with a mouse or trackpad, so phones get just the ripple. Both are theme colours, so they follow light and dark mode:

Result
Loading...
Live Editor
import { useState } from 'react';
import { SelectList } from '@elik56a/react-native-pickers';

const fruits = ['Apple', 'Banana', 'Cherry', 'Mango'].map((f) => ({ label: f, value: f }));

export default function Example() {
  const [fruit, setFruit] = useState<string | null>('Banana');
  return (
    <SelectList
      options={fruits}
      value={fruit}
      onChange={setFruit}
      theme={{ colors: { hover: 'rgba(200,70,13,0.08)', ripple: 'rgba(200,70,13,0.22)' } }}
    />
  );
}

Set either to 'transparent' to turn that effect off. Hover and press both lists to compare:

Result
Loading...
Live Editor
import { useState } from 'react';
import { Text, View } from 'react-native';
import { SelectList } from '@elik56a/react-native-pickers';

const sizes = ['Small', 'Medium', 'Large'].map((s) => ({ label: s, value: s }));

export default function Example() {
  const [a, setA] = useState<string | null>('Medium');
  const [b, setB] = useState<string | null>('Medium');
  return (
    <View style={{ flexDirection: 'row', gap: 16 }}>
      <View style={{ flex: 1 }}>
        <Text style={{ fontWeight: '600', marginBottom: 8 }}>Default</Text>
        <SelectList options={sizes} value={a} onChange={setA} />
      </View>
      <View style={{ flex: 1 }}>
        <Text style={{ fontWeight: '600', marginBottom: 8 }}>Turned off</Text>
        <SelectList
          options={sizes}
          value={b}
          onChange={setB}
          theme={{ colors: { hover: 'transparent', ripple: 'transparent' } }}
        />
      </View>
    </View>
  );
}

For the whole app, set them once on the provider:

<PickersProvider theme={{ colors: { hover: 'transparent', ripple: 'transparent' } }}>…</PickersProvider>

Dark mode has its own defaults (a light tint instead of a dark one); override them through darkTheme:

<PickersProvider
theme={{ colors: { hover: 'rgba(14,165,233,0.08)', ripple: 'rgba(14,165,233,0.2)' } }}
darkTheme={{ colors: { hover: 'rgba(56,189,248,0.12)', ripple: 'rgba(56,189,248,0.25)' } }}
>
…
</PickersProvider>

Building a theme in code​

import { createTheme, mergeTheme, lightTheme, darkTheme } from '@elik56a/react-native-pickers';

// Full theme from overrides on top of light (or dark when overrides.dark is true)
const brand = createTheme({ colors: { primary: '#0EA5E9' } });

// Explicit base
const brandDark = createTheme({ colors: { primary: '#38BDF8' } }, darkTheme);

// Generic deep merge (objects are merged, arrays and primitives replaced)
const tweaked = mergeTheme(lightTheme, { radius: { md: 4 } });

Using the theme in your own components​

usePickerTheme() returns the active, resolved theme, so your buttons and fields can match the pickers:

import { usePickerTheme, textStyle } from '@elik56a/react-native-pickers';

function DoneButton({ onPress }: { onPress: () => void }) {
const theme = usePickerTheme();
return (
<Pressable onPress={onPress} style={{ backgroundColor: theme.colors.primary, borderRadius: theme.radius.md, padding: theme.spacing.md }}>
<Text style={[textStyle(theme, 'md', 'bold'), { color: theme.colors.onPrimary }]}>Done</Text>
</Pressable>
);
}

textStyle(theme, size, weight?) returns { fontSize, fontWeight, fontFamily, color } from the tokens.

Matching your design system​

Map your existing tokens once:

import { colors, radii, fonts } from './design-system';

const pickerTheme = {
colors: { primary: colors.brand500, primaryMuted: colors.brand50, text: colors.gray900, textSecondary: colors.gray500, border: colors.gray200 },
radius: { md: radii.md, sheet: radii.xl },
typography: { fontFamily: fonts.regular, fontFamilyMedium: fonts.semibold },
};
const pickerDarkTheme = { colors: { primary: colors.brand300, background: colors.gray950, sheet: colors.gray900 } };

<PickersProvider theme={pickerTheme} darkTheme={pickerDarkTheme}>…</PickersProvider>

→ For per-part tweaks beyond tokens, see Styling.