# SchemeSwitcher

> Theme switcher / dark mode toggle: System, Light and Dark as SegmentedTabs, or one icon button cycling through them. Calls setScheme, which persists the choice.

Component · utility. Registry item `scheme-switcher`, shown in the vega Style. JSON: https://nativecn.dev/r/styles/vega/scheme-switcher.json

## Installation

```sh
npx nativecn-cli@latest add scheme-switcher
```

Also installs: [theme](https://nativecn.dev/docs/theme/theme), [segmented-tabs](https://nativecn.dev/docs/components/segmented-tabs), [button](https://nativecn.dev/docs/components/button).

Packages: `lucide-react-native@^1.51.0`, `react-native-svg`.

## Usage

### SchemeSwitcher demo

The segmented and icon Variants, sharing the persisted Scheme.

```tsx
import { View } from "react-native";

import { SchemeSwitcher } from "@/components/scheme-switcher";
import { createStyles } from "@/theme";

const useStyles = createStyles((t) => ({
  root: { gap: t.spacing[6] },
  icon: { alignSelf: "flex-start" },
}));

// Both Variants share the persisted Scheme: changing one updates the other.
export default function SchemeSwitcherDemo() {
  const styles = useStyles();
  return (
    <View style={styles.root}>
      <SchemeSwitcher />
      <SchemeSwitcher variant="icon" style={styles.icon} />
    </View>
  );
}
```

## Props

| Prop | Type and description |
|---|---|
| `variant` | "segmented" (default): System / Light / Dark tabs \| "icon": one icon-only Button cycling System → Light → Dark |
| `disabled` | boolean: blocks changes (default false) |
| `style` | ViewStyle, layout only, merged last |

## Variants

- `segmented`, `icon`

## Accessibility

- segmented: a tablist named "Theme" with System, Light and Dark tabs, aria-selected on the current one.
- icon: an icon-only Button named "Theme: <Scheme>" (e.g. "Theme: Dark"), with a 48 tap area.
- disabled is announced as disabled and blocks changes.

## Notes

Lets the user choose the light/dark Scheme. Both Variants read `schemePreference` and call `setScheme` from useTheme(), which persists the choice (AsyncStorage, `nativecn-theme`) and makes native chrome follow; ThemeProvider must wrap the root Layout. `segmented` is SegmentedTabs with System (smartphone), Light (sun) and Dark (moon). `icon` is an icon-only ghost Button showing the current Scheme's icon; each press moves to the next Scheme. To switch from code instead, call setScheme('system' | 'light' | 'dark') from useTheme().

## Source

### {components}/scheme-switcher.tsx

```tsx
import { Moon, Smartphone, Sun, type LucideIcon } from "lucide-react-native";
import type { Ref } from "react";
import type { StyleProp, View, ViewStyle } from "react-native";

import { Button } from "@/components/button";
import {
  SegmentedTabs,
  SegmentedTabsList,
  SegmentedTabsTrigger,
} from "@/components/segmented-tabs";
import { useTheme, type SchemePreference } from "@/theme";

/** The three Schemes in cycling order, with their label and icon. */
const schemes = [
  { value: "system", label: "System", icon: Smartphone },
  { value: "light", label: "Light", icon: Sun },
  { value: "dark", label: "Dark", icon: Moon },
] as const satisfies readonly { value: SchemePreference; label: string; icon: LucideIcon }[];

/**
 * - `segmented`: System · Light · Dark as SegmentedTabs.
 * - `icon`: one icon-only Button showing the current Scheme; each press moves to the next.
 */
export type SchemeSwitcherVariant = "segmented" | "icon";

export type SchemeSwitcherProps = {
  /** Default `segmented`. */
  variant?: SchemeSwitcherVariant;
  disabled?: boolean;
  /** Layout only, merged last onto the root. */
  style?: StyleProp<ViewStyle>;
  testID?: string;
  ref?: Ref<View>;
};

/**
 * Theme switcher / dark mode toggle: picks System, Light or Dark with the Theme's `setScheme`,
 * which persists the choice (AsyncStorage, `nativecn-theme`) and makes native chrome follow.
 */
export function SchemeSwitcher({ variant = "segmented", disabled, ...props }: SchemeSwitcherProps) {
  const { schemePreference, setScheme } = useTheme();
  const index = schemes.findIndex((s) => s.value === schemePreference);
  const current = schemes[index] ?? schemes[0];

  if (variant === "icon") {
    const next = schemes[(index + 1) % schemes.length]!;
    return (
      <Button
        variant="ghost"
        icon={current.icon}
        aria-label={`Theme: ${current.label}`}
        disabled={disabled}
        onPress={() => setScheme(next.value)}
        {...props}
      />
    );
  }

  return (
    <SegmentedTabs
      value={schemePreference}
      onValueChange={(value) => setScheme(value as SchemePreference)}
      disabled={disabled}
      {...props}
    >
      <SegmentedTabsList aria-label="Theme">
        {schemes.map((s) => (
          <SegmentedTabsTrigger key={s.value} value={s.value} label={s.label} icon={s.icon} />
        ))}
      </SegmentedTabsList>
    </SegmentedTabs>
  );
}
```
