A three-column tour of JOTLANG — the source on the left, the HTML it compiles to in the middle, and what it actually looks like on the right. Everything you see rendered is real, with Tailwind doing the styling.
A JOTL document is a set of directives. One of them is always the markup tree. Everything else — DOCTYPE, html, head, body — is generated for you.
The simplest possible JOTL file has one directive — >> document — containing whatever you want rendered. The compiler wraps the result in a full HTML skeleton automatically.
Three things are happening here. The >> and << pair is the directive sigil — the widest structural bracket in JOTL, used for document-level sections. The > and < pair is the block element sigil, used for everything inside the markup tree. And the .1 after heading is a variant, a suffix that tells the compiler this is level 1 rather than level 2 or 3.
The colon after each opener is mandatory — it's the moment where tag declaration ends and content begins. This consistency matters: whether a block is one line or fifty, the structure reads the same way.
HTML has block, inline, and void elements, but uses the same <tag></tag> syntax for all three. JOTL gives each its own sigil, so you can tell at a glance what kind of thing you're looking at.
Block elements hold other elements or prose. They open with >, close with <, and typically span multiple lines. The closing tag can optionally name the element for clarity.
A paragraph inside the section.
Inline elements appear within text flow and stay on one line. They open with /> and close with </. The sigil shape is visually distinct from block markers, so mixed content reads cleanly.
This paragraph has emphasized text, plus a badge.
Some elements are complete on their own — an image, a line break, a form field. In HTML these are called "void" elements. JOTL gives them a third sigil, !>, with no closer. The exclamation point visually announces: this element is finished.
HTML's single <tag> syntax makes you read documentation to know what kind of element you're dealing with. Is <progress> block? Inline? Void? In JOTL, the sigil tells you immediately. > progress < is a block. /> progress </ is inline. !> progress is void. No spec lookup required.
Attributes describe properties of an element. JOTL puts them in square brackets after the tag name, and accepts unquoted values for simple cases — numbers, booleans, identifiers, and CSS lengths.
You don't need quotes around values that are plain identifiers, numbers, booleans, or CSS lengths. Quotes are only required when the value contains spaces, slashes, or other punctuation that would confuse the parser.
In JOTL, style=primary means the string "primary", not a variable called primary. Variables always use the & sigil: style=&theme_name. This keeps the two clearly distinct — no context switching required.
When you have more than three attributes, or any value is long, break them onto their own lines. Alignment matters for readability.
Many HTML elements are really sub-types — h1 and h2, primary and secondary buttons, small and large cards. JOTL unifies these as variants on a single tag name using a .name suffix.
Variants are part of a tag's identity, not metadata about it. A level-2 heading isn't "a heading with a level attribute" — it's h2, a distinct semantic thing that deserves its own name. Variants keep JOTL tight.
A simple rule: if the value is part of what the element is, make it a variant (.primary). If it's metadata about the element, make it an attribute ([id=submit]). The distinction matters because variants are enumerated — each tag has a fixed list of valid variants — while attributes are open-ended.
The everyday structural elements. Headings use variants for levels, lists use variants for ordered versus unordered, and navigation has semantic sub-tags for the parts.
HTML has <ul> and <ol> — two different tags for bullet versus numbered. JOTL uses one tag (list) with a variant. Switching from bullets to numbers is a one-character change, not a rewrite.
A nav has a brand (logo + name) and a set of links. JOTL gives each its own tag inside > nav <, so the structure is self-documenting.
The places where HTML is most verbose. JOTL cuts the line count roughly in half while making the visual structure match the data structure.
A JOTL table is written the way a table actually looks — columns across the top, rows beneath. The - sibling marker separates cells within each row, positioned to match the column order.
| Name | Role | |
|---|---|---|
| Alice Chen | Engineer | alice@co.io |
| Ben Torres | PM | ben@co.io |
Form fields are semantically void — they carry all their information in attributes. One !> field element replaces a <label> wrapping an <input>. The field type determines what HTML input is generated.
Inside content, JOTL uses Markdown's familiar sigils for bold, italic, and inline code. These are shortcuts for the full inline forms — use whichever fits the situation.
This paragraph has bold text, italic text, and inline code.
function greet(name: string) {
return `Hello, ${name}!`
}
Content inside triple-backtick blocks is preserved exactly — no JOTL parsing happens inside. You don't need to escape &, <, *, or anything else. The text is safe.
A short form for inline links in prose, a full inline form for complex link content, and a block form for wrapping whole sections.
Document-level sections sit above or below the markup tree. They handle metadata, styles, imports, and reactive script — everything that isn't what the user sees on the page.
A complete JOTL file typically has four or five directives: >> meta for the head, >> style for CSS, >> import for components, >> document for the markup tree, and >> script for reactive logic.
A directive with no body (like >> meta [...]) terminates at end-of-line with no closer needed. A directive with a body (like >> document: ... <<) uses : to introduce content and requires << to close. Same logic as block elements — the colon always marks where content begins.
JOTL uses % for comments — not // or # — and \ to escape any sigil. Simple, consistent, unusual in a good way.
To show a literal sigil, escape it. Use *asterisks* for bold. The & character escapes a sigil, _ escapes underscore.
The part that makes JOTL shine. Mobile-first responsive overrides live right next to the attribute they modify — no media queries to hand-write, no Tailwind prefixes to memorize.
The ^(breakpoint)=value syntax attaches a responsive override to the preceding attribute. The base value applies below the breakpoint; the override applies at and above. Mobile-first, by design.
Drag the right edge of the preview below to resize it. Watch the grid change from 1 column to 2 to 4 as you cross 500px and 1024px.
The same syntax handles multiple attributes in one list. Each override attaches to the most recent base attribute — or you can be explicit by writing attr^(breakpoint)=value:
Responsive overrides are designed for layout attributes — cols, gap, layout, size, visible. They aren't meant for appearance (color, background, shadow), because changing colors based on viewport usually means you want a theme system, not a media query. The compiler will warn if you use ^() on a style attribute.
Tailwind handles responsive with prefixes: grid-cols-1 md:grid-cols-2 lg:grid-cols-4. That's six classes for one concern. In JOTL the same thing is cols=1 ^(768px)=2 ^(1024px)=4 — still three values, but living on one attribute, and with the breakpoint values explicit in the source.
The trade-off: Tailwind's named breakpoints (sm, md, lg, xl, 2xl) are shared across a whole project, so they're consistent. JOTL's pixel values are explicit in every file, which is more honest but requires discipline to keep consistent across a codebase. For a small project, JOTL wins on clarity. For a large project, you'd want to factor breakpoints into a shared config — a feature planned for v0.5.
Reactive state, control flow, and the three reference sigils. Shipped in v0.5.0 — the example below compiles and runs today. Skip it if you're writing pure static pages; read on when you're ready for interactivity.
JOTL uses three distinct sigils for three kinds of references — values, functions, and loop iterators. The distinction makes sigil-confusion impossible: a reader always knows what kind of thing they're looking at.
Count: 3
&name is a value reference — reactive, renders as content. @name is a handler — a function to call or pass by reference. $name is a loop iterator, scoped to a ~ for loop body. Using the wrong one is a compile error, not a subtle bug.
A collection of small conventions that make JOTL pleasant to read. Follow them when you can; violate them when you have a reason.
> heading.2 is idiomatic. > heading [level=2] works but reads awkwardly. Variants express identity; attributes express metadata.
< section is useful when the opening > section [id=hero]: is twenty lines above. For three-line blocks, bare < is fine.
When you have more than three attributes, or any value is long, multi-line formatting is the norm. Readability wins over compactness.
type=email reads better than type="email". Quotes are for strings with spaces or special characters.
Styles belong in >> style, logic in >> script, markup in >> document. Don't put business logic in expression wrappers, and don't hand-write CSS properties in markup attributes.
Start with the base value — what it looks like on a phone — then add overrides for larger viewports. This matches modern CSS conventions and produces smaller compiled output.
Changing columns at different viewport sizes is right. Changing colors at different viewport sizes is probably wrong — reach for a theme system instead.
A quick translation table for people moving between the two. Bookmark this page and come back when you've forgotten how something maps.
JOTL ships as a small command-line compiler, a development server with live reload, and a syntax-highlighting extension for VS Code and Cursor.
The compiler is published to npm as jotl. Install it globally to get the jotl command on your PATH.
jotl servePointing the dev server at a JOTL file (or a folder with an index.jot) compiles on every save, splits the output into pure HTML plus linked runtime.js and styles.css, and injects a live-reload script that calls location.reload() the moment the file changes. No iframes, no build tooling, no framework — just a real browser viewing real HTML.
/The compiled HTML body, with two <script> tags appended: one pointing at /__jotl/runtime.js (the reactive runtime plus your transpiled >> script body) and one at /__jotl/livereload.js (a 12-line EventSource client). Inline >> style bodies are extracted to /__jotl/styles.css. View source and you'll see plain HTML — exactly what would ship to production.
The jotlang-language extension works in both VS Code and Cursor. Syntax highlighting handles every directive, sigil, and control-flow keyword — including embedded TypeScript, JavaScript, and CSS inside >> script and >> style blocks. The Language Server provides diagnostics, hover tooltips, and completions.
Every JOTL document is rendered with a small preset stylesheet baked in by the compiler — roughly 9 KB inlined, no external requests. The foundation is new.css by Xz (MIT-licensed, vendored), which classlessly styles raw HTML elements with a centered 750 px content rail. On top of that we layer a small set of JOTL primitives that match the structural classes the compiler emits — .jotl-grid, .jotl-stack, .jotl-group, .jotl-card — plus a handful of quality-of-life rules (e.g. horizontal spacing between sibling <button> elements). Light and dark mode come for free via prefers-color-scheme.
Re-skinning is one variable away. Every visual decision routes through a CSS custom property under the --jotl-* namespace (--jotl-bg-1, --jotl-tx-1, --jotl-lk-1, --jotl-radius, …), so overriding a single token in your own stylesheet re-paints the document without touching selectors.
<style> in <head>>> style [preset=base]>> style [preset=none]>> style [ref="/jotl.css"]The cascade is engineered so authors always win: the preset is emitted first, then any >> style [ref=...] links, then inline >> style: { ... } bodies, then the compiler's responsive scope rules. Override --jotl-lk-1, --jotl-bg-1, --jotl-radius, or any of the other tokens documented in packages/compiler/styles/README.md.
If you outgrow the defaults, copy styles/jotl.css from the npm package, opt out with >> style [preset=none], and link your fork. There's nothing magical about the preset — it's plain CSS targeting the same selectors the compiler emits.
JOTL lives as a small monorepo. The compiler, the language server, and the editor extension each have their own package.json and can be released independently.
If you've never run JOTL locally before — install the package globally, save any of the >-block examples from §1–§7 of this tutorial as hello.jot, then run jotl serve hello.jot and open the URL. Edit the file, save, and watch the page reload.
A second compiler — solid-jotlang — emits SolidJS components from the same JOTL grammar. Use it when you want fine-grained reactivity, components, signals, and a full SPA build pipeline. New in v0.5.0.
The standalone jotl compiler emits static HTML and a tiny runtime — perfect for content sites and progressively enhanced pages. The sibling package solid-jotlang takes the same .jot source and compiles it to SolidJS components instead, so you get reactive signals, derived memos, effects, and idiomatic JSX control flow — written entirely in JOTL's whitespace-sigil syntax.
Authoring stays the same: > heading.1: Counter <, &count, @increment, ~ if, ~ for. What changes is the output. solid-jotlang emits a Solid component (TSX/JSX) per >> component: block, plain JavaScript at module scope for top-level >> script:, and global stylesheet injection for any >> style: body.
The new >> component Name: directive is a meta-wrapper — it can contain its own >> props:, >> script:, >> style:, and >> document: blocks. One .jot file may declare any number of components, each emitted as an exported function.
A file with no >> component: blocks but a top-level >> document: still works — the compiler emits a single exported component called Default. This makes the simplest case (one component per file) require zero ceremony.
JOTL-Solid is explicit by design: the variables you want to be reactive are declared with the signal(), memo(), or resource() helpers, and effects use effect(...). The compiler rewrites those calls into Solid's primitives, hoists ES imports out of the script body, and turns assignments to signals into setter calls.
Reads call the signal — count() in expressions, exactly the Solid convention. Writes look like ordinary assignments because the compiler rewrites them. Inside JOTL markup, &count automatically calls the signal too, so the same reference works in both places.
JOTL's ~ if / ~ elif / ~ else and ~ for become Solid's <Show>, <Switch> / <Match>, and <For>. This means you get fine-grained DOM updates without a virtual DOM diff — a single signal change updates only the affected node.
The package ships a Vite plugin under solid-jotlang/vite. Drop it next to vite-plugin-solid and import .jot files like any other module.
Use jotl for documents, marketing sites, and anything you'd otherwise write in static HTML or a small static-site generator. Use solid-jotlang when the page is an application — interactive forms, real-time data, multi-component UIs that need reactivity.
Made for people who write markup by hand, prefer short sigils to long tags, and want their responsive design to live next to the layout attribute it modifies.
This tutorial covers JOTL v0.5.0. The full specification is in spec/RULEBOOK.md; the compiler reference is in spec/ELEMENT_MAPPING.md; the example corpus lives in packages/compiler/tests/examples/ and packages/compiler/tests/expected/. The compiler is published as jotl on npm; the editor extension as jotlang-language on the VS Code marketplace.