Files
MrLowPr--overdrive/ps5-react
..
2026-10-14 04:20:24 +02:00
2026-10-14 04:20:24 +02:00
2026-10-14 04:20:24 +02:00
2026-10-14 04:20:24 +02:00
2026-10-14 04:20:24 +02:00
2026-10-14 04:20:24 +02:00
2026-10-14 04:20:24 +02:00
2026-10-14 04:20:24 +02:00
2026-10-14 04:20:24 +02:00
2026-10-14 04:20:24 +02:00
2026-10-14 04:20:24 +02:00
2026-10-14 04:20:24 +02:00
2026-10-14 04:20:24 +02:00
2026-10-14 04:20:24 +02:00
2026-10-14 04:20:24 +02:00
2026-10-14 04:20:24 +02:00
2026-10-14 04:20:24 +02:00
2026-10-14 04:20:24 +02:00

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. 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:

    brew install python node git cmake sdl2 llvm@18 curl libarchive openssl@3
    
  2. Clone the project and install dependencies:

    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:

    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:

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.

import {useState} from 'react';
import {AppRegistry, FocusScope, Text, View} from '@ps5-react/core';

function App() {
  const [count, setCount] = useState(0);

  return (
    <FocusScope autoFocus wrap>
      <View className="w-screen h-screen items-center justify-center gap-6 bg-slate-950">
        <Text className="font-inter text-4xl font-bold text-white">
          My first PS5 app
        </Text>
        <Text className="font-inter text-2xl text-slate-300">Count: {count}</Text>
        <View onPress={() => 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">
          <Text className="font-inter text-xl text-white">Add one</Text>
        </View>
        <View onPress={() => 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">
          <Text className="font-inter text-xl text-white">Reset</Text>
        </View>
      </View>
    </FocusScope>
  );
}

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:

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

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.

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:

{
  "$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 <Video>: H.264, HEVC and VP9 up to 4K, more formats up to 1080p (docs/VIDEO.md).

Configuration errors are reported before expensive builds. Add networking and filesystem opt-ins only when your app needs those capabilities.

Tailwind-style styling

className and tw compile a supported subset of Tailwind CSS v3 utilities into literal style objects at build time. No stylesheet, CSS parser or class lookup runs on the console.

Layout, focus and state

import {Text, View} from '@ps5-react/core';

function GameCard({title, selected, onOpen}) {
  return (
    <View selected={selected} onPress={onOpen}
      className="w-64 gap-3 rounded-xl border-2 border-transparent bg-slate-800 p-5
        selected:bg-sky-900 focused:border-cyan-400">
      <Text className="font-inter text-xl font-semibold text-white">{title}</Text>
      <Text className="font-inter text-sm text-slate-300">Cross to open</Text>
    </View>
  );
}

focused: and pressed: read a focusable element's interaction state. Other variants read props on the same element, including selected:, disabled:, active: and checked:. Text styles do not cascade from a parent View.

Reusable styles

import {Text, tw} from '@ps5-react/core';

const heading = tw`font-inter text-3xl font-semibold text-white`;

function Heading({children}) {
  return <Text style={heading}>{children}</Text>;
}

Custom themes

Extend apps/my-app/tailwind.config.js:

export default {
  baseWidth: 1280,
  theme: {
    extend: {
      colors: {brand: '#45d4de', canvas: '#101820'},
      spacing: {18: 72},
      fontSize: {hero: '48px'},
      fontFamily: {inter: './assets/Inter-Regular.ttf'},
    },
  },
};
<View className="bg-canvas p-18">
  <Text className="font-inter text-hero text-brand">Your library</Text>
</View>

Lengths use logical pixels at a default base width of 1280. The compiler scales lengths to the app's render width: p-4 becomes 48 physical pixels at 3840. Later classes win; explicit style wins over classes.

Supported approach Avoid
Flexbox, gaps, spacing, borders, colors and supported gradients CSS Grid, child selectors, shadows and browser filters
Documented state variants such as focused: and selected: Responsive breakpoints, dark: and CSS pseudo-classes
Complete alternatives: selected ? 'bg-sky-900' : 'bg-slate-800' Class construction: `bg-${color}-500`
tw for shared compiled style objects Runtime class compilation and CSS stylesheets

Unsupported utilities fail the build with a source location and explanation. See the complete supported-utility reference.

Navigation and long lists

Group a page or dialog with a focus scope and handle Circle through onBack:

import {FocusScope, Text, View} from '@ps5-react/core';

function Settings({close}) {
  return (
    <FocusScope autoFocus trap onBack={() => { close(); return true; }}>
      <View className="gap-4 rounded-xl bg-slate-900 p-8">
        <Text className="font-inter text-2xl text-white">Settings</Text>
        <View onPress={close} className="rounded-lg bg-slate-700 p-4 focused:bg-sky-800">
          <Text className="font-inter text-xl text-white">Done</Text>
        </View>
      </View>
    </FocusScope>
  );
}

Use VirtualList inside a ScrollView for large catalogs. It mounts rows near focus and the viewport rather than creating every card at once.

import {useCallback} from 'react';
import {ScrollView, Text, View, VirtualList} from '@ps5-react/core';

function Library({games, openGame}) {
  const renderItem = useCallback(({item}) => (
    <View onPress={() => openGame(item)}
      className="h-[200px] w-[150px] rounded-lg bg-slate-800 p-4 focused:bg-sky-800">
      <Text className="font-inter text-lg text-white">{item.title}</Text>
    </View>
  ), [openGame]);

  return (
    <ScrollView className="flex-1 px-8">
      <VirtualList data={games} keyExtractor={game => game.id}
        renderItem={renderItem} numColumns={5}
        itemHeight={200} rowGap={24} columnGap={24} />
    </ScrollView>
  );
}

Rows have a fixed height in logical pixels. Keep keys consistent and renderItem stable. See Navigation for explicit focus links, back handling and events, and Long lists for recycling, pagination and renderer limits.

Animation

The Motion-style API drives animation in the C engine, without per-frame React state updates:

import {motion, Text, transitions} from '@ps5-react/core';

function AnimatedCard({open}) {
  return (
    <motion.View onPress={open}
      initial={{y: 16}} animate={{y: 0}}
      whileFocus={{y: -4, scale: 1.04}} transition={transitions.focus}
      className="w-48 rounded-xl bg-slate-800 p-6">
      <Text className="font-inter text-xl text-white">Open library</Text>
    </motion.View>
  );
}

Simple animation classes compile to the same motion props:

import {View} from '@ps5-react/core';

<View onPress={open}
  className="w-48 h-28 rounded-xl bg-slate-800 transition-transform duration-150
    focused:scale-105 animate-in slide-in-from-bottom-4" />

AnimatePresence supports exit transitions for keyed children. Prefer small moving elements; fullscreen fades are expensive on the CPU renderer. Scale and rotation have scratch-buffer size limits. See Animation.

Native APIs

Import public modules from @ps5-react/core; apps do not access the host's internal global object. Filesystem/controller calls are synchronous; HTTP and downloads submit work to native workers.

Module Typical usage
FileSystem Read/write app data, create directories and inspect storage
Http, Downloads, DownloadFormats Fetch JSON/text, stream files, normalize split manifests and route artifacts
Platform, DeviceInfo, Users Choose platform behavior and inspect device/user information
Controller, useGamepad, useController Feedback, analog state and raw input subscriptions
Notifications, Sound System notifications and short baked feedback sounds
Power, BackHandler, Linking Request awake behavior, exit the app and open an HTTP(S) URL

Availability and hardware evidence vary by API. The native API reference contains signatures, limits, error behavior and desktop equivalents.

Persistent app data

import {FileSystem} from '@ps5-react/core';

const directory = `${FileSystem.dataDir}/preferences`;
const path = `${directory}/settings.json`;

export function saveSettings(settings) {
  FileSystem.mkdir(directory, {recursive: true});
  FileSystem.writeFile(path, JSON.stringify(settings));
}

export function loadSettings() {
  return FileSystem.exists(path)
    ? JSON.parse(FileSystem.readFile(path))
    : {volume: 0.5};
}

Filesystem failures and malformed JSON throw; handle them where your app can show a useful error or choose a fallback.

Logical path Purpose
FileSystem.appDir / /app0 Application files; treat as read-only on PS5
FileSystem.dataDir / /download0 Persistent writable app data
FileSystem.tempDir / /temp0 Temporary app files

Desktop paths map into .build/<app>/sandbox/. Console paths such as /data and external drives require the console-access opt-in and successful startup elevation. A running jailbreak alone does not grant every title payload access.

HTTP requests

Use Http.request for bounded JSON or text responses:

import {Http} from '@ps5-react/core';

export async function loadCatalog(url) {
  const response = await Http.request(url, {
    method: 'GET',
    maxBytes: 1024 * 1024,
  });
  if (!response.ok) throw new Error(`Catalog request failed: HTTP ${response.status}`);
  return response.json();
}

// Async handler: const catalog = await loadCatalog('https://example.com/catalog.json');

Replace the example URL with your endpoint. Non-2xx responses resolve normally; transport and size-limit errors reject. There is no global browser fetch or AbortController. This API returns text/JSON, not arbitrary binary buffers.

Large-file downloads

File bytes stay in native memory and stream directly to disk. JavaScript receives progress and a completion promise:

import {Downloads, FileSystem, Power} from '@ps5-react/core';

export async function downloadArchive(url, reportProgress) {
  const directory = `${FileSystem.dataDir}/archives`;
  FileSystem.mkdir(directory, {recursive: true});

  const task = Downloads.enqueue({
    url,
    destination: `${directory}/archive.zip`,
    connections: 8,
    adaptive: true,
    resume: true,
    // sha256: 'trusted 64-character hexadecimal digest',
  });
  const unsubscribe = task.subscribe(reportProgress);
  Power.keepAwake(true);
  try {
    await task.done;
    return task.snapshot;
  } finally {
    unsubscribe();
    Power.keepAwake(false);
  }
}

// Listener: progress => console.log(progress.state, progress.written, progress.total)

The parent directory must exist; existing final files are preserved. Partial files use .part and eligible checkpoints. Resume depends on provider support and file identity. One task runs at a time with multiple connections within it. task.cancel() requests cancellation; task.done rejects with AbortError on cancellation.

Own tasks at the right lifetime: component cleanup cancels a component-owned download; an app-wide queue can survive screen navigation. Downloads stop when the app exits. The Power example assumes one active job; coordinate awake ownership if several features use it.

For PS5 HTTP, downloads and remote images, add these fields to app.json:

{
  "networking": true,
  "filesystemAccess": "console"
}

The build includes an exact-title filesystem helper. Startup needs resident Lapy or an ELF loader on localhost port 9021, successful filesystem elevation and the console trust store. HTTPS certificate verification remains enabled. Desktop networking works without the PS5 opt-in.

DownloadFormats.artifact routes package/image/archive destinations; Downloads.enqueueManifest handles supported contiguous split-file manifests. Downloading a file does not install, register or launch it. See Networking for options and complete examples, and Download performance for resource budgets and hardware benchmark planning.

Device information and controller feedback

import {useEffect} from 'react';
import {Controller, DeviceInfo, Platform, Text, useGamepad} from '@ps5-react/core';

function ControllerStatus() {
  const pad = useGamepad();
  useEffect(() => {
    Controller.setLightBar('#45d4de');
    return () => Controller.resetLightBar();
  }, []);

  return (
    <Text className="font-inter text-lg text-white">
      {Platform.OS}: {pad.connected ? 'Controller connected' : 'No controller'}
    </Text>
  );
}

// Inspect on demand: const device = DeviceInfo.get();
// Trigger feedback from an action: Controller.vibrate(0.4, 100);

Use raw input for analog controls and specialized interactions. Menus should use the focus navigation system.

Images, fonts and sound

Local imports are baked into the app at build time:

import {Image, Sound} from '@ps5-react/core';
import cover from './assets/cover.png';
import confirm from './assets/confirm.wav';

function Cover({open}) {
  return (
    <Image source={cover} resizeMode="cover"
      className="w-48 h-64 rounded-xl"
      onPress={() => { Sound.play(confirm); open(); }} />
  );
}

Create those files before using the snippet. WAV imports accept short 16-bit PCM mono/stereo sounds, up to 10 seconds. Fonts use imported TTF/OTF files or theme mappings. Literal font sizes are discovered during bundling when the font's sizes array is not pinned in assets.config.js. An explicit nonempty array replaces automatic discovery; include every physical size you use. Declare runtime-selected sizes and extra glyphs there as well, and resolve font-baker warnings before shipping. For text in other languages, see International text.

Remote images load through native workers and a bounded cache:

<Image source={{uri: coverUrl}} resizeMode="cover"
  className="w-48 h-64 rounded-xl" />

The PS5 networking opt-in applies to remote images too. See Remote images, Sound and font styling.

Build for PS5

npm run build -- --app my-app

The build bundles JS, bakes assets, compiles the host and verifies the package. Public dependencies are pinned and hash-verified. Use an existing public SDK with npm run build -- --app my-app --sdk /path/to/ps5-payload-sdk.

Output for this example Contents
dist/PPSA99100/ Complete application folder
dist/PPSA99100.zip ZIP containing that folder
dist/PPSA99100.receipt.json Configuration, dependency/toolchain provenance and file hashes

Install the complete folder under /data/homebrew/PPSA99100/ using PS5Upload or your loader's supported workflow. Keep sce_sys/, sce_module/, notices and any helper alongside eboot.bin. Follow your title-registering loader's registration procedure before launching.

The supported packaging workflow is an extracted folder, not a PKG. Build and preview tools do not connect to, upload to or launch on a console. Host/SDK changes and new artifacts require their own hardware validation.

Project structure

apps/<app>/                 JSX, app.json, themes, assets and font configuration
runtime/js/                 Public @ps5-react/core APIs, focus and motion
native/shared/              Native bridge, workers and framebuffer presentation
native/desktop/             macOS graphics, lifecycle and input
native/ps5/                 PS5 graphics, lifecycle, input and filesystem access
tools/                     Bootstrap, bundling, styles, baking and builds
docs/                      API references, architecture and hardware evidence

The rendering pipeline is:

React / JSX
    ↓ QuickJS + Embedded React
Software renderer
    ↓ ARGB framebuffer
OpenGL presenter
    ↓
Native macOS preview / fullscreen PS5 application

OpenGL presents the software-rendered result; it does not directly render React widgets. Ordinary application changes belong in JSX and configuration, without copied hosts or edits to C++.

Development commands

Command Purpose
npm ci Install locked JavaScript dependencies
npm run doctor Check the native development environment
npm run dev -- --app my-app Watch sources and rebuild/restart the preview
npm run preview -- --app my-app Run one native preview
npm run preview -- --app my-app --resolution 1080p Preview the 1920×1080 layout of a "resolution": "auto" app (default 2160p)
npm test Run local runtime/compiler/transport tests and starter UI checks
npm run build -- --app my-app Build and verify a PS5 application folder
npm run create -- --name my-app --title-id PPSA99100 Scaffold an app

The scripted UI self-test belongs to the starter. Local tests and ELF/SELF checks do not establish PS5 execution, measured throughput or firmware support. Logs/artifacts live in .build/; dependencies are cached in .deps/. Both are excluded from version control.

Troubleshooting

Symptom Next check
Preview prerequisites are missing Run npm run doctor; activate the Python virtual environment
CMake reports an old libcurl Install current development files; inspect CMake's selected headers/libraries
A utility class fails compilation Check its source location against supported utilities
An element cannot receive D-pad focus Add onPress or focusable; check its scope and layout
PS5 filesystem calls report permission errors Check console-access opt-in, the complete helper package and startup access log; see Storage
A download cannot resume Check provider range support, validators/hash and partial/checkpoint ownership; see Networking

Documentation

Guide Read it for
Architecture Rendering, threading, platform boundaries and reproducibility
External apps Separate repositories and editor configuration
Navigation · Long lists Focus, back handling, scrolling and large catalogs
Tailwind · Animation Utilities, themes, transitions and renderer limits
Native APIs · Remote images Platform services, signatures and media
Networking · Download performance HTTP, downloads, resume, formats and resource budgets
Storage · Hardware evidence Filesystem access and exact console validation results
Dependencies · Contributing Setup, provenance, licenses and contribution workflow

Credits and license

Created by half144 and PS5 React contributors. Built on Embedded React, ps5-homebrew-ui, ps5-opengl, PS5 Payload SDK, QuickJS-ng and React. The utility palette and scales come from Tailwind CSS v3.4.17 under the MIT License.

Framework-owned material uses GPL-3.0-or-later with the reasonable attribution-preservation term in LICENSE-ATTRIBUTION. Preserve NOTICE in redistributions. Third-party material retains its own licenses and notices; no proprietary Sony modules are redistributed. The public release is source-only. Binary redistribution must follow the corresponding-source and notice requirements in Dependencies.

The project is independent of Sony Interactive Entertainment. See brand assets for the original project logo.