Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/pocket-stack/pocketjs/llms.txt

Use this file to discover all available pages before exploring further.

PocketJS renders every UI from a small set of typed components imported from @pocketjs/framework/components. There are exactly three host primitives — View, Text, and Image — plus a Sprite primitive for animated atlases. All higher-level app-shell components (Screen, Modal, Gallery, and friends) are built on top of these and live in the same package. Control flow helpers (Show, For, Index, Switch, Match) are imported from solid-js in Solid apps, or expressed directly in Vue Vapor’s JSX.
<view> (lowercase) is an internal renderer target and is not a JSX intrinsic. It is deliberately excluded from the type declarations so that a stray <view> in application code fails the TypeScript check. Always use the capitalized View.

View

View is the only container primitive. It lays out its children using flexbox (via Taffy), carries compiled Tailwind-subset styling via class, and can register with the focus manager via focusable.
import { View, Text, Image } from "@pocketjs/framework/components";

<View class="flex-row items-center gap-3 p-5 bg-slate-50">
  <Image class="w-10 h-10 rounded-lg" src="logo.png" />
  <View class="flex-col">
    <Text class="text-base text-slate-950 font-bold">PocketJS</Text>
    <Text class="text-xs text-slate-500">SOLID + RUST + SCEGU</Text>
  </View>
</View>
A View becomes interactive by adding focusable and an onPress handler. onPress fires when the node is focused and the user presses the Circle button (the PSP’s confirm button). Always pair onPress with focusable — an unfocusable node never receives input directly, though it can still act as a bubble target if a focused descendant has no handler.
import { View, Text } from "@pocketjs/framework/components";
import { createSignal } from "solid-js";

function PressButton() {
  const [count, setCount] = createSignal(0);
  return (
    <View
      class="px-4 py-2 rounded-xl bg-blue-600 focus:bg-blue-500 active:bg-blue-700 transition-colors duration-150"
      focusable
      onPress={() => setCount(count() + 1)}
    >
      <Text class="text-base text-white font-bold">Press Circle</Text>
    </View>
  );
}

Props

ViewProps, TextProps, and ImageProps are exported from @pocketjs/framework/components for typing your own wrapper components.
PropViewTextImageTypeNotes
classstringCompiled Tailwind-subset class literal.
styleRecord<string, number | string>Per-key dynamic style object.
childrenJSX.ElementImage takes no children.
focusablebooleanRegisters the node with the focus manager.
onPress() => voidFires when focused and Circle is pressed.
ref(node: NodeMirror) => void | NodeMirrorHandle to the underlying mirror node.
nodeRef(node: NodeMirror) => voidCallback ref for Vue Vapor (avoids JSX ref collision).
srcstringBaked texture name from the pak.

The style prop

class is compiled ahead of time. Use style for values only known at runtime — it sets individual style keys directly, diffed per key on each render:
const TRACK_FRAMES = 600;

<View
  class="h-2 rounded-full bg-gradient-to-r from-emerald-500 to-emerald-600"
  style={{ width: (position() / TRACK_FRAMES) * 160 }}
/>
Prefer transform keys (translateX, translateY, scale, rotate) for motion — they animate without triggering relayout.

The ref prop

ref gives you the underlying NodeMirror, which you pass to imperative APIs like animate(). Both Solid ref forms work — a plain variable (Solid assigns it) or a callback:
import { View, type NodeMirror } from "@pocketjs/framework/components";
import { animate } from "@pocketjs/framework/animation";
import { onMount } from "solid-js";

function Underline() {
  let underline: NodeMirror | undefined;
  onMount(() => {
    if (underline) animate(underline, "width", 210, { dur: 700, easing: "out" });
  });
  return <View ref={underline} class="h-1 w-0 rounded-full bg-blue-500" />;
}

Text

Text renders type. Its children are laid out as a single inline run — one measured line, not separate flex items — so you can freely mix static strings and reactive expressions:
import { Text } from "@pocketjs/framework/components";
import { createSignal } from "solid-js";

