Skip to content

Jx Markdown

Studio writes this format for you. The writing surface and frontmatter panel edit .md files visually — this page documents the syntax for when you hand-edit one or read a diff.

A Jx Markdown file is a Markdown document that transpiles to the same JSON structure the compiler and runtime consume everywhere else. It combines three layers:

  1. YAML frontmatter — top-level document properties ( tagName , state , style , …)

  2. Directives — explicit elements using remark-directive syntax

  3. Standard Markdown — headings, paragraphs, lists, and links mapped to HTML elements

Markdown support is opt-in: the project must enable the parser extension in project.json ( "extensions": ["@jxsuite/parser"] ). There is no implicit .md handling without it.

A minimal component

---
tagName: my-greeting
state:
  name:
    type: string
    default: World
---

:::div

# Hello, undefined!

:::

The frontmatter declares the document schema; the body declares the element tree. A .md file whose frontmatter has a tagName containing a hyphen is a component; a file without one is a content document (a blog post, a docs page) that produces a plain element tree. Any content file can add component schema later without changing how it's processed.

Frontmatter

All top-level Jx document keys go in the YAML frontmatter, $ prefixes included: tagName , $id , state , $media , $defs , $elements , $layout , $paths , $handlers , imports , observedAttributes . Any other keys pass through to the document unchanged — which is how pages set title or $head .

Directives

Three directive forms cover the element tree:

Container directives wrap children. Outer containers use more colons than inner ones (minimum three), and the closing fence matches the opening count:

::::section{className="hero"}
:::h1
Welcome
:::
::::

Leaf directives (two colons) are self-closing:

::img{src="/photo.jpg" alt="A photo"}

Text directives (one colon) sit inline within prose:

Click :a[here]{href="/about"} for details.

The directive name is the element's tagName — any HTML tag or any registered custom element.

Attributes

Attributes use the HTML-like {key="value"} syntax. Three characters can't start a directive attribute key — $ , : , and @ — so Jx defines drop-the-prefix conventions that the transpiler reverses:

You write The document gets
prototype , ref , component , props , switch , elements $prototype , $ref , $component , $props , $switch , $elements
--title , --description $title , $description (element annotations, dropped from HTML output)
style.hover.color (see below) style[":hover"].color

DOM properties like src , id , and export are not mapped — they pass through as-is. Attributes matching aria-* , data-* , or slot route into the element's attributes object; everything else becomes a top-level DOM property.

Nested objects are written as dot-separated keys. Props on a component instance use props.* :

:::pricing-card{props.plan="Pro" props.price.ref="#/state/proPrice"}
:::

which expands to:

{
  "tagName": "pricing-card",
  "$props": {
    "plan": "Pro",
    "price": { "$ref": "#/state/proPrice" }
  }
}

Style attributes

Element styles are style.* dot-path attributes. CSS pseudo-classes drop their : prefix and named media queries drop their @ prefix — the transpiler restores both:

::button{style.padding="8px 16px" style.hover.backgroundColor="blue" style.--dark.color="#f0f0f0"}
Click me
{
  "tagName": "button",
  "style": {
    "padding": "8px 16px",
    ":hover": { "backgroundColor": "blue" },
    "@--dark": { "color": "#f0f0f0" }
  }
}

Root-level styles go in frontmatter under style , where YAML allows :hover and @--dark keys to be written directly.

Repeaters

An array repeater has no tagName , so it serializes as a directive named after its $prototype , with the nested block as the map template:

# Recent posts

:::Array{items.ref="#/state/posts"}

:::

Standard Markdown

Plain Markdown maps to the elements you'd expect: headings to h1h6 , paragraphs to p , emphasis to em / strong , links to a , images to img , lists to ul / ol + li , fenced code to pre > code , tables to table structures. Mixing prose and directives in one file is the normal case, not a special one.

Fenced code with a recognized language tag ( json , ts , js , bash , html , css , yaml , md , and their aliases) is syntax-highlighted at build time: each token becomes a span carrying its light and dark colors as --shiki-light / --shiki-dark CSS variables, so highlighting follows the site's color scheme automatically. Unknown languages fall back to plain text — no fence ever breaks.

Note

These docs are themselves Jx Markdown: every aside like this one is a container directive ( :::doc-note , :::doc-tip , :::doc-warning ) rendered by a component the docs site registers via $elements on its content collection.

Markdown or JSON?

Both formats produce the same document, and Studio edits both transparently — so choose by what the file mostly contains:

Prefer Markdown Prefer JSON
Content-heavy pages (blog posts, docs) Complex interactive components
Components with significant prose Components with many state functions
Landing pages, marketing content Deeply nested element hierarchies
Quick prototyping Components with complex $prototype usage

Two hard limits to know: there is no runtime .md format (files always transpile to JSON first), and event-handler bodies or computed expressions live in frontmatter state definitions — never inline in the directive body.