How to Implement Theming and Design Tokens in Native SDK Applications
The Native SDK runtime provides a lightweight theming system that enables real-time switching between light/dark color schemes and complete design-token packs using the LivePreview class in docs/src/lib/live-preview.ts.
Theming and design tokens in Native SDK applications are handled by a dual-axis system that combines color schemes with interchangeable token registers. According to the vercel-labs/native source code, this architecture allows you to re-style entire interfaces instantly without rebuilding scenes or re-rendering JavaScript components.
Understanding the Theming Architecture
The Native SDK theming engine operates on two distinct primitives that work together to control the visual appearance of your application.
Theme Schemes (Light/Dark)
The Scheme axis controls overall brightness using a binary light/dark toggle. When you call the engine API preview_set_theme(handle, dark), the canvas immediately redraws using the appropriate color values from the current token register while maintaining all layout and component structure.
Theme Packs (Design Tokens)
A theme pack is a self-contained register of design tokens—including palettes, control tables, and type scales—that replaces the entire design system definition. The engine API preview_set_theme_pack(handle, namePtr, nameLen) validates the pack name and applies the new token register across the whole preview. Because the Native SDK stores token data on the engine side, no JavaScript re-rendering occurs; the canvas simply redraws with the new values.
Working with the LivePreview API
The high-level LivePreview class wraps the low-level WebAssembly engine calls, exposing convenient methods for runtime theme manipulation.
Toggling Color Schemes
To switch between light and dark modes, use the setTheme method on your LivePreview instance:
import { PreviewEngine, LivePreview } from "./live-preview";
// Initialize engine (wasm instance loaded elsewhere)
const engine = new PreviewEngine(wasmExports);
const preview = engine.create(`...your Native markup...`, false);
// Toggle dark mode
function toggleDarkMode(isDark: boolean) {
preview.setTheme(isDark);
}
// Switch to dark scheme
toggleDarkMode(true);
This method maps directly to the engine's preview_set_theme function, triggering an immediate canvas redraw with the scheme-appropriate colors from the active token pack.
Switching Theme Packs
To replace the entire design token set, call setThemePack with one of the built-in pack identifiers:
import { themePacks, ThemePack } from "./live-preview";
// Apply a different token pack
function applyThemePack(pack: ThemePack) {
preview.setThemePack(pack);
}
// Switch to the "geist" design system
applyThemePack("geist");
The built-in packs are defined as a constant array in docs/src/lib/live-preview.ts at lines 55-61:
export const themePacks = ["house", "geist"] as const;
export type ThemePack = (typeof themePacks)[number];
Complete Implementation Example
Here is a complete workflow demonstrating dynamic theme switching with UI controls:
import { PreviewEngine, LivePreview, themePacks, ThemePack } from "./live-preview";
// Initialise the preview engine
const engine = new PreviewEngine(wasmExports);
const preview = engine.create(`...your Native markup...`, false);
if (!preview) throw new Error("Failed to create preview");
// Toggle light/dark scheme
function toggleDarkMode(isDark: boolean) {
preview.setTheme(isDark);
}
// Switch design-token packs
function applyThemePack(pack: ThemePack) {
preview.setThemePack(pack);
}
// Example: Cycle through packs on button click
let currentPack: ThemePack = "house";
document.getElementById("themeBtn")?.addEventListener("click", () => {
currentPack = currentPack === "house" ? "geist" : "house";
applyThemePack(currentPack);
});
// Initial state
toggleDarkMode(true);
applyThemePack("geist");
Extending with Custom Theme Packs
While the Native SDK ships with house and geist packs, you can add custom theme packs by registering them in the engine's Zig source code. Once compiled into the engine, custom packs are referenced by their manifest-facing name using the same setThemePack method. The engine validates the pack name against available registers before applying the new token set, ensuring type-safe theme management across your application.
Summary
- Theme schemes control light/dark brightness via
preview.setTheme(isDark), mapped topreview_set_themein the engine. - Theme packs are complete design-token registers accessible via
preview.setThemePack(pack), using the built-in"house"and"geist"constants defined indocs/src/lib/live-preview.ts. - Live re-theming occurs instantly on the engine side without JavaScript re-rendering, requiring only a canvas redraw.
- Custom packs can be added through Zig source compilation and referenced identically to built-in packs.
Frequently Asked Questions
What is a theme pack in the Native SDK?
A theme pack is a self-contained register of design tokens—including color palettes, spacing values, typography scales, and control tables—that defines a complete design system. According to the source code in docs/src/lib/live-preview.ts, packs like "house" and "geist" are stored on the engine side and swapped instantly via the preview_set_theme_pack API without requiring scene rebuilds.
How do I switch between light and dark modes?
Use the setTheme method on your LivePreview instance, passing a boolean where true indicates dark mode. This wraps the low-level preview_set_theme engine API and triggers an immediate canvas redraw using the current theme pack's color values for the selected scheme.
Can I create custom theme packs?
Yes. Custom theme packs are implemented by adding new token registers to the engine's Zig source code. Once compiled, these packs are referenced by name using the same setThemePack method that handles built-in packs, allowing you to extend the Native SDK with your own design systems.
Where are the built-in theme packs defined?
The built-in theme packs are declared as a TypeScript constant array in docs/src/lib/live-preview.ts at lines 55-61. The current implementation exports themePacks = ["house", "geist"] with a corresponding ThemePack type, which provides IntelliSense and compile-time validation when calling setThemePack.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →