Build Your Deck
Everything you need to create, test, and publish an ExouDeck dashboard. From starter kit to marketplace in minutes.
Quick Start
Download the starter kit, open it in your browser, and you have a working deck. No build tools, no dependencies, no server required.
Three steps to a working deck:
- 1Unzip — Extract the starter kit to any folder
- 2Open — Open index.html in a browser — the example widgets render immediately with demo data
- 3Customize — Edit deck.json to change the name and theme, add widgets to the widgets/ folder
Deck Structure
A deck is a flat folder (zipped for upload) containing these files. Only deck.json and your widgets are required — everything else is auto-injected on upload if missing.
index.htmlEntry point — loads the runtime and themeNodeck-runtime.jsEngine — layout, polling, widgets, license, hot-reloadNodeck.jsonConfig — API endpoint, theme, layout, widget registryYestheme.cssVisual theme using CSS custom propertiesYessample-data.jsonDemo data so the deck works offlineYeswidgets/*.htmlOne self-contained file per widgetCreate newAGENT_BRIEF.mdStructured spec for AI agentsNoAuto-injection: When you upload a zip with just deck.json and your widgets/ folder, the platform automatically adds the runtime, index.html, default theme, sample data scaffold, and agent brief.
Configuration — deck.json
This is the control file. It tells the runtime how to lay out widgets, where to fetch data, and how to style the dashboard.
{
"exoudeck_version": "1.0",
"name": "My Dashboard",
"description": "What this deck does",
"api": {
"base_url": "http://localhost:8800",
"poll_interval_ms": 10000,
"endpoints": { "dashboard": "/dashboard" },
"demo_mode": true
},
"theme": {
"file": "theme.css",
"variables": {
"--accent": "#f3bd7d",
"--bg-0": "#050608",
"--text": "#f4efe7"
}
},
"layout": {
"columns": 12,
"widgets": [
{
"id": "my-widget",
"widget": "my-widget",
"position": { "col": 1, "span": 6, "row": 1 },
"config": {}
}
]
},
"widget_registry": {
"my-widget": {
"file": "widgets/my-widget.html",
"name": "My Widget",
"data_path": "my_key"
}
}
}Key fields
api.demo_modeWhen true, loads sample-data.json instead of polling an API. Set to false for production.api.base_urlThe root URL of your data API. Only used when demo_mode is false.layout.widgets[].positionGrid placement — col (1-12), span (width), row (visual grouping).widget_registry[].data_pathKey in the API response to extract and pass to this widget.Creating Widgets
Each widget is a single .html file in the widgets/ folder. It runs inside a Shadow DOM — fully isolated styles, no conflicts.
<style>
.card {
padding: 18px;
min-height: 160px;
border: 1px solid var(--stroke);
border-radius: var(--radius);
background: var(--glass);
color: var(--text);
font-family: "Plus Jakarta Sans", system-ui, sans-serif;
}
h3 { margin: 0 0 14px; font-family: "Space Grotesk", sans-serif; }
</style>
<div class="card" data-widget="my-widget">
<h3>My Widget</h3>
<div id="value">Loading...</div>
</div>
<script>
(function() {
// Init: called once when widget loads
window['__exoudeck_my-widget'] = function(shadow, config) {
// setup canvas, bindings, etc.
};
// Data: called every time new data arrives
var host = document.currentScript.getRootNode().host;
if (host) {
host.addEventListener('deck-data', function(e) {
var data = e.detail;
if (!data) return;
var el = host.shadowRoot.getElementById('value');
if (el) el.textContent = data.count;
});
}
})();
</script>How it works
Tip: The starter kit includes two example widgets — a metric card and an animated chart. Use them as templates for your own.
Sample Data
When demo_mode is true in deck.json, the runtime loads sample-data.json instead of hitting an API. This means your deck works offline — open index.html and everything renders.
{
"budget": {
"daily_budget_usd": 5.00,
"spent_today_usd": 3.42,
"remaining_today_usd": 1.58,
"calls_today": 127,
"tokens_today": 184200
},
"jobs": {
"total": 45,
"active": 3,
"stalled": 0,
"failed": 1,
"completed": 41
},
"status": {
"focus": "System operational",
"state": "Healthy"
}
}Structure rules
- Top-level keys must match the data_path values in your widget registry
- Each widget gets passed only its own slice — if data_path is "budget", it receives the budget object
- Use realistic values so the deck looks good in previews and during review
Theming
Themes are driven by CSS custom properties. Set them in deck.json → theme.variables and the runtime applies them to the document root. Widgets inherit them automatically.
--bg-0Page background--bg-1Card background--glassGlass fill--strokeBorder color--textPrimary text--mutedSecondary text--accentPrimary accent--accent-2Secondary accent--okSuccess / healthy--warnWarning--critCritical / error--radiusBorder radiusTip: Use CSS var(--accent, #f3bd7d) with fallbacks inside your widgets so they still render if theme variables are missing.
Uploading Your Deck
When your deck is ready, zip the folder and upload it through the creator dashboard. The platform validates, fills in any missing boilerplate, and queues it for review.
What happens on upload
- 1Validation — The zip is parsed and checked for a valid deck.json with matching widget files.
- 2Auto-injection — Missing standard files (runtime, index.html, theme, sample-data scaffold, agent brief) are injected automatically.
- 3Re-validation — The completed deck is validated again to ensure everything is correct.
- 4Storage — The final zip is stored and linked to your product listing.
- 5Review queue — Your product status changes to Pending — the admin team reviews it before it goes live.
Minimum upload: You only need a deck.json and your widgets/ folder. Everything else is added for you. Maximum file size is 10MB.