function Counter() {
  const [count, setCount] = createSignal(0);
  return <Text class="text-sm text-slate-600">Count: {count()}</Text>;
}
Count: and the reactive value are concatenated and measured together. When the signal or ref changes, only the text content updates via replaceText — no relayout unless the measured width actually changes. Text style — font slot, color, tracking, alignment — comes from the class on the <Text> element itself. Available text sizes map to baked font-atlas slots:
UtilityBaked px
text-xs12
text-sm14
text-base16
text-lg18
text-xl20
text-2xl24
text-4xl36

Empty text and layout

An empty text node — such as the placeholder Solid emits for a <Show> that is currently false — is excluded from layout entirely. It contributes no width, height, or gap. It re-enters layout the moment it becomes non-empty. This is why toggling a <Show> inside a gap-N row leaves no phantom gap where the hidden element was.

Image

Image draws a baked texture. Its src is an asset name, not a path or URL. At build time the pipeline scans src strings, packs referenced images into the app’s .pak, and the renderer resolves the name to an uploaded texture at runtime.
import { Image } from "@pocketjs/framework/components";

<Image class="w-10 h-10 rounded-lg shadow" src="logo.png" />
Set the drawn size with box utilities (w-10 h-10); class controls layout, src controls pixels. src is reactive — assigning a new name swaps the texture via setImage.

Sprite

Sprite is an auto-playing animated sprite atlas. Backed by an image node whose sprite atlas key the Rust core cycles every vblank — deterministic, zero per-frame JS. It plays from the first frame the moment it is displayed, so revealing or paging a Sprite starts its animation automatically.
import { Sprite } from "@pocketjs/framework/components";

<Sprite class="w-12 h-12" sprite="explosion" />
For signal-driven frame control (e.g. a loading spinner), use Image with createSpriteAnimation:
import { Image } from "@pocketjs/framework/components";
import { createSpriteAnimation } from "@pocketjs/framework/lifecycle";

const frame = createSpriteAnimation(
  ["spinner-00.svg", "spinner-01.svg", "spinner-02.svg"],
  { frameStep: 3 },
);

<Image class="w-10 h-10" src={frame()} />;

Control Flow

In Solid apps, render lists and conditionals with Solid’s control-flow components rather than array.map + &&. Import them directly from solid-js. Vue Vapor apps use Vue’s native JSX control-flow patterns instead.

Show

Toggles a subtree on a boolean condition, with an optional fallback:
import { Show } from "solid-js";
import { Text } from "@pocketjs/framework/components";

<Show
  when={count() > 3}
  fallback={<Text class="text-sm text-slate-500">Keep going…</Text>}
>
  <Text class="text-sm text-emerald-600">Reactive on real hardware.</Text>
</Show>

For

For renders a list keyed by reference. Its callback receives the item and an index accessor. When the array is reordered, For moves existing nodes to their new positions instead of destroying and recreating them. Focus, animation state, and imperative refs survive a move.
import { For } from "solid-js";
import { View, Text } from "@pocketjs/framework/components";

<For each={items()}>
  {(item, i) => (
    <View
      class="flex-row items-center gap-3 p-1 rounded-lg shadow bg-white border-slate-200 focus:bg-blue-50 focus:border-blue-500 transition-colors duration-150"
      focusable
      onPress={() => select(i())}
    >
      <Text class="text-xs text-slate-950 font-bold">{item.title}</Text>
      <Text class="text-xs text-slate-500">{item.time}</Text>
    </View>
  )}
</For>

Index

Index is keyed by position. The item is an accessor, the index is a plain number. Use it when the list length is stable and only the values at each slot change (equalizer bars, a fixed set of rows).
import { Index } from "solid-js";
import { View } from "@pocketjs/framework/components";

<Index each={bars()}>
  {(bar, i) => (
    <View class="w-2 rounded-md bg-emerald-500" style={{ height: bar() }} />
  )}
</Index>

Switch / Match

Pick one of several branches:
import { Switch, Match } from "solid-js";
import { Text } from "@pocketjs/framework/components";

