Vue NativeVue Native
Guide
Components
Composables
Navigation
Architecture
  • iOS
  • Android
  • macOS
GitHub
Guide
Components
Composables
Navigation
Architecture
  • iOS
  • Android
  • macOS
GitHub
  • Getting Started

    • Introduction
    • Installation
    • Your First App
    • Project Structure
  • Core Concepts

    • Components
    • Styling
    • Theming
    • TypeScript
    • Navigation
    • Navigation Components
    • Native Modules
    • Native Code Blocks
    • Hot Reload
  • UI Patterns

    • Forms and v-model
    • Shared Element Transitions
    • Teleport
    • Custom Native Components (escape hatch)
  • Quality & Debugging

    • Testing
    • Debugging
    • Error Handling
  • Platform Hardening

    • Security
    • Accessibility
    • Performance
  • Integration Guides

    • State Management
    • Deep Linking & Universal Links
    • State Persistence
    • Push Notifications
    • Error Reporting & Monitoring
  • Tooling

    • Managed Workflow
    • VS Code Extension
    • Neovim Plugin
  • Building & Releasing

    • Building Native Apps
    • Deployment & App Store Submission
  • Reference

    • Upgrade Guide
    • Migrating from React Native
    • Known Limitations & Platform Differences
    • Troubleshooting

Theming

Vue Native ships a small, reactive theme system for consistent design tokens and dark-mode support. It is built on Vue's provide / inject, so theme values stay reactive and are scoped to the component tree — no global singletons.

The three building blocks:

APIPurpose
createThemeDefines light/dark token sets; returns a <ThemeProvider> component and a useTheme composable.
createDynamicStyleSheetBuilds a stylesheet that re-evaluates whenever the active theme changes.
useColorSchemeReactively tracks the system light/dark setting.

A complete working app lives in examples/theming/.

1. Define a theme

Call createTheme once in a dedicated module. It takes a light and a dark variant of the same token shape and returns a ThemeProvider plus a useTheme hook bound to that definition:

// theme.ts
import { createTheme } from '@thelacanians/vue-native-runtime'

export const { ThemeProvider, useTheme } = createTheme({
  light: {
    colors: {
      background: '#F2F2F7',
      surface: '#FFFFFF',
      text: '#1C1C1E',
      textSecondary: '#8E8E93',
      primary: '#007AFF',
      error: '#FF3B30',
    },
    spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 32 },
    borderRadius: { sm: 6, md: 10, lg: 16 },
    fontSize: { caption: 12, body: 16, title: 20, heading: 28 },
  },
  dark: {
    colors: {
      background: '#000000',
      surface: '#1C1C1E',
      text: '#F5F5F7',
      textSecondary: '#98989D',
      primary: '#0A84FF',
      error: '#FF453A',
    },
    spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 32 },
    borderRadius: { sm: 6, md: 10, lg: 16 },
    fontSize: { caption: 12, body: 16, title: 20, heading: 28 },
  },
})

The token shape is entirely yours — colors, spacing, borderRadius, and fontSize above are just conventions. Both variants must have the same keys.

2. Provide the theme at the root

Wrap your app (or any subtree) with <ThemeProvider>. It accepts an optional initial-color-scheme prop ('light' or 'dark', defaults to 'light'):

<!-- App.vue -->
<script setup lang="ts">
import { ThemeProvider } from './theme'
import HomeScreen from './HomeScreen.vue'
</script>

<template>
  <ThemeProvider initial-color-scheme="light">
    <HomeScreen />
  </ThemeProvider>
</template>

3. Consume tokens with useTheme

Inside any descendant, useTheme() returns the reactive theme plus controls:

const { theme, colorScheme, toggleColorScheme, setColorScheme } = useTheme()
PropertyTypeDescription
themeComputedRef<T>The resolved token set for the active scheme.
colorSchemeRef<'light' | 'dark'>The currently active scheme.
toggleColorScheme() => voidFlip between light and dark.
setColorScheme(scheme: 'light' | 'dark') => voidSet a specific scheme.

Calling useTheme() outside a <ThemeProvider> throws — wrap your root first.

4. Theme-aware styles with createDynamicStyleSheet

createDynamicStyleSheet takes the reactive theme and a factory function, and returns a ComputedRef stylesheet that re-computes whenever the theme changes. No manual watch needed:

