Styling
Kog styling is React Native's StyleSheet model mapped onto LVGL's style system — and the two fit unusually well, because both want you to define styles once and share them.
import { StyleSheet } from '@kog/ui';
const styles = StyleSheet.create({
card: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
gap: 8,
shadowColor: '#000000',
shadowOpacity: 0.1,
},
title: { fontSize: 18, fontWeight: 'bold', color: '#111111' },
});
StyleSheet.create is resolved at build time: colors become integers, enums become numbers, and each unique style becomes a shared native style object — used by every widget that references it, which is exactly how LVGL wants styles used for memory efficiency. The idiom you know is the idiom that's optimal.
Layout is flexbox
LVGL has a real flexbox engine, so RN's layout props map directly:
flexDirection, justifyContent, alignItems, alignSelf, flexWrap, flex, gap/rowGap/columnGap, padding*, margin*, width/height/min*/max*.
flexDirectiondefaults tocolumn, exactly like React Native: a container withgap/justifyContent/alignItems/etc. and no explicit direction stacks its children vertically. (A style with no flex props isn't a flex container at all.)- Numbers are pixels (embedded displays are fixed-density; there's no
dp). - Percentages work:
width: '50%'. position: 'absolute'withtop/left/right/bottomfor overlays.display: 'grid'exists for grid layouts (minimal surface in v1).
Dynamic styles
Static styles cost nothing at runtime. When a style depends on state, only that property updates:
<View style={[styles.dot, isOn && styles.dotLit]} /> // conditional merge
<Bar style={{ width: `${progress}%` }} /> // reactive inline prop
<Pressable style={({ pressed }) => pressed && styles.down} /> // interaction states
Style arrays merge left-to-right (static parts merged at build time). Interaction states (pressed, focused, disabled, checked) also map to LVGL's native state selectors.
For smooth transitions, bind a useAnimatedValue to a prop and call .animate() — native interpolates each frame in C. One asymmetry to know: an animated value is in the prop's native units, so animated opacity runs 0–255, whereas static/reactive opacity is authored 0–1.
Theming and dark mode
const scheme = useColorScheme(); // 'light' | 'dark', reactive
const bg = DynamicColor({ light: '#fff', dark: '#111' });
Wrap your app in <ThemeProvider> to define palette tokens once; theme changes propagate reactively — the shared native styles are swapped, not re-created per widget.
Fonts and glyph coverage
Text renders with LVGL's built-in Montserrat font, which covers printable ASCII only. Non-ASCII characters — emoji, em-dashes (—), typographic quotes, arrows (→), bullets (•) — show as blank boxes on the device and in the simulator. Keep rendered strings ASCII (--, ->, -, straight quotes). Bundling custom fonts for wider glyph coverage is planned.