<Switch fallback={<Text>Idle</Text>}>
  <Match when={state() === "loading"}><Text>Loading…</Text></Match>
  <Match when={state() === "ready"}><Text>Ready.</Text></Match>
</Switch>

App-Shell Primitives

@pocketjs/framework/components also exports a layer of higher-level components that compose View with focus, overlay, and layout behavior.

Screen

A full-screen flex container with sensible defaults. The default class is "relative flex-col w-full h-full bg-slate-50 overflow-hidden". Pass a custom class to override.
import { Screen, Text } from "@pocketjs/framework/components";

<Screen>
  <Text class="text-base text-slate-950 font-bold">Hello PocketJS</Text>
</Screen>

Focusable

A shorthand for View with focusable={true} preset. All ViewProps pass through.
import { Focusable, Text } from "@pocketjs/framework/components";

<Focusable
  class="px-4 py-2 rounded-lg bg-slate-200 focus:bg-sky-500"
  onPress={() => startGame()}
>
  <Text class="text-slate-900">Play</Text>
</Focusable>

FocusScope

FocusScope captures d-pad traversal to a subtree for the lifetime of the component. When active is truthy it calls pushFocusScope on the root node under the hood, auto-focusing the first focusable child and restoring the previous focus on dispose.
import { FocusScope, Text } from "@pocketjs/framework/components";

<FocusScope class="flex-col gap-2 p-3 rounded-xl bg-white">
  <Text class="text-sm text-slate-600">Navigation is trapped here</Text>
</FocusScope>
Options (active, autoFocus, restoreFocus) mirror pushFocusScope. See Input & Focus for details on focus scope semantics.

FocusGrid

FocusGrid overlays explicit row/column d-pad traversal on a subtree. LEFT/RIGHT move within a row; UP/DOWN move between rows. The columns prop sets how many items per row; wrap enables edge-wrapping.
import { FocusGrid, View } from "@pocketjs/framework/components";

<FocusGrid columns={4} wrap class="flex-row flex-wrap gap-2">
  {tiles.map((tile) => (
    <View class="w-16 h-16 rounded-lg bg-slate-200 focus:bg-blue-500" focusable />
  ))}
</FocusGrid>

ActionHandler

ActionHandler binds a button to a handler without rendering a visible node. It wraps onButtonPress and cleans up on unmount. Use it for global shortcuts or secondary buttons on a screen.
import { ActionHandler } from "@pocketjs/framework/components";
import { BTN } from "@pocketjs/framework/input";

<ActionHandler button={BTN.CROSS} onPress={() => goBack()} />
The active prop gates the handler on/off (accepts a boolean or reactive accessor). allowWhenBlocked keeps it firing even when a modal blocks background input.

Portal

Portal mounts its children into the overlay root — a full-screen, z-index: 1000, absolutely-positioned node that sits above all screen content. Used internally by Modal and ActionBar.
import { Portal, View, Text } from "@pocketjs/framework/components";

<Portal>
  <View class="absolute bottom-3 left-3 right-3 px-3 py-2 rounded-lg bg-slate-800 shadow-lg">
    <Text class="text-xs text-white">This is in the overlay layer</Text>
  </View>
</Portal>
Modal combines a Portal, a semi-transparent backdrop, and a FocusScope. While open is truthy the backdrop fades to 62% opacity, the panel appears, and pushButtonHandlerBlock silences all background button handlers so only the modal content receives input.
import { Modal, View, Text, Focusable } from "@pocketjs/framework/components";
import { createSignal } from "solid-js";

