---
title: "ui.layout@1 · OpenVibe.Services"
canonical: "https://openvibe.services/docs/contracts/ui.layout"
updated: null
---

# ui.layout@1 · OpenVibe.Services

HTML: [ui.layout@1 · OpenVibe.Services](https://openvibe.services/docs/contracts/ui.layout)

# UiLayout `ui.layout@1`

Generated at 2026-11-13 06:25:48 UTC from [`openvibe-contracts v0.419.0`](https://github.com/OpenVibers/OpenVibe.Contracts/tree/v0.419.0) and [`openvibe-sdk v0.161.0`](https://github.com/OpenVibers/OpenVibe.SDK/tree/v0.161.0).

- Version: 1.0.0
- Owner: contracts
- Visibility: public
- Status: active
- Compatibility: backward
- Schema: [`https://openvibe.network/contracts/ui/layout.v1.json`](/docs/contracts/ui.layout.json)

ui.layout@1: one person's arrangement of one surface on one device class. A layout names the surface and its catalog version, the device, its own name, its author and its sharing (private, link or gallery), and one entry per part it places. A part is visible or not, sits on the surface's grid (x, y, w, h in cells) or docks to an edge for an overlay, and carries a style (a theme token tint, a shape, opacity 0.4-1 and the label) and bindings. It is data only: no URL, no markup and no code may appear anywhere in it, and every part id, tint, shape, opacity, size and binding must be one the catalog allows. openvibe-sdk/ui-layout validates it before any server, editor or AI answer stores or shows it.

## Fields

| Field | Type | Required | Description | Constraints |
| --- | --- | --- | --- | --- |
| `id` | string |  | Assigned by OpenVibe.Network when the layout is stored; absent in a draft, a share answer or an AI suggestion. | - `pattern ^lay_[0-9A-HJKMNP-TV-Z]{26}$` |
| `revision` | integer |  | Assigned by the store; every change to a stored layout moves it by one. | - `minimum 1` |
| `active` | boolean |  | Set by OpenVibe.Network: the one layout a product draws for this surface on this device class. A draft, a share answer and an AI suggestion carry no active field. |  |
| `surface` | string | yes |  | - `pattern ^[a-z][a-z0-9_]*(\.[a-z0-9_]+)+$`<br>- `maxLength 80` |
| `surface_version` | integer | yes |  | - `minimum 1` |
| `device` | enum | yes |  | - `one of "phone", "tablet", "computer"` |
| `name` | string | yes | The person's name for this layout; "My phone controls" is fine. | - `minLength 1`<br>- `maxLength 80` |
| `author` | object | yes | The author's display name and, for a private or link layout, the subject. A share or gallery answer carries the display name only, never the account. | - `no other fields` |
| `author.name` | string | yes |  | - `minLength 1`<br>- `maxLength 80` |
| `author.subject` | [identity.subject-ref](/docs/contracts/identity.subject-ref) |  |  |  |
| `sharing` | enum | yes |  | - `one of "private", "link", "gallery"` |
| `parts` | `https://openvibe.network/contracts/ui/layout.v1.json#/$defs/part` | yes |  | - `minItems 1`<br>- `maxItems 64` |
| `updated_at` | string |  |  | - `format date-time` |

## Examples

From the contract's own test fixtures: valid ones validate, rejected ones must fail.

 Valid: overlay-dock

~~~~
{
  "surface": "bot.overlay",
  "surface_version": 1,
  "device": "computer",
  "name": "One-thumb overlay",
  "author": {
    "name": "Ana"
  },
  "sharing": "link",
  "parts": [
    {
      "id": "stop",
      "visible": true,
      "place": {
        "dock": "right",
        "order": 0,
        "w": 2,
        "h": 1
      },
      "style": {
        "tint": "danger",
        "shape": "round",
        "opacity": 1,
        "label": true
      },
      "bindings": {
        "keys": [
          "Space"
        ],
        "gamepad": [
          "south"
        ]
      }
    },
    {
      "id": "battery",
      "visible": true,
      "place": {
        "dock": "right",
        "order": 1,
        "w": 2,
        "h": 1
      },
      "style": {
        "tint": "surface",
        "shape": "pill",
        "opacity": 0.8,
        "label": false
      },
      "bindings": {
        "keys": [],
        "gamepad": []
      }
    }
  ]
}
~~~~

Valid: phone-controls

~~~~
{
  "surface": "bot.controls",
  "surface_version": 1,
  "device": "phone",
  "name": "My phone controls",
  "author": {
    "name": "Ana",
    "subject": {
      "type": "user",
      "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
    }
  },
  "sharing": "private",
  "parts": [
    {
      "id": "stop",
      "visible": true,
      "place": {
        "x": 0,
        "y": 0,
        "w": 1,
        "h": 1
      },
      "style": {
        "tint": "surface",
        "shape": "pill",
        "opacity": 1,
        "label": true
      },
      "bindings": {
        "keys": [
          "Space"
        ],
        "gamepad": [
          "south"
        ]
      }
    },
    {
      "id": "stick",
      "visible": true,
      "place": {
        "x": 0,
        "y": 1,
        "w": 4,
        "h": 3
      },
      "style": {
        "tint": "surface",
        "shape": "round",
        "opacity": 1,
        "label": false
      },
      "bindings": {
        "keys": [
          "W",
          "A",
          "S",
          "D"
        ],
        "gamepad": [
          "left-stick"
        ]
      }
    },
    {
      "id": "battery",
      "visible": true,
      "place": {
        "x": 0,
        "y": 4,
        "w": 2,
        "h": 1
      },
      "style": {
        "tint": "success",
        "shape": "pill",
        "opacity": 1,
        "label": true
      },
      "bindings": {
        "keys": [],
        "gamepad": []
      }
    },
    {
      "id": "latency",
      "visible": false,
      "place": {
        "x": 2,
        "y": 4,
        "w": 2,
        "h": 1
      },
      "style": {
        "tint": "muted",
        "shape": "pill",
        "opacity": 1,
        "label": true
      },
      "bindings": {
        "keys": [],
        "gamepad": []
      }
    }
  ],
  "updated_at": "2026-11-11T12:00:00.000Z"
}
~~~~

Rejected: colour-tint

~~~~
{
  "surface": "bot.controls",
  "surface_version": 1,
  "device": "phone",
  "name": "Bad tint",
  "author": {
    "name": "Ana"
  },
  "sharing": "private",
  "parts": [
    {
      "id": "stop",
      "visible": true,
      "place": {
        "x": 0,
        "y": 0,
        "w": 2,
        "h": 2
      },
      "style": {
        "tint": "#ff0000",
        "shape": "round",
        "opacity": 1,
        "label": true
      },
      "bindings": {
        "keys": [],
        "gamepad": []
      }
    }
  ]
}
~~~~

Rejected: transparent

~~~~
{
  "surface": "bot.controls",
  "surface_version": 1,
  "device": "phone",
  "name": "Too faint",
  "author": {
    "name": "Ana"
  },
  "sharing": "private",
  "parts": [
    {
      "id": "stop",
      "visible": true,
      "place": {
        "x": 0,
        "y": 0,
        "w": 2,
        "h": 2
      },
      "style": {
        "tint": "danger",
        "shape": "round",
        "opacity": 0.2,
        "label": true
      },
      "bindings": {
        "keys": [],
        "gamepad": []
      }
    }
  ]
}
~~~~

## Validate

~~~~
const contracts = require('openvibe-contracts');
contracts.validate('ui.layout@1', value);   // { valid, errors: [{ path, message }] }
~~~~
