LSD

.lsd File Format Specification

LSD/1 Status: Stable · MIME: application/vnd.lsd+gzip · Extension: .lsd

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

KeyTypeDescription
magicstringExactly "LSD/N" where N is a positive integer.
versionnumberInteger matching the major number in magic.
exportedAtnumberUnix epoch ms at time of export.
stateobjectThe 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.

KeyTypeDescription
customStylesarrayCustom styles attached to the active page.
brandPackagesarrayNamed brand packages the user has saved.
userComponentsarrayReusable component definitions.
classCombosarrayNamed class-combo records.
globalClassesarrayNamed global CSS classes.
themeStylesarrayPer-tag theme style overrides.
elementPresetsarraySaved element presets.
pagesarrayPage records (each with its own styles, patches, visibility).
activePageIdstringWhich page is active on import.
builderModestring"builder" or "off".
builderPatchesarraySerialized builder patches.
assetsobject{ 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.

FieldTypeNotes
tokensobjectDesign-token tree (colors, spacing, radius, shadow, type).
roleTokensobjectRole-derived tokens (e.g. --text--on-primary).
stateVariantsobjectHover/focus/active/disabled derivations.
typeTokensobjectType-scale tokens (heading sizes, line heights).
customStylesarrayFlat list of { id, name, css } records.
brandPackagesarrayNamed preset bundles.
activeThemestring"light", "dark", or a user theme id.
themestringLegacy alias for activeTheme.
frameworkstringExport target: tailwind, bootstrap, foundation, …
baseColorstringHex seed for palette generation.
palettearrayArray of ColorToken records.
fontsobjectBody / heading / display family picks.
exporterstringActive exporter name.
devicestringViewport preset: mobile / tablet / desktop / full.
pagesarraySee §3.
activePageIdstringActive page in the editor.
visibilityarrayVisibility rules scoped to the active page.
interactionsarrayInteraction rules scoped to the active page.
builderModestring"builder" or "off".
builderPatchesarraySerialized tree patches.
fxPresetsarraySaved animation-effect presets.
filterPresetsarraySaved 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:

5. Size limits

LimitValueRationale
Max file size5 MiBKeeps imports fast; discourages asset embedding.
Max JSON depth~32Sanitizers cap recursion.
Max asset data URL~1 MiBLogo / favicon should be tiny SVG or small raster.

6. Versioning policy

The envelope follows a LSD/N major-version scheme.

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:

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:

  1. Gunzip the input (with fallback to treating the bytes as UTF-8 JSON).
  2. Parse JSON.
  3. Check magic === "LSD/1".
  4. Read state.tokens and state.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

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