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()
mode—Ref<'light' | 'dark' | 'system'>, defaults to'system'. Writable directly, or viasetMode.isDark— resolvedComputedRef<boolean>. Folds'system'to the OS appearance on web (viamatchMedia). On Lynx native today this always resolves tofalse(light) — there's no stable native appearance signal exposed yet, so treatsystemas web-only for now.setMode(next)— pins an explicit mode.toggle()— flips between light and dark based on what's currently shown, not themodevalue. Callingtoggle()whilemodeis'system'pins to the opposite of whateverisDarkcurrently 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.darkCSS 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.