# useColorScheme

> A cross-platform hook that provides access to the user's preferred color scheme with hydration-safe web support.

**BNA UI** — a React Native / Expo component library.
These components render through `react-native`, not the DOM: there are no HTML
elements, no Tailwind classes and no Radix primitives. Source is copied into your
project and imported through `@/components/ui/*`, `@/components/charts/*`,
`@/hooks/*` and `@/theme/*`. Colours come from the `useColor` hook rather than
hardcoded hex; sizing tokens (`HEIGHT`, `FONT_SIZE`, `BORDER_RADIUS`, `CORNERS`)
come from `@/theme/globals`.

- Docs: https://ui.ahmedbna.com/docs/hooks/useColorScheme
- Markdown: https://ui.ahmedbna.com/docs/hooks/useColorScheme.md
- Structured JSON (props, usage, source, examples): https://ui.ahmedbna.com/r/ai/useColorScheme.json
- Install payload (source plus every file it imports): https://ui.ahmedbna.com/r/useColorScheme.json
- Install: `npx bna-ui add useColorScheme`
- Registry dependencies: `mode-provider`

---

## Installation

### CLI

```bash
npx bna-ui add useColorScheme
```

### Manual

**1.** This hook reads the theme mode from ModeProvider. Install it first:

```bash
npx bna-ui add mode-provider
```

**2.** Copy and paste the following code into your project.

```ts
// hooks/useColorScheme.ts
import { useColorScheme as useRNColorScheme } from 'react-native';

import { useModeContext } from '@/providers/mode-provider';

/**
 * The one place the app's colour scheme is decided.
 *
 * A mounted `ModeProvider` wins, so an in-app light/dark toggle works on every
 * platform — including web, where react-native-web has no
 * `Appearance.setColorScheme` for the toggle to write through. With no provider
 * this is just the OS scheme, so installing `useColor` alone still behaves as
 * it always has.
 *
 * React Native 0.86 widened `ColorSchemeName` to `'light' | 'dark' |
 * 'unspecified'`. The theme is binary — `Colors` only has `light` and `dark`
 * keys — so collapse the third value here, once, and let every consumer keep
 * indexing with a two-value union.
 */
export function useColorScheme(): 'light' | 'dark' {
  const system = useRNColorScheme() === 'dark' ? 'dark' : 'light';
  return useModeContext()?.scheme ?? system;
}
```

**3.** For web support, also copy the web-specific implementation.

```ts
// hooks/useColorScheme.web.ts
import { useEffect, useState } from 'react';
import { useColorScheme as useRNColorScheme } from 'react-native';

import { useModeContext } from '@/providers/mode-provider';

/**
 * To support static rendering, this value needs to be re-calculated on the client side for web.
 *
 * Mirrors the native variant: a mounted `ModeProvider` wins, falling back to the
 * OS scheme. The provider is what makes the toggle work here at all —
 * react-native-web's `Appearance` is read-only, exposing `getColorScheme` and
 * `addChangeListener` but no setter, so nothing can push an override into the
 * value `useRNColorScheme()` reports.
 *
 * React Native 0.86's `ColorSchemeName` includes `'unspecified'`, which the
 * binary theme has no slot for, so it collapses here.
 */
export function useColorScheme(): 'light' | 'dark' {
  const [hasHydrated, setHasHydrated] = useState(false);

  useEffect(() => {
    setHasHydrated(true);
  }, []);

  const system = useRNColorScheme() === 'dark' ? 'dark' : 'light';
  const scheme = useModeContext()?.scheme ?? system;

  if (hasHydrated) {
    return scheme;
  }

  return 'light';
}
```

**4.** Update the import paths to match your project setup.

## Usage

```tsx
import { useColorScheme } from '@/hooks/useColorScheme';
```

```tsx
export function ThemedComponent() {
  const colorScheme = useColorScheme();

  return (
    <View
      style={{
        backgroundColor: colorScheme === 'dark' ? '#000' : '#fff',
      }}
    >
      {/* Your themed content */}
    </View>
  );
}
```

## API Reference

### useColorScheme

Returns the color scheme the app should render as, with platform-specific
optimizations.

A mounted [`ModeProvider`](/docs/providers/mode-provider) wins — that is how an in-app
light/dark toggle repaints your components on every platform. With no provider
mounted this is simply the OS scheme, so the hook works standalone.

#### Returns

| Type                | Description                                                                                                                                                                     |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `'light' \| 'dark'` | The current color scheme. Never `null` — React Native's own `useColorScheme` can return `'unspecified'` (the OS has no preference set), which this hook collapses to `'light'`. |

## Platform Behavior

### Native (iOS/Android)

