---
title: Screens and devices
description: A screen is one entry with an id and a layout tree; devices are viewport presets that set insets, chrome and $env comparisons.
status: shipped
url: https://getairframe.com/framewerk/core/screens-and-devices
---

> Part of the Framewerk docs. Index of every page: /framewerk/llms.txt

# Screens and devices

A **screen** is one entry in `screens` with a unique `id` (the target of `navigate(id)`) and a `layout` root node; a **device** is a viewport preset that sets dimensions, safe-area insets and platform chrome without locking the spec to one phone.

## Examples

A named screen with a style and device:

**A screen with a device**

```json
{
  "formatVersion": "1.0",
  "project": { "defaultStyle": "sample-light" },
  "screens": [
    {
      "id": "menu",
      "title": "Menu",
      "route": "/menu",
      "style": "sample-light",
      "device": "iphone15",
      "stateVars": {
        "oatMilk": { "type": "boolean", "default": "false", "label": "Oat milk" }
      },
      "layout": {
        "_id": "menu-root",
        "type": "column",
        "gap": 12,
        "padding": 16,
        "children": [
          { "_id": "menu-h", "type": "text", "variant": "heading", "content": "Menu" },
          { "_id": "menu-body", "type": "text", "content": "Espresso · Cortado · Filter" }
        ]
      }
    }
  ]
}
```

Two screens, one navigating to the other, with a desktop preset on the second:

**Two screens, two devices**

```json
{
  "formatVersion": "1.0",
  "screens": [
    {
      "id": "home",
      "device": "iphone15",
      "layout": {
        "_id": "home-root",
        "type": "column",
        "gap": 16,
        "padding": 24,
        "children": [
          { "_id": "home-h", "type": "text", "variant": "heading", "content": "Home" },
          { "_id": "home-go", "type": "button", "variant": "primary", "content": "Open dashboard", "action": "navigate(dash)" }
        ]
      }
    },
    {
      "id": "dash",
      "device": "web-lg",
      "layout": {
        "_id": "dash-root",
        "type": "row",
        "gap": 16,
        "padding": 24,
        "children": [
          { "_id": "dash-side", "type": "column", "width": 200, "children": [{ "_id": "dash-nav", "type": "text", "content": "Nav" }] },
          { "_id": "dash-main", "type": "column", "flex": 1, "children": [{ "_id": "dash-h", "type": "text", "variant": "heading", "content": "Dashboard" }] }
        ]
      }
    }
  ],
  "flows": [{ "from": "home", "to": "dash", "trigger": "Open dashboard" }]
}
```

## Anatomy

| Field | Type | Notes |
| --- | --- | --- |
| `id` | string | **Required.** Unique; the target of `navigate(id)`. |
| `layout` | `LayoutNode` | The root node. A screen without one renders nothing. |
| `title` | string | Human label. |
| `summary` | string | What the screen is for: who is on it, the one job they came to do, and what is deliberately not here. Aim for 100 words or fewer. |
| `route` | string | URL path, for code generation. |
| `style` | string | Overrides `project.defaultStyle`. |
| `device` | string | The preset this screen is designed at; overrides `project.defaultDevice`. |
| `stateVars` | `Record<string, StateVarDef>` | See [State & expressions](/framewerk/core/state-and-expressions.md). |
| `status` | `"draft" \| "in-review" \| "approved" \| "deprecated"` | Workflow metadata; does not affect rendering. |
| `priority` | `"P0" \| "P1" \| "P2"` | Workflow metadata. |

### Device presets

The renderer ships presets that set viewport dimensions, safe-area insets, and platform chrome:

`iphoneSE` · `iphone15` · `ipadMini` · `ipadPro` · `mweb` · `web-sm` · `web-md` · `web-lg` · `web-xl` · `desktop` · `auto`

Specs are not locked to them — a preset is just a viewport plus insets, and `auto` fills whatever space the host gives it. Presets matter for two things: `safeArea` padding resolves against the preset's insets, and `$env.device` expressions compare against its class (`mobile`, `tablet`, `desktop`). `effectiveDevicePreset` is the one resolver for screen → project → fallback, the orientation swap and `auto`'s measured width; `$env.breakpoint` (`xs` < `sm` < `md` < `lg` < `xl`, by viewport width) compares by rank, so `$env.breakpoint >= md` reads naturally.

## Stability

Screen fields listed above are part of the format contract in `api-report.md`. Workflow metadata (`status`, `priority`, `summary`) is validated but does not affect rendering; treat it as provisional for anything beyond authoring tools.
