Layout format
Everything a layout says about itself lives in plugin.json. This page is the full reference.
plugin.json#
{
"id": "yourname.my-layout",
"name": "My Layout",
"version": "1.0.0",
"author": "Your name",
"description": "What it looks like, in a sentence.",
"type": "layout",
"requires": {
"aseoriy.library-layouts": { "version": "^2.1.0", "hub": "b2ac4147-faca-4770-87d2-b429a8fa5abf" }
},
"settings": [
{ "id": "shelf", "type": "toggle", "label": "Recently played row", "default": true }
],
"layout": {
"contract": 2,
"styles": ["layout.css"],
"preview": "preview.png",
"tokens": { "--sl-accent": "#ff7a59" },
"labels": { "recentGames": "Jump back in" },
"blocks": {
"home": { "covers": "portrait", "shelf": { "setting": "shelf", "values": { "true": "recent", "false": "none" } } },
"gamePage": { "layout": "hero", "stats": ["playtime", "lastPlayed"] },
"manageGame": "properties",
"profilePage": true
}
}
}
idis required: lowercase,yourname.layout-name, and never reused for something else. It's how Sail tells layouts apart and finds updates.type: "layout"marks it as a layout. A layout with amainscript is refused.requirestells Sail to offer Library Layouts to anyone who installs your layout without it. Keep it as it is.contractis the version of the format you wrote for. The current one is 2. Library Layouts still shows contract 1 layouts, and tells people when a layout needs a newer engine.styleslists your stylesheets (up to 8).previewpoints at an optional picture of your layout; Library Layouts doesn't show it yet.
Building blocks#
Each block is a part the engine builds. Leave a block out and Sail's own interface stays in that spot, so you can start small. Unknown blocks and values are ignored (developer mode lists them), so a newer layout still works on an older engine.
| Block | Values | What it builds |
|---|---|---|
titleBar | rows: 1 or 2menu: "full" or "none"sailButtons: "hidden" or "shown" | A menu bar (Sail, View, Friends, Games, Help) holding every Sail button. With 2 rows the page tabs get their own row; with 1 they share the bar. sailButtons: "hidden" moves Sail's buttons (Add Game, Settings and so on) into the menu; with menu: "none" they always stay, so nobody is left without them. |
tabs | position: "titleBar"names: { "downloads": "Store" }order: tabs to show first, in that order (the rest follow)hidden: tabs to hide | Moves Sail's page tabs up into the title bar. Tabs: library, downloads, mods, workshop, downloadsmgr, achievements, maintenance. |
gameList | position: "left"filter, search: true or false | Your games listed in the left panel, with a filter and a search box. |
home | covers: "wide" or "portrait"shelf: "none" or "recent"header: "none" or "count" | Tall covers, a recently played row, and an "All Games (n)" heading on the home page. |
gamePage | layout: "sail" or "hero"stats: any of lastPlayed, playtime, achievements | A full-width hero image with the title on it, and stats next to the Play button. |
manageGame | "properties" | Turns Manage Game into a properties window with its pages listed on the left. |
profilePage | true | A profile page for the signed-in account, opened from the account menu on Sail's profile button. |
bottomBar | items: any of addGame, downloads, friends | A bar along the bottom with those buttons. |
friendsPanel | "shown" or "hidden" | Whether Sail's friends list starts shown on the right. People can still open it from the bottom bar's Friends button or the menu bar's Friends menu, so include one of those if you hide it. |
Options people can change#
Options are your manifest's normal settings (toggle, select, number, text, heading, the same as plugin settings). Sail draws them in its usual settings window and keeps them on each PC. Your CSS and blocks can follow them:
- A toggle that's on adds the class
sl-opt-<id>to the page, so&.sl-opt-shelfin your CSS means "while that toggle is on". - A choice adds
sl-opt-<id>-<value>, for example&.sl-opt-accent-mint. - A number becomes a CSS variable,
var(--sl-opt-<id>). - A block value can follow an option:
{ "setting": "covers", "values": { "true": "portrait", "false": "wide" } }. Withoutvalues, the option's own value is used. - A whole block can be switched with
"when": "<id>"(built only while that toggle is on) or"unless": "<id>".
Colours and fonts#
layout.tokens sets CSS variables while your layout is on. Names must start with --sl-, and values are plain text (no url(), no ;). The engine's parts use these, among others:
| Token | Used for |
|---|---|
--sl-bg, --sl-surface, --sl-surface-strong | Page background and panels |
--sl-top, --sl-list | The title bar and the game list |
--sl-text, --sl-text-bright, --sl-text-strong, --sl-muted, --sl-faint | Text, from normal to quiet |
--sl-accent, --sl-accent-2, --sl-accent-dark | Highlights, the selected game, buttons |
--sl-menu-bg, --sl-menu-hover, --sl-menu-line, --sl-menu-border | Menus |
--sl-art, --sl-letter, --sl-field, --sl-line | Cover placeholders, search fields, divider lines |
--sl-good, --sl-warn, --sl-running | Installed, needs attention, running |
--sl-font | The font for everything the engine draws |
To colour the rest of Sail too, set Sail's own variables (--bg-color, --card-bg, --text-color, --border-color, --accent) in your CSS, as the starter does.
Words#
layout.labels replaces words the engine draws. Text only, up to 60 characters each.
| Label | Default |
|---|---|
home, search | Home, Search |
recentGames, allGames | Recent Games, All Games ({count}). {count} is the number of games. |
today, yesterday, thisWeek, thisMonth, earlier | Today, Yesterday, This week, This month, Earlier |
lastPlayed, playtime, achievements | Last played, Play time, Achievements |
pageGeneral, pageCompat, pageSaves, pageFiles, pageLibrary | The properties window's pages: General, Compatibility, Saves, Installed Files, Library |
addGame, downloads, friends | Add a Game, Downloads, Friends & Chat |
Last updated 21 October 2026