.lsd File Format Specification
The .lsd format is a single-file, portable envelope that
carries a complete LSD brand package: tokens, custom styles, user
components, theme overrides, class combos, interactions, and pages.
It is designed to be imported by the LSD editor, by third-party tools,
and by AI agents that want to read or emit brand state.
1. Envelope
A .lsd file is a gzipped UTF-8 JSON document.
Implementations should accept an uncompressed UTF-8 JSON document
as a fallback (detected by the absence of the gzip magic bytes
0x1f 0x8b at offset 0).
gzip header (0x1f 0x8b)
payload (UTF-8 JSON):
{
"magic": "LSD/1",
"version": 1,
"exportedAt": 1712345678901,
"state": { ... },
...
}
Required envelope keys
| Key | Type | Description |
|---|---|---|
magic | string | Exactly "LSD/N" where N is a positive integer. |
version | number | Integer matching the major number in magic. |
exportedAt | number | Unix epoch ms at time of export. |
state | object | The core AppState. See §2. |
Optional envelope keys
These are mirrors and overrides of fields that also
appear inside state. Envelope-level copies win on import
when both are present — this lets publishers ship a stable,
forward-compat view of a subtree.
| Key | Type | Description |
|---|---|---|
customStyles | array | Custom styles attached to the active page. |
brandPackages | array | Named brand packages the user has saved. |
userComponents | array | Reusable component definitions. |
classCombos | array | Named class-combo records. |
globalClasses | array | Named global CSS classes. |
themeStyles | array | Per-tag theme style overrides. |
elementPresets | array | Saved element presets. |
pages | array | Page records (each with its own styles, patches, visibility). |
activePageId | string | Which page is active on import. |
builderMode | string | "builder" or "off". |
builderPatches | array | Serialized builder patches. |
assets | object | { logo?, favicon? } — data URLs permitted. |
2. state shape
state is the AppState emitted by the editor.
All fields are optional on import — missing fields backfill from
defaults.
| Field | Type | Notes |
|---|---|---|
tokens | object | Design-token tree (colors, spacing, radius, shadow, type). |
roleTokens | object | Role-derived tokens (e.g. --text--on-primary). |
stateVariants | object | Hover/focus/active/disabled derivations. |
typeTokens | object | Type-scale tokens (heading sizes, line heights). |
customStyles | array | Flat list of { id, name, css } records. |
brandPackages | array | Named preset bundles. |
activeTheme | string | "light", "dark", or a user theme id. |
theme | string | Legacy alias for activeTheme. |
framework | string | Export target: tailwind, bootstrap, foundation, … |
baseColor | string | Hex seed for palette generation. |
palette | array | Array of ColorToken records. |
fonts | object | Body / heading / display family picks. |
exporter | string | Active exporter name. |
device | string | Viewport preset: mobile / tablet / desktop / full. |
pages | array | See §3. |
activePageId | string | Active page in the editor. |
visibility | array | Visibility rules scoped to the active page. |
interactions | array | Interaction rules scoped to the active page. |
builderMode | string | "builder" or "off". |
builderPatches | array | Serialized tree patches. |
fxPresets | array | Saved animation-effect presets. |
filterPresets | array | Saved filter presets. |
3. Pages
A page groups page-scoped records together. On import with an
activePageId that resolves to a known page, the editor
hydrates the top-level mirrors (customStyles,
builderPatches, visibility,
interactions) from that page.
{
"id": "page-home",
"name": "Home",
"path": "/",
"customStyles": [],
"builderPatches": [],
"visibility": [],
"interactions": []
}
4. Compression
The canonical encoding is gzip. The editor uses
CompressionStream('gzip') for export and
DecompressionStream('gzip') for import.
If a consumer lacks CompressionStream, it should emit
uncompressed JSON with the same envelope shape and the same filename
extension. Readers auto-detect by checking the gzip magic bytes at
offset 0:
0x1f 0x8b— gunzip then parse JSON.- anything else — parse JSON directly.
5. Size limits
| Limit | Value | Rationale |
|---|---|---|
| Max file size | 5 MiB | Keeps imports fast; discourages asset embedding. |
| Max JSON depth | ~32 | Sanitizers cap recursion. |
| Max asset data URL | ~1 MiB | Logo / favicon should be tiny SVG or small raster. |
6. Versioning policy
The envelope follows a LSD/N major-version scheme.
- Minor / additive changes stay on the current major — new fields are optional, defaults are backfilled on import, older editors ignore unknown keys.
- Breaking changes bump the major:
LSD/2. Readers that only understandLSD/1should refuse the import with a clear error.
A reader may forward-accept files whose major is
higher than it understands only if the state shape parses
cleanly. The reference editor does this defensively.
7. Import sanitization
All consumers SHOULD apply the following on import:
- Size cap: reject files larger than 5 MiB.
- Magic check: require
magicmatching^LSD\/\d+$. - Version check: reject unsupported majors.
- JSON depth cap: reject pathologically nested payloads.
- Key regex: reject object keys that don't match
^[A-Za-z_][A-Za-z0-9_-]*$. - Type coercion: unknown arrays default to
[], unknown strings to"", unknown numbers to0. - Dedupe by natural key: collisions get suffixed (e.g.
-2,-3).
8. Minimal example payload
{
"magic": "LSD/1",
"version": 1,
"exportedAt": 1712345678901,
"state": {
"tokens": {
"colors": {
"primary": "#2dd4bf",
"background": "#0b1017",
"text": "#e4e9f2"
},
"spacing": { "sm": "8px", "md": "16px", "lg": "24px" }
},
"framework": "css-vars",
"activeTheme": "dark",
"pages": [
{ "id": "page-home", "name": "Home", "path": "/" }
],
"activePageId": "page-home"
}
}
Gzipped, this payload is roughly 450 bytes on the wire.
9. Compatibility with third-party tools
A third-party tool that wants to read .lsd only needs:
- Gunzip the input (with fallback to treating the bytes as UTF-8 JSON).
- Parse JSON.
- Check
magic === "LSD/1". - Read
state.tokensandstate.framework.
A third-party tool that wants to emit
.lsd only needs to produce an envelope with
magic, version, exportedAt, and
state — the editor will backfill every other field from
defaults.
10. Security notes
.lsdfiles can contain data URLs inassets.logo,assets.favicon, and withincustomStyles. Renderers that display these should apply a strict Content-Security-Policy.customStylesentries contain raw CSS strings. Hosted / multi-tenant consumers MUST sanitize or sandbox before rendering — at minimum strip@import,expression(), and-moz-binding.- No executable code is part of this format. Future revisions will not add executable payloads without a major version bump.
11. License
This spec is published under CC0. The reference editor is proprietary;
the LSD framework package is MIT-licensed. Implementers may freely
produce and consume .lsd files without attribution.
Reference implementation:
Writer: src/export/lsdFile.ts
Reader: src/import/lsdFile.ts