<script setup lang="ts">
import { createDynamicStyleSheet } from '@thelacanians/vue-native-runtime'
import { useTheme } from './theme'

const { theme, colorScheme, toggleColorScheme } = useTheme()

const styles = createDynamicStyleSheet(theme, (t) => ({
  container: {
    flex: 1,
    backgroundColor: t.colors.background,
    padding: t.spacing.lg,
  },
  title: {
    fontSize: t.fontSize.heading,
    fontWeight: 'bold',
    color: t.colors.text,
  },
  card: {
    backgroundColor: t.colors.surface,
    borderRadius: t.borderRadius.lg,
    padding: t.spacing.md,
  },
}))
</script>

<template>
  <VView :style="styles.container">
    <VText :style="styles.title">{{ colorScheme }} mode</VText>
    <VButton :onPress="toggleColorScheme">
      <VText>Toggle theme</VText>
    </VButton>
  </VView>
</template>

styles is a ComputedRef, so it is auto-unwrapped in the template — bind styles.container, not styles.value.container. In <script> code, access the object via styles.value.

5. Follow the system setting

useColorScheme() reactively reports the OS appearance and updates when the user toggles system dark mode. Sync it into the theme with a watch:

<script setup lang="ts">
import { watch } from 'vue'
import { useColorScheme } from '@thelacanians/vue-native-runtime'
import { useTheme } from './theme'

const { colorScheme: systemScheme } = useColorScheme()
const { setColorScheme } = useTheme()

// Apply the system setting now and whenever it changes.
watch(systemScheme, (scheme) => setColorScheme(scheme), { immediate: true })
</script>

useColorScheme() returns { colorScheme, isDark }, both reactive refs.

6. Persist the user's preference

The simplest way to remember an explicit user choice across launches is the persist prop on <ThemeProvider>. It stores the scheme (via useAsyncStorage) whenever setColorScheme/toggleColorScheme is called and restores it on startup:

<!-- App.vue -->
<script setup lang="ts">
import { ThemeProvider } from './theme'
import HomeScreen from './HomeScreen.vue'
</script>

<template>
  <!-- true = default storage key; or pass a custom key string -->
  <ThemeProvider follow-system persist>
    <HomeScreen />
  </ThemeProvider>
</template>

With follow-system + persist, the theme follows the OS until the user makes an explicit choice, which is then persisted and restored on the next launch.

Advanced: manual control

For custom flows (e.g. resolving the initial scheme yourself, or a custom storage backend), store the preference with useAsyncStorage and read it back before the provider mounts:

// theme.ts (additions)
import { useAsyncStorage } from '@thelacanians/vue-native-runtime'

const storage = useAsyncStorage()
const STORAGE_KEY = 'theme-preference'

export async function loadSavedScheme(): Promise<'light' | 'dark' | null> {
  const saved = await storage.getItem(STORAGE_KEY)
  return saved === 'light' || saved === 'dark' ? saved : null
}

export function saveScheme(scheme: 'light' | 'dark'): Promise<void> {
  return storage.setItem(STORAGE_KEY, scheme)
}

A child screen can call useTheme().toggleColorScheme() to flip the scheme; pair that with saveScheme(colorScheme.value) to persist the explicit choice.

Semantic colors

In addition to hex/rgb values, the style engine recognizes semantic color names that resolve to platform dynamic colors (auto light/dark): background, label, secondaryLabel, tertiaryLabel, separator, systemBlue, systemRed, systemGreen, systemOrange, systemGray.

const styles = createStyleSheet({
  container: { backgroundColor: 'background' },
  title: { color: 'label' },
  divider: { backgroundColor: 'separator' },
})

These map to UIColor.systemBackground/.label/… on iOS, theme attributes on Android, and NSColor.windowBackgroundColor/.labelColor/… on macOS.

Summary

  • createTheme → tokens + <ThemeProvider> + useTheme.
  • createDynamicStyleSheet(theme, factory) → reactive, theme-aware styles.
  • useColorScheme → follow the OS dark-mode setting.
  • useAsyncStorage → persist an explicit user preference.
  • Full runnable example: examples/theming/.

See also

  • Styling — the full style property reference.
  • useColorScheme — system appearance detection.
  • useAsyncStorage — key/value persistence.
Edit this page
Last Updated: 7/28/26, 7:56 PM
Contributors: github-actions[bot], Abdul Hamid
Prev
Styling
Next
TypeScript