Your first app
Scaffold a Lynx project, run it on a simulator and make it reactive — in about five minutes.
Prerequisites
You'll need Node.js 22+ and the native toolchain from Installation. The scaffolded app can check it for you with npx sigx doctor.
Scaffold the app
Using the CLI
pnpm create @sigx@latest hello-lynx -- --kind lynx --install
cd hello-lynx
pnpm dlx sigx doctorThis is the plain-styles template, which the samples below are based on. Add --styling daisyui for daisyUI components or --styling tailwind for Tailwind classes. With another package manager, add --pm pnpm (or yarn, bun) and use its spelling for the commands below. doctor prints a fix: line for anything that needs attention.
Run on a simulator or emulator
pnpm run run:android # or: npm run run:ios (macOS only)run:android builds the app, installs it and launches it. With nothing connected, it boots your most recently used Android emulator. It then keeps a dev server running, so edits to src/App.tsx hot-reload straight into the running app. The first Android build downloads Gradle and its dependencies and takes a few minutes; later builds are incremental.
Once the app is installed, npm run dev (sigx dev) is all you need day to day. It starts the dev server and a dashboard that lists every simulator, emulator and device it can see. Select one and press Enter to install the app if it's missing and launch it. You don't run run:ios or run:android alongside it. Use those for a one-off build on one platform, or for a --release build.
A physical iPhone or iPad also needs a signing team. Set your Apple Team ID for the command, for example SIGX_IOS_DEVELOPMENT_TEAM=AB12CD34EF npx sigx run:ios --device "My iPhone". The CLI usage guide covers devices and release builds.
Your first component
Open src/App.tsx and replace it with:
import { component } from '@sigx/lynx';
const App = component(() => {
return () => (
<view style={{ padding: '24px' }}>
<text style={{ fontSize: '24px', fontWeight: '600' }}>Hello, Lynx!</text>
</view>
);
});
export default App;
component, signal and defineApp come from @sigx/lynx, and component() works the same way it does on the web. Only the elements differ. Instead of HTML tags you use Lynx's lowercase native elements, such as <view>, <text>, <image> and <scroll-view>, and each one renders to a real native view. Inline style values are CSS strings with units, like '24px', as in the scaffold. A bare number is treated as px. See Native elements and Styling & layout.
src/main.tsx mounts App with defineApp(<App />).mount(null); you don't need to change it.
Adding reactivity
import { signal, component } from '@sigx/lynx';
const App = component(() => {
const count = signal(0);
return () => (
<view style={{ display: 'flex', flexDirection: 'column', padding: '24px', gap: '12px' }}>
<text style={{ fontSize: '32px' }}>{count.value}</text>
<view
bindtap={() => count.value++}
style={{ padding: '12px', backgroundColor: '#3b82f6', borderRadius: '12px' }}
>
<text style={{ color: '#fff' }}>Tap me</text>
</view>
</view>
);
});
export default App;
bindtap is the native tap event. When count.value changes, only the <text> node that reads it updates: the op streams to the main thread and patches one native view. For press states, long-press and pan gestures, use Pressable and friends from @sigx/lynx-gestures.
Next steps
- Learn the native elements and styling model
- Add screens with Navigation
- Browse the module catalog
- Hit a problem? See Troubleshooting
