> ## Documentation Index
> Fetch the complete documentation index at: https://docs.yield.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Perps dashboard

> A full desktop perpetual futures trading terminal on the Perps API: run it, deploy it and embed it in your product.

export const ExpandableDemo = ({src, title, desktopWidth = 1440, desktopHeight = 900, caption}) => {
  const [open, setOpen] = useState(false);
  const [scale, setScale] = useState(0.5);
  const boxRef = useRef(null);
  useEffect(() => {
    const box = boxRef.current;
    if (!box) return undefined;
    const update = () => setScale(Math.min(1, box.clientWidth / desktopWidth));
    update();
    const observer = new ResizeObserver(update);
    observer.observe(box);
    return () => observer.disconnect();
  }, [desktopWidth]);
  const dialogRef = useRef(null);
  const closeRef = useRef(null);
  useEffect(() => {
    const dialog = dialogRef.current;
    if (!dialog) return undefined;
    if (open && !dialog.open) {
      dialog.showModal();
      closeRef.current?.focus();
    }
    if (!open && dialog.open) dialog.close();
    const previousOverflow = document.body.style.overflow;
    if (open) document.body.style.overflow = "hidden";
    return () => {
      document.body.style.overflow = previousOverflow;
    };
  }, [open]);
  return <figure style={{
    margin: "24px 0"
  }}>
      <div ref={boxRef} style={{
    position: "relative",
    width: "100%",
    height: `${Math.round(desktopHeight * scale)}px`,
    overflow: "hidden",
    borderRadius: "16px",
    border: "1px solid rgba(127, 127, 127, 0.25)"
  }}>
        <iframe src={src} title={`${title} (preview)`} width={desktopWidth} height={desktopHeight} loading="lazy" tabIndex={-1} aria-hidden="true" style={{
    border: 0,
    width: `${desktopWidth}px`,
    height: `${desktopHeight}px`,
    transform: `scale(${scale})`,
    transformOrigin: "0 0",
    pointerEvents: "none"
  }} />
        <button type="button" onClick={() => setOpen(true)} aria-label={`Open the ${title} live demo`} style={{
    position: "absolute",
    inset: 0,
    width: "100%",
    height: "100%",
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    background: "rgba(0, 0, 0, 0.12)",
    border: 0,
    cursor: "pointer"
  }}>
          <span style={{
    padding: "10px 18px",
    borderRadius: "999px",
    background: "#111827",
    color: "#ffffff",
    fontSize: "15px",
    fontWeight: 600,
    boxShadow: "0 4px 16px rgba(0, 0, 0, 0.25)"
  }}>
            Open live demo
          </span>
        </button>
      </div>
      <figcaption style={{
    marginTop: "8px",
    fontSize: "14px",
    opacity: 0.75,
    textAlign: "center"
  }}>
        {caption ? `${caption} ` : ""}
        <a href={src} target="_blank" rel="noopener noreferrer">
          Open in a new tab
        </a>
      </figcaption>

      <dialog ref={dialogRef} aria-label={title} onClose={() => setOpen(false)} onClick={event => {
    if (event.target === event.currentTarget) setOpen(false);
  }} style={{
    width: "100vw",
    height: "100vh",
    maxWidth: "100vw",
    maxHeight: "100vh",
    margin: 0,
    padding: "24px",
    border: 0,
    background: "rgba(0, 0, 0, 0.75)",
    display: open ? "flex" : "none",
    alignItems: "center",
    justifyContent: "center",
    boxSizing: "border-box"
  }}>
        {open && <div style={{
    position: "relative",
    width: "min(94vw, 1600px)",
    height: "88vh",
    borderRadius: "16px",
    overflow: "hidden",
    background: "#ffffff",
    boxShadow: "0 24px 64px rgba(0, 0, 0, 0.4)"
  }}>
            <iframe src={src} title={title} allow="clipboard-read; clipboard-write" style={{
    border: 0,
    width: "100%",
    height: "100%"
  }} />
            <button ref={closeRef} type="button" onClick={() => setOpen(false)} aria-label="Close the demo" style={{
    position: "fixed",
    top: "8px",
    right: "8px",
    width: "36px",
    height: "36px",
    borderRadius: "999px",
    border: 0,
    background: "#111827",
    color: "#ffffff",
    fontSize: "18px",
    lineHeight: "36px",
    cursor: "pointer"
  }}>
              ✕
            </button>
          </div>}
      </dialog>
    </figure>;
};

The Perps dashboard is a full desktop trading terminal you host on your own domain. Its layout follows the Hyperliquid trading screen: a market bar, a price chart, an order form, and positions and open orders below. It is built on the [Perps API](/guides/perps/index), and users sign every action in their own wallet.

