Skip to content

Jx Markdown

Studio writes this format for you. The writing surface and frontmatter panel edit .md files visually, and 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 carries top-level document properties ( tagName , state , style , …)

  2. Directives are explicit elements using remark-directive syntax

  3. Standard Markdown maps headings, paragraphs, lists, and links 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, and 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"}

:::

Heading anchors

Every heading gets an id built from its own text, so ## Getting started becomes #getting-started and repeats get -2 , -3 in document order. Two things about that are worth knowing outside English: letters in any script are kept, so a Japanese or Cyrillic heading gets a real anchor rather than an empty one, and an accented heading produces one anchor whichever way the accent was typed, since é can be a single character or an e plus a combining mark, which look identical and used to produce two different links. Plain-ASCII headings are unaffected, so existing links keep working.

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, and 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.