---
title: Getting Started
description: Install FileDOM and run your first game.
---

Requires macOS, Node.js 20+, and React 18.

:::info[What a session does to your Desktop]
While a game runs, your existing Desktop items are hidden in place with the macOS
hidden flag. They are not moved, renamed, or deleted, and they reappear when the
session ends. The Dock and menu bar autohide during the session and go back to
your own settings afterwards. FileDOM creates and deletes only its own sprite
files.
:::

## Install

```bash
pnpm add filedom react@18.3.1 react-reconciler@0.29.2
pnpm add -D tsx
```

## Check permissions

```bash
pnpm exec filedom doctor
```

Grant the Automation and Accessibility permissions it asks for under
System Settings → Privacy & Security.

## Render your first game

Save this as `pong.tsx`. Each paddle and the ball is a real file on your Desktop.

```tsx title="pong.tsx"
import path from 'node:path';
import { useState } from 'react';
import {
  defineAssets, solid, startSession, Stage, StageProvider, Sprite, Wallpaper,
  useDesktopGameShell, useFixedTick, useGameControls,
} from 'filedom';

const FIELD = '#1c1c1e';

// Icons are baked PNGs: build them once, before anything renders.
const assets = defineAssets({
  iconDir: path.resolve('pong-icons'),
  icons: {
    ball: (c) => c.circle({ r: c.canvas * 0.3, fill: '#ffffff' }),
    paddle: solid('#0a84ff'),
  },
});
assets.build();

function Pong() {
  const { metrics } = useDesktopGameShell({ deriveGeometry: (m) => m, showDesktop: true });
  const [left, setLeft] = useState(400);
  const [right, setRight] = useState(400);
  const [ball, setBall] = useState({ x: 600, y: 400, vx: 24, vy: 16 });

  useGameControls({
    shortcuts: [
      { keys: ['w', 's'], label: 'LEFT', onPress: (k) => setLeft((y) => y + (k === 'w' ? -48 : 48)) },
      { keys: ['i', 'k'], label: 'RIGHT', onPress: (k) => setRight((y) => y + (k === 'i' ? -48 : 48)) },
    ],
    onQuit: () => void session.then((s) => s.exit()),
  });

  useFixedTick(() => {
    if (!metrics) return;
    setBall(({ x, y, vx, vy }) => {
      x += vx;
      y += vy;
      if (y < 0 || y > metrics.height) vy = -vy;
      const paddleY = vx < 0 ? left : right;
      const atEdge = vx < 0 ? x < metrics.iconSize : x > metrics.width - metrics.iconSize;
      if (atEdge && Math.abs(y - paddleY) < metrics.iconSize) vx = -vx;
      // Missed: serve again from the centre.
      if (x < 0 || x > metrics.width) return { x: metrics.width / 2, y: metrics.height / 2, vx: -vx, vy };
      return { x, y, vx, vy };
    });
  }, 50, Boolean(metrics));

  if (!metrics) return null;
  return (
    <StageProvider metrics={metrics} park={{ x: metrics.width / 2, y: metrics.iconSize }} background={FIELD}>
      <Wallpaper source={{ kind: 'color', value: FIELD }} />
      <Stage prebake={[assets.icon('paddle')]}>
        <Sprite icon={assets.icon('paddle')} at={{ x: metrics.iconSize / 2, y: left }} />
        <Sprite icon={assets.icon('paddle')} at={{ x: metrics.width - metrics.iconSize / 2, y: right }} />
      </Stage>
      {/* Mounted last so the ball draws above every other file. */}
      <Stage prebake={[assets.icon('ball')]}>
        <Sprite icon={assets.icon('ball')} at={ball} />
      </Stage>
    </StageProvider>
  );
}

const session = startSession(<Pong />);
```

```bash
pnpm exec tsx pong.tsx
```

`W`/`S` and `I`/`K` move the paddles. `Q` quits and shows your Desktop items again.

The full game, with scores and sound, is on the [Pong](/docs/examples/pong) page.
If nothing appears, see [Troubleshooting](/docs/guides/troubleshooting).
