Coming from React Native
Kog is React Native for microcontrollers. You import View, Text, Image, Pressable, StyleSheet, Animated, Platform and the rest from 'react-native', hooks from 'react', and the code you write looks like the code you already write for a phone. There is no className, no CSS and no DOM: those are compile errors that name the React Native replacement.
Kog holds itself to two rules, and this page is how it keeps the first one honest:
- React Native is the reference. When Kog's API shape, name or default differs from React Native's, the difference is listed below with its reason. If it's not on this page, it behaves as in React Native, and a difference you find is a bug.
- No surprises in the engine.
Map,Set, arrays and objects keep the performance a JavaScript developer expects (Map.getandMap.deleteare O(1), not a scan), and anything that can't work fails loudly with a message that tells you the fix. It never silently does nothing.
Read Thinking in Kog for the engine model: components mount once and updates are fine-grained. It changes how your program runs, not what a correct program computes.
What is the same
- Components and props.
View,Text,Image,ImageBackground,TextInput,ScrollView,FlatList,SectionList,Pressable, theTouchable*family,Button,Switch,ActivityIndicator,Modal,SafeAreaView,KeyboardAvoidingView, plus the communitySlidershape. A community-shapedProgressBaris a Kog extension from@kog/ui. The full list is in thereact-nativeAPI reference. - Layout. Every
Viewis a flex container withflexDirection: 'column'andalignItems: 'stretch'by default, as in React Native.position: 'absolute'with any combination oftop/left/right/bottom, percentages,gap,flexWrapandalignSelfall work. - Defaults.
ImageusesresizeMode: 'cover'.ScrollViewandFlatListscroll vertically unlesshorizontalis set.TouchableOpacitydims toactiveOpacity0.2. Fractional sizes round to the nearest pixel. - StyleSheet.
StyleSheet.create, style arrays (later wins, falsy entries skipped),StyleSheet.flatten,compose,absoluteFill,absoluteFillObjectandhairlineWidth. Inlinestyle={{ width: w }}works with dynamic values. - Text rendering.
false,true,nullandundefinedrender nothing, so{count > 0 && count}is empty at zero. - Hooks.
useState,useReducer,useEffect/useLayoutEffectwith React's deps semantics,useMemo,useCallback,useRef(includingrefon built-in elements),useContext,useId,useImperativeHandle,useSyncExternalStore,memo,forwardRef,Fragmentandimport React from 'react'. - Platform APIs.
Platform,Dimensions,useWindowDimensions,PixelRatio,Appearance,useColorScheme,Keyboard,Alert,AppState,EasingandAnimated(with the limits below). - Images.
require('./logo.png')bundles an image with your app,{ uri: 'https://…' }loads one from the network, and@2xvariants are picked for you. See Images and assets. - Networking.
fetch,WebSocket,AbortController,setTimeoutandclearTimeoutare globals, as in React Native. - Audio. React Native has no audio API, so Kog follows Expo's:
import { useAudioPlayer, useAudioPlayerStatus, useAudioRecorder } from 'expo-audio'works as in an Expo app, withrequire('./sound.mp3')for bundled sounds. See the Audio API.
Every divergence
Each row is something a React Native developer could trip over. The reason column says why Kog can't simply match React Native.
Engine and language
| React Native | Kog | Why |
|---|---|---|
| A component re-renders when its state or props change. | The component body runs once, at mount. Expressions that read state re-run on their own, and the compiler keeps derived consts live. | Kog has no virtual DOM. Updates call one native setter each, which is what makes a 60 fps slider affordable on a microcontroller. Thinking in Kog has the details. |
setCount(1); console.log(count) logs the old value. | It logs 1. State writes are synchronous; the screen update is batched. | Kog reads state live instead of from a render snapshot. |
| Handlers can capture stale state. | Handlers always read current state. | Same reason: there is no render snapshot to go stale. |
| An effect's cleanup sees the values from the render that created it. | The cleanup reads state live, so on a re-run it sees the new value. Copy a value to a const inside the effect to keep the old one. | Closures read signals, not snapshots. |
useEffect(fn) with no deps runs after every render. | It re-runs when a reactive value it reads changes. | There are no renders to run after. |
useEffect(fn, [props]) re-runs whenever the parent re-renders, because props is a new object each time. | It never re-runs: a component's props object is created once and keeps its identity. The compiler warns when a deps array lists the props object, or an alias of it such as const p = props. | List the values you depend on, [props.id], as the exhaustive-deps lint already asks. |
| An effect whose dependency changed while it ran runs again, even if that run threw. | If a useEffect(fn, deps) writes one of its own dependencies and then throws, that pending re-run is dropped. The next change to a dependency runs it again. | The throw abandons the queued run instead of replaying it. |
useMemo deps decide when it recomputes. | useMemo tracks what it reads; its deps array is ignored. | The memo is a signal-graph node that already knows its inputs. |
memo(Component, areEqual) skips re-renders. | memo returns the component unchanged, and areEqual is never called. forwardRef passes ref as the second argument. | Nothing re-renders, so there is nothing to skip. |
| A context consumer re-renders when the value changes. | The provider's value is read once at mount. Provide an accessor (value={() => theme}) for a context that changes. | Context is fine-grained: only the expressions that call the accessor update. |
<Child {...props} /> forwards whatever keys props has on each render. | The spread's values stay live, but its set of keys is read once, at mount. | A component mounts once, so its prop object is built once. |
<View {...props} /> spreads onto a built-in element. | A compile error: Spread props are not supported in v1. | Each built-in prop compiles to its own native setter, so the compiler has to see the names. |
| A fragment can be a list row. | Fragments work everywhere except as a .map() or FlatList row, where they are a compile error. The same goes for a row component whose root is a fragment or an early-return guard. Wrap the row in a View key={…}, or filter the list instead of returning null. | The keyed list reconciler moves exactly one widget per row. |
JSX can appear in any function, such as a renderRow() helper. | JSX compiles only inside a component (a capitalized function, or one wrapped in memo/forwardRef from react) or an inline render prop such as renderItem={({ item }) => …}. Anywhere else it is a compile error. Turn the helper into a component. | The compiler lowers JSX component by component. |
| Early returns can appear anywhere in a component. | Only as guards at the top of the component (if (!visible) return null), and those stay reactive. Others are a compile error. | The body runs once, so a return in the middle can't be re-evaluated. |
Setting a prop back to undefined restores the default. | The last value stays in effect. The same holds for a dynamic size that becomes 'auto'. Image.source is an exception: null/undefined clears the image and releases its decoded memory. | The native widget tree is write-only and has no "unset" operation yet. |
ref={cond ? a : b} follows the condition; changing poolSize or rowHeight resizes a list. | All three are read once, at mount. | The element and its recycling pool are built once. |
| A conditionally rendered subtree rebuilds only when its condition changes. | The same, but a callback ref that reads state while the subtree is being built also rebuilds it when that state changes. | Building a branch runs inside the effect that decides the condition. Read state in an effect or a handler, not in a callback ref. |
| An error while rendering a subtree replaces it with the error boundary's fallback. | If a conditional subtree throws while it rebuilds, the widgets it built before the throw stay on screen until the condition changes again. The error still reaches the nearest ErrorBoundary. | Widgets are created as the subtree builds; there is no render phase to discard. |
| Fast Refresh keeps component state across edits. | Hot reload remounts the app in well under a second, keeping the current route but not useState values. | State-preserving reload is on the roadmap. |
| An effect that keeps setting state it reads throws "Maximum update depth exceeded". | After 100,000 updates Kog logs kog: effects did not settle after 100000 updates … and stops only that looping effect; everything else keeps working. | The same protection, scoped to the offender. |
flushSync or a batched update inside a running effect applies before the effect continues. | It is deferred until the running effect returns, so an effect never re-enters itself. | Effects run to completion, one at a time. |
setInterval is a global. | There is no global setInterval (calling it throws a ReferenceError). Use useInterval from @kog/react. | It hasn't been added to the device's built-in library yet. setTimeout and clearTimeout are real. |
Re-exporting components through your own index.ts is harmless. | Import built-in components directly from 'react-native'. A local barrel makes the compiler treat them as your own components, which fails at build or mount with a message that says so. | The compiler resolves built-in components through their import. |
JavaScript performance
These follow from the engine Kog runs on device. Operations a web developer relies on keep their web complexity; the few residuals are listed with the idiom that avoids each.
| On V8 | In Kog | What to do |
|---|---|---|
Map/Set get, set, has, delete are O(1); size is O(1). | The same: O(1) amortized, including delete. Iteration is in insertion order and live: entries added during a loop are visited, deleted ones are skipped. | Nothing. Use Map as you would on the web. |
The iteration protocol: Symbol.iterator, Array.from, new Map(iterable), [...map], const [a, b] = set, and for…of over arrays, strings, typed arrays, maps, sets and your own iterables. | All of it works. for…of over an array or a Set allocates nothing, and over a Map it allocates one [key, value] pair per step, as V8 does. new Map([[1]]) maps 1 to undefined, as the spec says. | Nothing. |
map.keys(), values(), entries() are lazy, live iterators. | The same. | Nothing. |
break out of a for…of calls the iterator's return(). | return() is not called on an early break. | Don't rely on return() for cleanup in a custom iterator. |
s += x in a loop is amortized O(n). | It is O(n²): each concatenation copies the string. | Push parts to an array and join once. |
| Objects with thousands of distinct dynamic keys are fine. | The first use of each new property key costs an insertion into a sorted table, O(number of keys). | Use a Map for open-ended key sets. |
The full engine list is in the native runtime's performance characteristics.
JavaScript language and library
Most of the standard library behaves as it does in V8 and Hermes. That includes the full Date object, JSON.stringify with a replacer, space and toJSON, and JSON.parse with a reviver and JSON's exact grammar (01, 1. or a raw tab in a string is a SyntaxError, as in V8). Every array-index key comes first in Object.keys, for…in and JSON, in ascending order, and arrays have real holes. Object.hasOwn and hasOwnProperty work on strings, and report a function's length, name and prototype and an error's message as own properties. A throwing sort comparator propagates, apply takes array-likes, and new works on bound functions. Also available: encodeURIComponent and its siblings, queueMicrotask, Promise.any (which rejects with an AggregateError, now a global), new Error(message, { cause }) (with cause hidden from Object.keys and JSON, as in V8), Object.is/hasOwn/entries/values/fromEntries/getOwnPropertyDescriptors, Array.of, at/flat/flatMap/find/findLast/includes/fill, String at/includes/startsWith/endsWith/padStart/padEnd, Number.isSafeInteger, Math.cbrt/hypot, typed-array iteration and array methods, ArrayBuffer.isView/slice, and React Native's global alias of globalThis. These are the differences:
| On V8 / Hermes | In Kog | What to do |
|---|---|---|
Date uses the device's time zone. | There are no time zones: local time is UTC. getTimezoneOffset() is 0, and toLocaleString()/toLocaleDateString() print a fixed en-US form. Date.now() reads the device clock, the same one as clock. | Format dates yourself, and apply any UTC offset yourself. |
| Arrays accept any property key. | A key that isn't an array index, such as a[-1] = x or a[1.5] = x, throws a TypeError that suggests a Map or an object. Named properties like a.extra = 1 work. Writing far past the end allocates the gap. | Use a Map or an object for sparse or non-integer keys. |
Object.freeze, seal and property attributes (writable, enumerable, configurable) are enforced. | freeze, seal and preventExtensions return the object unchanged and isFrozen is false. Every property reads as writable, enumerable and configurable, so defineProperty with enumerable: false still enumerates. A getter or setter descriptor on an array index is a TypeError. Getters and setters on objects work. | Don't rely on a frozen object throwing, or on non-enumerable properties staying out of Object.keys. |
| Sloppy-mode code is allowed. | The engine is always strict. Assigning to an undeclared variable throws a ReferenceError, writing a getter-only property throws a TypeError, arguments doesn't alias the parameters, and a plain function call doesn't get this === globalThis. | Write strict-mode code, which TypeScript already expects. |
WeakMap, WeakSet, WeakRef, Proxy, Reflect, DataView, BigInt, Intl and structuredClone exist. | They don't; typeof gives 'undefined', so feature tests work. Class #private fields compile to a WeakMap, so a bundle that uses them fails at load with ReferenceError: WeakMap is not defined. | Use a Map and delete entries yourself; read bytes through typed arrays. Use a plain or _-prefixed field instead of #private. |
class X extends Error (or extends Array) makes instanceof X true. | Compiled to ES5, the subclass is lost: instanceof X is false and its methods are missing. Node behaves the same on the same ES5 output. | Call Object.setPrototypeOf(this, X.prototype) in the constructor. |
RegExp supports lookbehind, named groups and \p{…}. | They are SyntaxErrors, and they make the whole bundle fail at load. | Use plain groups and explicit character classes. |
Direct eval(…) and new String(x) work. | Direct eval is a SyntaxError, and new String, new Number and new Boolean throw a TypeError. | Use indirect (1, eval)(…), and String(x), Number(x), Boolean(x). |
Symbol is a primitive type. | Symbol is a string-keyed shim: typeof Symbol() === 'string', though Symbol('x').toString() gives 'Symbol(x)'. Symbol keys are hidden from Object.keys, for…in and JSON.stringify, as in V8, and so is any ordinary string key that starts with @@sym:. | Don't rely on typeof x === 'symbol', and don't start your own keys with @@. |
A cause assigned to an error after it was created shows up in Object.keys. | It is hidden, like a cause passed to the constructor. | Don't enumerate an error's keys to find its cause; read err.cause. |
Array.prototype methods work on any array-like. | They work on arguments but not on typed arrays: Array.prototype.slice.call(u8) is a TypeError. | Use the typed array's own methods. |
| The rest of ES2023 is there. | Also absent: copyWithin, toSorted and the other copying methods, Promise.withResolvers, String normalize/localeCompare/matchAll/raw, typed-array from/of/at/join/sort, and Error.captureStackTrace. 'ß'.toUpperCase() stays 'ß'. A stack overflow is an InternalError, not a RangeError. | Feature-test, or use the older equivalent. |
The build catches most of these at the source line, in every app module (.ts included): RegExp lookbehind, named groups, \p{…} and the d/v flags, #private members, direct eval, new String/Number/Boolean, BigInt literals, and the absent globals (WeakMap, Proxy, Intl and the rest) are errors. A module that feature-tests a global with typeof is allowed to use it. localeCompare, normalize, matchAll, toSorted, toReversed, toSpliced, copyWithin and Error.captureStackTrace are warnings, because the compiler can't always tell what type the value is. So is reading a super.x property in a class: TypeScript's ES5 output runs the getter with the prototype as this, so call a method instead. Packages from node_modules are checked too, as warnings prefixed dependency:.
The engine's own record of these is native/vendor/mquickjs/PROVENANCE.md.
Composing components
children works as in React Native. A component can render {props.children}, destructure { children }, type its props with PropsWithChildren, show children behind a condition ({open && children}, {loading ? <Spinner /> : children}), return children as its whole render, or forward them to another component or a context provider. Children keep their JSX order, including a conditional child or list rows that appear later. Text children arrive as React Native passes them, so <Label>hi</Label> gives children === 'hi', and the component renders them inside its own <Text>. A component can also be passed as a prop and rendered as a tag: ({ Icon }) => <Icon size={24} />.
import { useState, type PropsWithChildren } from 'react';
import { View, Text, Pressable, StyleSheet } from 'react-native';
function Card({ title, children }: PropsWithChildren<{ title: string }>) {
return (
<View style={styles.card}>
<Text style={styles.title}>{title}</Text>
{children}
</View>
);
}
function Collapsible({ open, children }: PropsWithChildren<{ open: boolean }>) {
return <View>{open && children}</View>;
}
function Label(props: PropsWithChildren) {
return <Text style={styles.label}>{props.children}</Text>;
}
function IconButton({ Icon, onPress }: { Icon: (p: { size: number }) => JSX.Element; onPress: () => void }) {
return (
<Pressable onPress={onPress}>
<Icon size={24} />
</Pressable>
);
}
function Dot({ size }: { size: number }) {
return <View style={{ width: size, height: size, borderRadius: size / 2, backgroundColor: '#2f6fed' }} />;
}
export default function App() {
const [open, setOpen] = useState(false);
const [n, setN] = useState(0);
return (
<Card title="Settings">
<Label>count = {n}</Label>
<IconButton Icon={Dot} onPress={() => { setOpen(!open); setN(n + 1); }} />
<Collapsible open={open}>
<Text>More options</Text>
</Collapsible>
</Card>
);
}
const styles = StyleSheet.create({
card: { padding: 12, gap: 8 },
title: { fontSize: 18 },
label: { color: '#888888' },
});
| In React Native | In Kog | Why |
|---|---|---|
A prop can hold an element: <Card header={<Title />} />, rendered with {props.header}. | A build error: {props.header}: Kog renders only `children` as a slot, not an element passed in another prop. Pass the component instead (header={Title}) and render it as a tag: <props.header />. | children is the one slot; any other prop that holds an element is ambiguous with plain data. |
A render prop returns JSX: {props.render(item)}. | A build error: Render props are not supported in Kog (a component mounts once and builds its widgets in place). Pass a component (Item={MyItem}) and render it with the values as props: <props.Item value={…} />. | A component mounts once, so there is no render pass to call the function from. Render props on built-in lists, such as FlatList's renderItem, do work. |
children can be a function: {props.children(value)}. | A build error: children as a function is not supported in Kog. Use the component-as-prop pattern above. | Same reason. |
<Label>count = {n}</Label> passes children as an array of parts. | It passes one string, 'count = 3', which updates in place when n changes. | Text children are joined into one label. |
Text can sit next to elements as children: <Card>hi <Icon /></Card>. | A build error: Text must be wrapped in <Text>…</Text>. The same goes for text children passed to a built-in component such as Modal or a context provider. At runtime, text rendered outside a <Text> throws React Native's Text strings must be rendered within a <Text> component. | React Native has the same rule; Kog enforces it when it builds. |
Changing a component prop (Icon={other}) re-renders with the new one. | A component prop is read once, at mount. Children are built once, when the wrapper mounts or when their condition turns on. | Components mount once. |
Styling
| React Native | Kog | Why |
|---|---|---|
fontWeight draws nine weights. | fontWeight accepts React Native's full set and lowers to two: normal below 600, bold from 600. The device currently draws only the regular face. | The device ships one compiled-in font family; custom fonts are planned. |
fontFamily picks any installed font. | Accepted and ignored. | Same: one font family on device. |
| Text can use any Unicode character. | The built-in font covers printable ASCII plus °, • and LVGL's symbols; anything else draws as a box. | Fonts are compiled into the firmware to fit in flash. |
textAlign: 'justify', alignItems: 'baseline'. | Not available; using either is a type error. | LVGL can't draw them. |
margin: 'auto' pushes or centers. | A compile error that points you to alignSelf/justifyContent. width: 'auto' and other size or inset 'auto' values are accepted and mean "unset". | LVGL's flex has no auto margins. |
lineHeight is the total height of a line. | lineHeight is the extra space between lines. | It maps to LVGL's line spacing. |
borderTopLeftRadius and the other per-corner radii. | One borderRadius for all corners. | LVGL draws a single radius. |
borderTopColor and per-side colors, borderStyle: 'dashed'. | One borderColor; solid only. | LVGL draws a single border color and style. |
flexShrink, flexBasis, aspectRatio. | flexShrink accepts only 0; flexBasis and aspectRatio are not supported. flexGrow and the start/end insets and margins work (left-to-right only). | LVGL's flex has no shrink or basis; aspectRatio is planned. |
transform takes perspective, matrix and the 3D rotations. | rotate, scale, scaleX, scaleY, translateX, translateY, skewX and skewY work; the rest are compile errors. | LVGL's transforms are 2D. |
transform entries compose in list order and may repeat. | Each kind appears once (a repeat is an error), and LVGL applies them in a fixed order: translate, then scale and rotate about the origin. | LVGL has one value per transform property, not a matrix. |
| In a style array, later entries win. | The same, including over a dynamic inline value, even when the later entry is props.style or an imported style. The exception: transform, transformOrigin, shadowOffset, shadowRadius and elevation set dynamically inline still outrank later entries the compiler can't see, so put such an inline object last. | A dynamic inline value is set on the element itself; the compiler re-applies later entries over it, except for those composite keys. |
StyleSheet.flatten returns the styles as written. | For a StyleSheet.create style it returns the values as the device stores them: opacity: 0.5 comes back as 0.502, fontWeight: '700' as 'bold', marginStart as marginLeft. Plain objects are merged unchanged. | Sheets are resolved to device values at build time; the authored spelling isn't kept on the device. |
Any style key works in a style array or StyleSheet, and unknown keys are ignored. | Every React Native style key Kog lacks is an error that gives the reason, such as style 'borderTopWidth' is not supported on Kog: LVGL draws one border width for all sides: use borderWidth. It's a build error for styles the compiler sees, and the same text, prefixed kog: , is thrown for style objects built at runtime. | Ignoring a key would be a silent difference on screen. |
| An inline object chosen by a condition can hold any values. | It must be all literals; put the condition inside the value ({ width: big ? 200 : 100 }). | The chosen object is interned whole, and a dynamic value can't be unset. |
overflow: 'scroll' on a View does nothing. | It makes the view scroll (without clipping to its rounded corners). | LVGL objects scroll natively. |
| A pressed style replaces the default one. | In a Pressable function style, the pressed style layers over the default (like CSS :active). Set every changing property in both branches. Only the shapes pressed ? a : b, pressed && a and arrays of them are accepted. | Pressed styles compile to native state styles, so a press runs no JavaScript. |
ellipsizeMode head and middle. | Both truncate at the tail. tail and clip behave as in React Native. | LVGL truncates only at the end of a label. |
elevation draws a platform shadow. | elevation: n becomes LVGL's plain shadow of radius 2n offset n pixels down. | There is no platform shadow to defer to. |
Nested Text styles a span. | Elements inside Text are a compile error. | LVGL labels are single-style leaf widgets. |
Colors can be named ('red'), hsl(), hex, rgb()/rgba() or 'transparent'. | Hex (3, 4, 6 or 8 digits), rgb()/rgba() and 'transparent' work, and alpha 0 is fully transparent. Other named colors and hsl() are errors that list the accepted forms. | Not implemented; the hex and functional forms cover every color. |
| Sizes are density-independent points. | Sizes are logical pixels; boards with a HiDPI panel scale them (PixelRatio.get() reports the factor). StyleSheet.hairlineWidth is 1. | Embedded panels have a fixed density. |
Components and APIs
| React Native | Kog | Why |
|---|---|---|
Animated.event maps gestures or scroll to values. | Not available. | It needs JavaScript on every frame; Kog's animations run natively in C. |
interpolate maps to numbers, angles and colors. | Numbers and angles ('deg'/'rad') work, with extrapolate; color outputs and an easing option throw. A two-point range is exact; a multi-stop or clamped range runs as consecutive linear segments, exact at each breakpoint. | The native engine interpolates numbers; colors need a native color blend (planned). |
An animated value's listeners and __getValue() track every frame. | During a run the JavaScript value is already the end value; listeners fire on setValue and at the end of a run. stop() estimates the stopped value from the elapsed time. | Animations run entirely in C, with no JavaScript per frame. |
Animated.add, subtract, multiply, divide, modulo, diffClamp. | They throw. | Each needs JavaScript on every frame. |
| Image source candidates can be an array of URI objects. | Pass a single { uri } or a bundled require() result; candidate arrays are unsupported. Bundled require() returns a descriptor object instead of an opaque number. | Image variants are selected at build time for the board. |
Animated.Image, Animated.FlatList and Animated.SectionList are ordinary values. | They work as JSX tags (<Animated.FlatList>), but reading one as a value (const L = Animated.FlatList) throws with a message that points at the plain component. | It keeps the list and image code out of bundles that don't use them. |
Animated.spring and decay simulate physics, using velocity. | spring runs an exact native curve whose duration comes from React Native's stiffness and damping formulas; decay is an ease-out to the same rest point. velocity is ignored. | The native engine runs timed curves, not a physics simulation. |
An Image whose new source fails to load goes blank. | When a network source fails, the previous network image stays on screen, and stays in memory, until the next source loads. An SD-card source that fails is cleared. | The previous decoded image is released only when a new one replaces it. |
Easing.back(s), elastic(b), poly(n), step0, custom easing functions. | Every built-in curve runs exactly; back and elastic only with their default parameters, poly(n) for whole numbers 1 to 63. step0, step1 and custom functions throw. | Curves are native presets, not JavaScript functions. |
Image loads network images anywhere. | Network and SD-card images need a board with PSRAM, which today means the Waveshare 1.46B or AMOLED-2.06. Elsewhere onError says so, before anything is downloaded. Bundled require() images work everywhere. | Decoding a downloaded image needs megabytes of RAM; bundled images draw straight from flash. |
| The OS keyboard has its own height. | Kog draws an on-screen keyboard when a TextInput is focused; it covers half the display. KeyboardAvoidingView treats behavior: 'height' like 'padding'. | There is no OS keyboard. |
Modal's onRequestClose fires on the Android back button. | It is accepted but never fires. presentationStyle and other iOS options are accepted and ignored. | There is no back button. |
Refs on built-in components have measure, focus, scrollTo and the rest. | The same, but measure reports pageX/pageY equal to x/y. findNodeHandle(ref) gives the raw native handle. | Kog has one coordinate space. |
TouchableHighlight draws its underlay as a separate layer; TouchableWithoutFeedback clones its child. | The underlay is the touchable's own pressed background (default black), and activeOpacity applies only when you set it; TouchableWithoutFeedback wraps its child. | Pressed looks are native state styles. |
ActivityIndicator's size sets its size. | A width/height in its style overrides size. | size is applied as the first style. |
animating={false} with hidesWhenStopped={false} shows a stopped indicator. | animating={false} always stops the spinner and hides its arcs, so hidesWhenStopped={false} changes nothing. | LVGL's spinner has no stopped look to show. |
Slider values are continuous floats, and step can be fractional. | Values are scaled onto LVGL's integer slider range, with the scale fixed at mount from the initial range and step; a native step snaps to whole numbers. | LVGL sliders are integer-valued. |
hitSlop, testID, nativeID and the accessibility* props work. | They are accepted and dropped at build time. | There is no accessibility service or test driver on the device. |
An absolute child with left and right (or top and bottom) and no size stretches between them, measured from the parent's padding box. | It stretches too, but the insets measure from the parent's content box (inside its padding). A parent sized by its content also counts its absolute children. StyleSheet.absoluteFill and absoluteFillObject have React Native's exact shape. | Kog stretches insets itself on top of LVGL, which only anchors them. |
An Image with StyleSheet.absoluteFill stretches over its parent. | A bundled image keeps its own size unless its style sets width and height; ImageBackground always fills. | A bundled image's own size counts as an authored size, and an authored size wins over the insets. |
A View's overflowing children are visible by default (overflow: 'visible'). | Unless you set overflow: 'visible', children are clipped to the view's box (it still doesn't scroll). A child that reaches past several ancestors needs 'visible' on each of them. | LVGL clips by default, and flipping it would change the rendering and touch areas of every existing app. |
zIndex paints a child above its siblings without moving it. | The same for absolutely positioned children. An in-flow child can't paint above a later in-flow sibling: its zIndex is clamped, with a warning, so the layout never changes. | LVGL's flex lays children out in drawing order. |
onLayout fires when a view moves or resizes. | It fires on the first layout and when the size changes, not for a move alone. | Kog listens for LVGL's size-change event. |
FlatList measures each row. | Recycling needs one fixed rowHeight. Header, footer, empty and section-header rows each take one row slot, and separators render inside the item's slot. numColumns, inverted and getItemLayout are not supported. VirtualizedList is FlatList (no getItem). | Rows are recycled natively from a fixed pool, which keeps memory flat. |
A style object passed down as a prop (style={props.style}) costs nothing extra. | A style object built at runtime is interned when first seen: O(number of keys), deduplicated. An Animated value inside one throws; put it in an inline literal on the element instead. | Styles are native objects. |
Button renders platform chrome; Android uppercases the title. | color is the background color (Android's meaning) and the title is not uppercased. | One look on every board. |
Platform.OS is 'ios' or 'android'. | Platform.OS is 'kog'. Platform.select reads a kog key, then native, then default. | It's neither, and code that branches on it should know. |
Dimensions change on rotation. | Dimensions are fixed; change listeners never fire. | The panel never rotates at runtime. |
AppState changes with backgrounding. | Always 'active'. StatusBar is accepted and draws nothing. I18nManager is left-to-right only; forceRTL(true) throws. | There is one app and no OS around it. |
Alert.alert shows a native dialog. | It shows a card on the top layer with React Native's buttons and styles (default, cancel, destructive); cancelable dismisses on a backdrop tap. Alert.prompt throws. | There is no OS dialog to call. |
| Navigation uses React Navigation. | Navigation uses @kog/router, shaped like React Router. A React Navigation-shaped facade is planned. | See Routing. |
Linking, Share, Clipboard, BackHandler. | Exported so ported code compiles, but every call throws or rejects with a message that names the reason, such as kog: Linking.openURL is not available on Kog (…). | There are no other apps, URL schemes or OS services on a microcontroller. |
Vibration.vibrate follows the platform: exact durations on Android, a fixed 400 ms per pulse on iOS. | Android semantics on every target, on the board's motor. A board without peripherals.motor throws DEVICE_UNSUPPORTED; cancel() there does nothing. | Exact durations on an on/off motor; one behaviour everywhere. |
expo-haptics drives the Taptic engine or the platform's effects, and resolves a no-op where there is none. | Each feedback type is a fixed motor pattern (table); without a motor every call rejects DEVICE_UNSUPPORTED. | Boards have a plain motor, and a missing one should be loud. |
expo-sensors subscriptions on a phone without the part never fire. | They throw SENSOR_UNAVAILABLE naming the board. DeviceMotion, Magnetometer, Barometer, LightSensor and Pedometer are unavailable on every board for now; Accelerometer and Gyroscope work where the board declares peripherals.imu. | Silence would hide a missing part. isAvailableAsync() is the probe. |
Accelerometer z is +1 g face up on Android and -1 on iOS. | The display's frame with Android's sign on every board (x right, y up, z out of the glass), whatever the IMU's mounting. | One convention; peripherals.imu.orientation maps the chip. |
isLowPowerModeEnabledAsync() and the battery-optimization check report the OS. | Always false; the low-power listener never fires. | There is no OS power-saver mode. |
expo-brightness keeps the app and system brightness apart on Android. | They are one value; the mode is always MANUAL and AUTOMATIC rejects. | One app owns the display. |
| Device permissions can be denied. | getPermissionsAsync/requestPermissionsAsync always resolve granted. | There is no permission system. |
useBatteryLevel() and the other battery hooks re-render the component. | const level = useBatteryLevel() stays live without a re-render, as useColorScheme() does. | Components mount once; the compiler keeps the binding current. |
LayoutAnimation animates the next layout change. | Layout changes apply immediately, with a one-time warning; the completion callback still runs. | LVGL's flex re-layout isn't animated. Animate properties with Animated. |
Audio (expo-audio)
expo-audio resolves to @kog/audio. The full list, with the API, is in the Audio reference.
| expo-audio | Kog | Why |
|---|---|---|
Plays AAC/.m4a, Opus, Ogg and FLAC. | WAV and MP3 only. A bundled file in another format is a build error that names the ffmpeg conversion; a card file reports AUDIO_FORMAT. | The decoders run on a microcontroller. |
require('./song.mp3') is a number. | The same; the sound is packed into the asset pack with your images and decoded in place from flash. | |
Recording presets write AAC .m4a. | Recordings are WAV (PCM s16). The preset's sampleRate and numberOfChannels are honoured; bitRate is ignored. | There is no AAC encoder on the device. |
http(s) sources stream. | They throw AUDIO_SOURCE; download to the SD card with fetch() or bundle with require(). | Not in v1. |
playbackRate corrects pitch. | The rate (0.25..4) changes the pitch; shouldCorrectPitch is always false. | No time-stretch DSP. |
useAudioPlayer re-creates the player when its source changes. | It calls replace() on the same player, which keeps its volume, loop and rate. | A component body runs once. |
useAudioPlayerStatus re-renders the component on each update. | It returns a live status: status.playing in JSX, and values derived from it, update in place. | Nothing re-renders. |
Lock-screen controls, background modes, setAudioModeAsync fields, permission prompts. | Accepted with no effect; permissions are always granted. | One app owns the device; there is no OS. |
Playlists are gapless; useAudioStream captures PCM. | Playlists have a few ms between tracks; useAudioStream throws. | One player reloads per track; real-time capture is not in v1. |
| A board's audio is always there. | On a board without a speaker, play() throws AUDIO_UNAVAILABLE naming the board; players still exist (with an error status), so the same app runs there. Without a microphone, record() throws. At most 8 players play at once (AUDIO_BUSY). | Audio hardware is a per-board option, and the mixer has 8 voices. |
Two behaviours are the same as in React Native but easy to miss on a device:
Animated.loopkeeps running after unmount. A loop on auseRef(new Animated.Value(0)).currentkeeps iterating on a timer after its component unmounts. It leaks no widgets, but it does keep working. Stop it in the effect's cleanup:useEffect(() => { const a = Animated.loop(…); a.start(); return () => a.stop(); }, []).- A transparent
Modalstill takes every touch. While it is visible, nothing underneath it receives touches, even where the modal draws nothing.
Kog extensions
These are Kog-only and live outside 'react-native', so the facade keeps React Native names separate from Kog extensions. Importing one from 'react-native' is a compile error that tells you to import it from @kog/ui:
@kog/ui:ProgressBar(the community shape),Arc(dials and gauges),RollerandDropdown(pickers),Checkbox,Bar,Spinner,Chart,DynamicColor,ThemeProvider(a scheme override for a subtree),ErrorBoundary,GestureDirection.@kog/react:useInterval,useTimeout,useAnimatedValue,useDisplay(shape-aware display geometry for round panels),useStorage,useDevice.@kog/hardware:useButton,useDigitalRead,useDigitalWrite,useAnalogRead,usePWM, and I2C/SPI buses.- Expo device modules:
expo-battery,expo-brightness,expo-sensorsandexpo-hapticsresolve to Kog's@kog/devicewith no install. See Device services. - Zero-import globals:
WiFi,TCPSocket,UDPSocket,TCPServerandclock.