<ExpandableDemo src="https://perps-dashboard.vercel.app/" title="Perps dashboard" caption="The Perps dashboard." />

For a compact module that fits inside an existing screen, see the [Perps widget](/guides/widgets-dashboard/perps-widget). Both come from the same source repository.

<Warning>
  Perps trading carries jurisdictional restrictions, and the Perps API blocks requests from banned regions. Read the [perpetuals disclaimer](/legal/perpetuals-disclaimer) and [Geo-blocking](/documentation/advanced-setup/geo-blocking/index) before you deploy.
</Warning>

## What users can do

* Search for and switch between markets, with last price, volume, open interest and funding in the market bar.
* Read a live price chart for the selected market.
* Go long or short with market or limit orders, set leverage and choose cross or isolated margin.
* Track positions (entry price, mark price, margin, leverage) and open orders in tabs, and close a position or cancel an order from there.
* Deposit to and withdraw from their Hyperliquid account from the header.

## Prerequisites

The prerequisites match the Perps widget: a Yield.xyz API key with the Perps API enabled, a [Moralis](https://moralis.io) API key, an optional [Reown](https://cloud.reown.com) project ID, and Node.js 24 with pnpm 10.

## Run it locally

<Steps>
  <Step title="Clone and install">
    ```bash theme={null}
    git clone https://github.com/stakekit/perps-widget
    cd perps-widget
    pnpm install
    ```
  </Step>

  <Step title="Set environment variables">
    The dashboard reads its own `.env`:

    ```bash theme={null}
    cp packages/dashboard/.env.example packages/dashboard/.env
    ```

    ```bash packages/dashboard/.env theme={null}
    VITE_PERPS_BASE_URL=https://perps.yield.xyz
    VITE_PERPS_API_KEY=your-yield-api-key
    VITE_MORALIS_API_KEY=your-moralis-api-key
    VITE_REOWN_PROJECT_ID=your-reown-project-id
    ```

    `VITE_PERPS_BASE_URL`, `VITE_PERPS_API_KEY` and `VITE_MORALIS_API_KEY` are required. `VITE_REOWN_PROJECT_ID` is optional.
  </Step>

  <Step title="Start the dev server">
    ```bash theme={null}
    pnpm dev:dashboard
    ```

    Open [http://localhost:3001](http://localhost:3001).
  </Step>
</Steps>

<Warning>
  Every `VITE_` variable is bundled into the browser build, so treat these keys as public. Restrict them where the provider allows it, and see the [security notice](/legal/security-notice).
</Warning>

## Build and deploy

```bash theme={null}
pnpm build:dashboard
```

The build output is written to `packages/dashboard/dist`. Deploy it to any static host that serves single-page apps, with every path routed to `index.html` as in the repository's `vercel.json`. Set the environment variables in your host's build settings.

## Embed it

The dashboard is designed for full-page use on desktop. Host it on its own route or subdomain, or embed it at full width:

```html theme={null}
<iframe
  src="https://trade.your-app.example"
  width="100%"
  height="900"
  style="border: 0"
  allow="clipboard-read; clipboard-write"
></iframe>
```

## How it works

The dashboard makes the same Perps API calls as the widget: providers, markets and candles for discovery; balances, positions and orders for the account; `POST /v1/actions` to build each trade; and `POST /v1/transactions/{transactionId}/submit` to submit the signature. See [How it works](/guides/widgets-dashboard/perps-widget#how-it-works) on the Perps widget page, and the [Perps API guide](/guides/perps/index) for the full flow.

## Customize it

The dashboard lives in `packages/dashboard`, with its screens in `src/components/modules/trade` and the header, positions and orders in `src/components/molecules`. API clients, state and shared components are in `packages/common`, shared with the widget. Styling uses Tailwind CSS v4.

## Troubleshooting

The [Perps widget troubleshooting](/guides/widgets-dashboard/perps-widget#troubleshooting) entries also apply to the dashboard: missing environment variables, wallet connection, the pnpm version and single-page app routing.

## License

The Perps dashboard is published under the Business Source License 1.1 (BSL-1.1), which converts to MIT on 2030-01-20. Read the `LICENSE` file in the repository before you deploy it.

## Next steps

<Columns cols={2}>
  <Card title="Perps widget" icon="mobile-screen" href="/guides/widgets-dashboard/perps-widget">
    The compact module from the same repository.
  </Card>

  <Card title="Perps API guide" icon="code" href="/guides/perps/index">
    Build your own Perps interface on the same API.
  </Card>
</Columns>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.