- Uses React Native's built-in `useColorScheme` hook
- Automatically updates when the system color scheme changes
- Returns the actual system preference immediately
- Listens for system-level theme changes

### Web

- Implements hydration-safe color scheme detection
- Returns `'light'` during server-side rendering to prevent hydration mismatches
- Updates to the actual system preference after client-side hydration
- Listens for system color scheme changes via media queries

## Implementation Details

The hook uses platform-specific files to handle different environments:

- `useColorScheme.ts` - Default implementation for React Native
- `useColorScheme.web.ts` - Web-specific implementation with hydration safety

### Web Hydration Safety

The web implementation includes a hydration check to prevent mismatches between server and client rendering:

```tsx
const [hasHydrated, setHasHydrated] = useState(false);

useEffect(() => {
  setHasHydrated(true);
}, []);

if (hasHydrated) {
  return colorScheme;
}

return 'light'; // Safe default during SSR
```

## Use Cases

This hook is essential for:

- Implementing dark mode support in your applications
- Creating theme-aware components
- Adapting UI colors based on system preferences
- Ensuring consistent theming across platforms
- Building accessible applications with proper contrast ratios

## Best Practices

### Theme Provider Pattern

Create a centralized theme provider for consistent theming:

```tsx
import { useColorScheme } from '@/hooks/useColorScheme';
import { createContext, useContext } from 'react';

const ThemeContext = createContext<'light' | 'dark'>('light');

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const colorScheme = useColorScheme();

  return (
    <ThemeContext.Provider value={colorScheme ?? 'light'}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  return useContext(ThemeContext);
}
```

### Color Palette Management

Define comprehensive color palettes for each theme:

```tsx
const colors = {
  light: {
    background: '#ffffff',
    text: '#000000',
    primary: '#007AFF',
    secondary: '#8E8E93',
    border: '#E5E5E7',
  },
  dark: {
    background: '#000000',
    text: '#ffffff',
    primary: '#0A84FF',
    secondary: '#636366',
    border: '#38383A',
  },
};

export function useThemeColors() {
  const colorScheme = useColorScheme();
  return colors[colorScheme ?? 'light'];
}
```

### Performance Optimization

Memoize theme-dependent calculations to avoid unnecessary re-renders:

```tsx
import { useMemo } from 'react';

export function useThemedStyles() {
  const colorScheme = useColorScheme();

  return useMemo(
    () => ({
      container: {
        backgroundColor: colorScheme === 'dark' ? '#000' : '#fff',
        flex: 1,
      },
      text: {
        color: colorScheme === 'dark' ? '#fff' : '#000',
      },
    }),
    [colorScheme]
  );
}
```

### Testing Both Modes

Always test your application in both light and dark modes:

```tsx
// Test component
export function ThemeTestComponent() {
  const colorScheme = useColorScheme();

  return (
    <View style={{ padding: 20 }}>
      <Text>Current theme: {colorScheme}</Text>
      <Text>Test your components in both modes!</Text>
    </View>
  );
}
```

## Advanced Usage

### Custom Theme Hook

Create a more sophisticated theme hook with additional features:

```tsx
import { useColorScheme } from '@/hooks/useColorScheme';
import { useMemo } from 'react';

interface Theme {
  colors: {
    primary: string;
    secondary: string;
    background: string;
    text: string;
    border: string;
  };
  spacing: {
    xs: number;
    sm: number;
    md: number;
    lg: number;
    xl: number;
  };
  borderRadius: {
    sm: number;
    md: number;
    lg: number;
  };
}

export function useTheme(): Theme {
  const colorScheme = useColorScheme();

  return useMemo(() => {
    const isDark = colorScheme === 'dark';

    return {
      colors: {
        primary: isDark ? '#0A84FF' : '#007AFF',
        secondary: isDark ? '#636366' : '#8E8E93',
        background: isDark ? '#000000' : '#FFFFFF',
        text: isDark ? '#FFFFFF' : '#000000',
        border: isDark ? '#38383A' : '#E5E5E7',
      },
      spacing: {
        xs: 4,
        sm: 8,
        md: 16,
        lg: 24,
        xl: 32,
      },
      borderRadius: {
        sm: 4,
        md: 8,
        lg: 12,
      },
    };
  }, [colorScheme]);
}
```

## Dependencies

- `react-native` - Required for the base `useColorScheme` hook
- `react` - Required for the web implementation hooks (useState, useEffect)

## Accessibility

The hook helps maintain proper accessibility by:

- Respecting user's system-level accessibility preferences
- Supporting high contrast modes automatically
- Enabling proper color contrast ratios for different themes
- Ensuring consistent theming across the application