function ConfirmDialog() {
  const [open, setOpen] = createSignal(false);
  return (
    <>
      <Focusable
        class="px-4 py-2 rounded-lg bg-blue-600 focus:bg-blue-500"
        onPress={() => setOpen(true)}
      >
        <Text class="text-base text-white font-bold">Open Modal</Text>
      </Focusable>

      <Modal open={open()}>
        <Text class="text-sm text-slate-950 font-bold">Confirm?</Text>
        <View class="flex-row gap-2">
          <Focusable
            class="px-3 py-1 rounded-lg bg-blue-600 focus:bg-blue-500"
            onPress={() => { setOpen(false); }}
          >
            <Text class="text-sm text-white">Yes</Text>
          </Focusable>
          <Focusable
            class="px-3 py-1 rounded-lg bg-slate-200 focus:bg-slate-300"
            onPress={() => setOpen(false)}
          >
            <Text class="text-sm text-slate-900">No</Text>
          </Focusable>
        </View>
      </Modal>
    </>
  );
}

ActionBar

ActionBar is a portal’d bottom action strip. Its default class docks it to the bottom of the screen as a floating pill:
import { ActionBar, Text } from "@pocketjs/framework/components";

<ActionBar>
  <Text class="text-xs text-slate-500">CIRCLE Confirm · CROSS Back</Text>
</ActionBar>

Grid

Grid lays tiles out as a wrapping row (flex-row flex-wrap) and, when columns is set, adds FocusGrid d-pad traversal. Pass gap as a number to set it via the style object while keeping class a single compiled literal.
import { Grid, View } from "@pocketjs/framework/components";

<Grid columns={4} gap={8} wrap>
  {items.map((item) => (
    <View class="w-16 h-16 rounded-lg bg-slate-200 focus:bg-blue-500" focusable />
  ))}
</Grid>

Lazy

Lazy gates a subtree on demand. While when is false nothing is built and the native subtree is destroyed. When it becomes true the content mounts, optionally after a reveal frame delay that shows a fallback (spinner or skeleton). The reveal is a one-shot latch — a later re-activation shows content immediately.
import { Lazy, View, Text } from "@pocketjs/framework/components";

<Lazy
  when={isVisible()}
  reveal={6}
  fallback={<Text class="text-xs text-slate-500">Loading…</Text>}
>
  {() => (
    <View class="flex-col gap-2 p-3 rounded-xl bg-white shadow">
      <Text class="text-base text-slate-950 font-bold">Loaded Content</Text>
    </View>
  )}
</Lazy>
Gallery is a full-screen horizontally paged strip. Pressing LTRIGGER pages left; RTRIGGER pages right. Each page slides in with a native translateX tween on the inner strip node — paint-only, no relayout, one FFI call per page turn. Off-window pages are not built, keeping the PSP draw budget in check.
import { Gallery, View, Text } from "@pocketjs/framework/components";
import { createSignal } from "solid-js";

function PagedContent() {
  const [page, setPage] = createSignal(0);
  return (
    <Gallery
      count={4}
      page={page}
      onPageChange={setPage}
      duration={300}
      easing="out"
      renderPage={(i) => (
        <View class="w-full h-full flex-col items-center justify-center bg-slate-50">
          <Text class="text-4xl text-slate-950 font-bold">Page {i + 1}</Text>
        </View>
      )}
    />
  );
}
Gallery binds LTRIGGER/RTRIGGER internally by default (bindTriggers={true}). Set bindTriggers={false} and manage onPageChange yourself if you need custom shoulder-button behavior.

Summary Table

ComponentPurpose
ViewFlexbox container. All layout, styling, and input.
TextInline text run. Mixed static + reactive content.
ImageBaked texture from pak. Size from class, pixels from src.
SpriteAuto-playing sprite atlas. Zero per-frame JS.
ScreenFull-screen root container with sensible defaults.
FocusableA View that is focusable by default.
FocusScopeTraps and restores focus within a subtree.
FocusGrid2-D grid focus navigation (columns, wrap).
ActionHandlerBinds a button to a handler without rendering a node.
PortalRenders children into the overlay root.
ModalBackdrop + focus-trapped panel in the overlay.
ActionBarDocked bottom bar in the overlay layer.
GridFlex-wrap tile layout with optional FocusGrid.
LazyOn-demand mount with optional reveal delay.
GalleryFull-screen horizontally paged strip (LTRIGGER/RTRIGGER).

Build docs developers (and LLMs) love