Theming

Dark Mode

useColorMode, the app-root class + remount contract, and what's not yet migrated to the semantic surface tier.

useColorMode() (from @vyui/kit) is a module-level singleton — one color mode per app, shared across every call site, no provide/inject needed.

import { useColorMode } from '@vyui/kit'

const { mode, isDark, setMode, toggle } = useColorMode()
  • modeRef<'light' | 'dark' | 'system'>, defaults to 'system'. Writable directly, or via setMode.
  • isDark — resolved ComputedRef<boolean>. Folds 'system' to the OS appearance on web (via matchMedia). On Lynx native today this always resolves to false (light) — there's no stable native appearance signal exposed yet, so treat system as web-only for now.
  • setMode(next) — pins an explicit mode.
  • toggle() — flips between light and dark based on what's currently shown, not the mode value. Calling toggle() while mode is 'system' pins to the opposite of whatever isDark currently resolves to — there's no "toggle from system" state.

The app-root contract

The color mode only takes effect where you bind it. Whatever element is the color-mode boundary for your app needs both of these:

<script setup lang="ts">
import { useColorMode } from '@vyui/kit'
const colorMode = useColorMode()
</script>

<template>
  <view :class="{ dark: colorMode.isDark }" :key="colorMode.mode" class="">
    <!-- app -->
  </view>
</template>
  • :class="{ dark: isDark }" — flows the .dark CSS custom-property overrides to every descendant via inheritance.
  • :key="mode" — forces a full remount on every mode change. This is required, not decorative: Lynx native only applies a class change to freshly-mounted nodes, so toggling the class on an already-mounted tree silently does nothing without it. The trade-off is a brief remount blink and loss of component-local state in that subtree — hoist any real app state above this boundary.

VyApp wires this contract for you (plus mounts OverlayRoot and sets --ui-radius) — use it instead of hand-rolling the above unless you need a custom root shell.

Not every component theme has been migrated onto the semantic surface tier yet: Island / IslandButton, and the avatar-group overlap ring, are still hardcoded light-only by design for now.