PS5 React — JSX brackets and a framebuffer pixel inside a display

PS5 React

React interfaces. Controller navigation. Standalone PS5 homebrew.
Write JSX, preview natively on macOS, and build a fullscreen application for PS5.

Quick start · First app · Styling · APIs · Documentation

PS5 React is an open-source framework for standalone console homebrew. React runs inside **QuickJS** through **Embedded React**. The software renderer draws the interface into a framebuffer; **OpenGL presents that framebuffer** on screen. The application runs in its own native host, without a browser or Sony RNPS hooks. **Status: experimental v0.1.0.** The original proof of concept and a specific System Explorer filesystem integration were user-confirmed on firmware 13.60 with kstuff and ShadowMount. Those results apply to the exact artifacts recorded in [Hardware evidence](docs/HARDWARE.md). New apps, networking and other native features need their own console validation. ## What you can build | Capability | What the framework provides | | --- | --- | | React interfaces | JSX, hooks, state, reusable components and fullscreen application hosts | | Controller navigation | Spatial D-pad focus, Cross activation, Circle back navigation and focus scopes | | Styling | A build-time subset of Tailwind CSS v3, custom themes and explicit style objects | | Animation and media | Motion-style components, local assets, native remote images and short WAV feedback sounds | | Native services | Filesystem, HTTP, large-file downloads, controller state, notifications, users and power control | This is a focused console renderer, not the complete official React Native runtime. Use the supported primitives and native modules from `@ps5-react/core`. ## Quick start ### Prerequisites The supported development environment is **macOS on Apple Silicon**. | Dependency | Purpose | | --- | --- | | Python 3.12+ and Pillow | CLI, asset baking and packaging | | Node.js and npm | React dependencies and JavaScript bundling | | Git, CMake, SDL2 and a C++ compiler | Dependency bootstrap and native desktop preview | | LLVM 18 | PS5 cross-compilation | | libcurl 7.85+ development files | Native desktop HTTP and image transport | | libarchive 3.7+ development files | Streaming ZIP, TAR, 7z and RAR extraction | 1. Install native tools: ```sh brew install python node git cmake sdl2 llvm@18 curl libarchive openssl@3 ``` 2. Clone the project and install dependencies: ```sh git clone https://github.com/half144/ps5-react.git cd ps5-react python3 -m venv .venv source .venv/bin/activate python3 -m pip install -r requirements.txt npm ci ``` 3. Check the environment and open the starter: ```sh npm run doctor npm run dev ``` Edit `apps/starter/index.jsx`. Saving JSX, assets or app configuration rebuilds and restarts the native preview. **State resets on restart; this is not Fast Refresh.** The first run fetches and builds pinned dependencies when needed. ### Controls | Action | Desktop preview | PS5 | | --- | --- | --- | | Move focus | Arrow keys | D-pad | | Activate focused element | Enter | Cross | | Navigate back | Backspace | Circle | | Options (the `options` action) | O | Options | | Exit application | Escape | PS button | | Stop the development watcher | Ctrl+C in the terminal | — | The starter exits automatically 30 seconds after its first displayed frame. Set `timeoutSeconds` to `0` in its `app.json` to disable that timeout. Scaffolded apps already use `0`. ## Your first app Choose a title ID unused by your local apps and your console: ```sh npm run create -- --name my-app --title-id PPSA99100 --title "My App" npm run dev -- --app my-app ``` Replace `apps/my-app/index.jsx` with this complete example. The scaffold includes its font asset and the `font-inter` theme mapping. ```jsx import {useState} from 'react'; import {AppRegistry, FocusScope, Text, View} from '@ps5-react/core'; function App() { const [count, setCount] = useState(0); return ( My first PS5 app Count: {count} setCount(value => value + 1)} className="w-72 items-center rounded-xl border-2 border-transparent bg-slate-800 px-6 py-4 focused:border-cyan-400 focused:bg-slate-700"> Add one setCount(0)} className="w-72 items-center rounded-xl border-2 border-transparent bg-slate-800 px-6 py-4 focused:border-cyan-400 focused:bg-slate-700"> Reset ); } AppRegistry.registerComponent('my-app', () => App); ``` The scaffold pins the starter's baked font sizes. To let this example and future literal sizes be discovered automatically, replace `apps/my-app/assets.config.js` with: ```js export default { fonts: {'Inter-Regular': {extraGlyphs: '—–'}}, }; ``` `onPress` makes each `View` focusable. Navigation chooses the next element from its laid-out position; the app does not manage manual focus indices. `FocusScope` groups navigation and supports focus memory, wrapping and trapping. ### Apps in another repository ```sh npm run create -- --name my-store --title-id PPSA99101 --dir ../my-projects npm run dev -- --app-dir ../my-projects/my-store npm run build -- --app-dir ../my-projects/my-store ``` The bundler resolves React and `@ps5-react/core` from this checkout; an external app does not need its own `node_modules`. Its directory name must not collide with a folder in `apps/`. See [external-app editor setup](docs/EXTERNAL-APPS.md). PNG files in the app's `sce_sys/` go into the title's `sce_sys/`: `icon0.png` (512×512) is the home-screen icon, `pic0.png` (1920×1080) its background and `pic1.png` (1920×1080) the splash shown until the first frame. Without an `icon0.png` the build draws a placeholder icon. ### Application configuration `app.json` controls the title identity, display name, version, display size, refresh rate and automatic exit. A configuration for the example app is: ```json { "$schema": "../../app.schema.json", "titleId": "PPSA99100", "contentId": "UP9000-PPSA99100_00-MYAPP00000000000", "name": "My App", "version": "01.000.000", "render": {"width": 1920, "height": 1080}, "surface": {"width": 1920, "height": 1080}, "timeoutSeconds": 0 } ``` The render size determines software rasterization cost and styling scale; the surface size is the displayed framebuffer size. Keep them equal: different sizes go through a bilinear texture scale that softens text. 3840×2160 gives sharp text on 4K TVs; 1920×1080 costs a quarter of the rasterization and uploads changed rows through the faster buffer-texture path, which matters for apps that redraw large areas every frame. `"resolution": "auto"` replaces `render` and `surface`, and is what the starter uses. The build compiles the app twice, at 1920×1080 and 3840×2160, and the title picks the layout matching the console's video output when it starts: 4K and 1440p output get 3840×2160, everything else 1920×1080. Classes and baked font sizes are per layout, so the design looks the same at either size; raw numeric `style` values are render pixels and do not scale. Leave `sizes` out of `assets.config.js` so each layout bakes the font sizes its classes use. The eboot carries both bundles and font sets (about 0.4 MB more for the starter). Preview the small layout with `npm run dev -- --resolution 1080p`. `"refreshRate": 120` asks for 120 Hz output (default 60). The display must accept 120 Hz at the surface size, which for 3840×2160 means HDMI 2.1; otherwise the console stays at 60 Hz. Each frame then has 8.3 ms, so frames that fit 60 Hz can miss at 120, and switching the TV into the mode delays the first frame by about 1.5 s. Motion keeps its speed at either rate. `"refreshRates": [60, 120]` lets the app switch between the listed rates while it runs with `Display.setRefreshRate` (docs/NATIVE-API.md); it must include `refreshRate`, and listing 120 sets the same `param.json` bits. `"video": true` links FFmpeg and the PS5's hardware decoder for `