Tutorial
Build your first panel
Write a small community panel from an empty folder, style it with Deska’s theme, run it on your desk and get it ready to publish. About fifteen minutes.
Before you start
A community panel is a folder of web files with a panel.json at its root. Deska runs it in a sandbox, inside its own panel, and the panel talks to Deska only through window.deska. There is no build step and nothing to host. You need Deska 1.26.0 or later on your computer, a text editor and, for the optional preview tool, Node 20 or later.
The panel you build here is a clock that remembers a time zone. The finished folder is starter/ in deska-panels-examples, next to fourteen larger examples, each teaching a different part of the SDK.
1. Make the folder and its panel.json
my-first-panel/
panel.json
index.html
app.css
app.js
icon.svgpanel.json says what the panel is and what it may do. People read it before they install:
{
"schema": 1,
"id": "my-first-panel",
"name": "My First Panel",
"version": "0.1.0",
"description": "A small clock that remembers one time zone.",
"author": "Your name",
"icon": "icon.svg",
"category": "other",
"permissions": [],
"network": [],
"sdk": 1,
"contributes": {
"panel": { "entry": "index.html", "scope": "workspace", "size": { "width": 300, "height": 200 } }
}
}idis 3 to 40 lowercase letters, digits and dashes. Publishing claims it for your account, and ids that name Deska, a coding agent or a built-in panel are refused.permissionsandnetworkare empty, so this panel cannot send notices, open links, copy, or reach the internet. Ask only for what you use.
Every field, and every id that is refused, is in the panel.json reference.
2. Load the SDK
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My First Panel</title>
<link rel="stylesheet" href="app.css">
<script src="/__deska/sdk.js"></script>
<script type="module" src="app.js"></script>
</head>
<body>
<main>
<p class="time" id="time">--:--</p>
<label class="field">
<span>Time zone</span>
<select id="zone"></select>
</label>
</main>
</body>
</html>/__deska/sdk.js is not a file in your folder. Deska serves it inside every panel, always matching the app, and it defines window.deska. Load it as a classic script before anything that uses it.
3. Style it with Deska’s theme
The SDK writes Deska’s theme on your page as CSS custom properties and updates them whenever the theme or the accent changes. Use them for every color and the panel matches dark, light and every accent:
body {
margin: 0;
background: var(--deska-bg);
color: var(--deska-text);
font: 13px/1.45 var(--deska-font-ui);
}
select {
color: var(--deska-text);
background: var(--deska-surface);
border: 1px solid var(--deska-border);
border-radius: var(--deska-radius-small);
}
select:focus-visible {
outline: 2px solid var(--deska-focus);
}The sixteen tokens and what each is for are in Theme tokens. The Theme Inspector example shows each one live, with its contrast.
4. Write the panel in five steps
const $ = (id) => document.getElementById(id)
// 1. Wait for Deska: the panel, the theme, its permissions, the locale.
const init = await deska.ready()
// 2. Load what this panel saved last time. null means a first run.
let zone = (await deska.storage.get('zone')) ?? Intl.DateTimeFormat().resolvedOptions().timeZone
// 3. Render, and name the time under the panel's title on the canvas.
function render() {
const time = new Intl.DateTimeFormat(init.locale, { timeZone: zone, timeStyle: 'short' }).format(new Date())
$('time').textContent = time
deska.setTitle(`${zone.split('/').pop().replaceAll('_', ' ')} ${time}`)
}
for (const name of Intl.supportedValuesOf('timeZone')) $('zone').add(new Option(name, name))
$('zone').value = zone
// 4. Save when the person changes something. Every call can fail with a code.
$('zone').addEventListener('change', async () => {
zone = $('zone').value
render()
try {
await deska.storage.set('zone', zone)
} catch (err) {
console.warn(`Could not save: ${err.code}`)
}
})
// 5. Tick only while someone can see the panel.
let timer = null
function start() {
render()
timer = setInterval(render, 15_000)
}
deska.on('visibility', ({ visible }) => {
clearInterval(timer)
if (visible) start()
})
if (deska.visible) start()
else render()deska.ready()answers with the panel, the theme, its permissions and the person’s locale. Nothing from Deska works before it.deska.storageis where data lasts: the sandbox has nolocalStorageor cookies. Values are JSON, up to 256 KB, shared by every copy of the panel on the computer. Images and other files go indeska.files.deska.setTitleshows a line under the panel’s name on the canvas: up to 60 characters.null, an empty string or a longer title is refused withinvalid_args, never cut. Set the panel’s own name,init.panel.name, to show the name alone.- Every call can fail with a code, like
storage_full,permission_deniedornot_focused. Answer the ones a person can act on; all ten are in Error codes and limits. deska.on('visibility')says when the panel is scrolled away, folded into a dock or behind the far view. It keeps running there, so stop timers and animations and catch up when it shows again.
A panel can reload at any time: moved to a dock, updated, Deska restarted. Keep what matters in storage and rebuild from what deska.ready() returns. Every method, with its arguments and errors, is in the SDK reference.
5. Run it on your desk
Open the Panel Library (Cmd/CtrlShiftE), go to Community, then Build a panel, choose Load a folder and pick the folder that holds panel.json. Deska checks it, lists anything to fix, adds the panel to your desk and reloads it every time you save a file. Switch Deska’s theme to check both looks.
To build next to your editor without Deska, the examples repository has a preview tool that serves a panel with the same rules and plays Deska’s side of the SDK, with a log of every call:
git clone https://github.com/0xPasho/deska-panels-examples.git
cd deska-panels-examples
npm install
npm run preview -- starter # the finished tutorial
npm run preview -- path/to/my-first-panel # your own folderWhat a panel cannot do
- Open frames itself, run workers or load scripts from the internet. To show a YouTube player or another site’s page, the panel asks Deska with deska.embed; audio and video files play from the sites you declare.
- Reach a site that is not in
network, or one that does not allow requests from any origin (CORS). The panel’s origin is opaque. - Send a form anywhere, open a popup or call
alert(). Handle a form’s submit yourself and open links withdeska.openUrl. - Keep a secret. There is no safe place for a token, so use public APIs.
- Take the keyboard on its own. Focus moves only when the person uses the panel; ask for attention with
deska.notify. - Each rule, with what to do instead, is in The sandbox.
6. Publish it
Publishing is open to invited authors for now. Before your first panel, change id, name and author to your own, then tell us what you are building at deska.dev/panels/apply.
Once your account is approved, open the loaded folder’s page in Build a panel, press Publish, say what changed and confirm. Raise version for every release: a published version never changes. Versions, permissions people are asked for again, and unlisting are in Publishing.