Tailwind CDN didn't load. Rendered previews will appear unstyled. Check your network connection or try reloading.
v0.5.0 · tutorial edition

Markup, rewritten for humans.

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.

Written for
HTML authors
Reading time
45 minutes
Install
npm i -g jotl
Editor support
VS Code & Cursor extension
Companion to
spec/RULEBOOK.md
Contents — 16 chapters
§ 01

Your first document

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.

JOTL source
Generated HTML
Rendered
>> document: > heading.1: Hello, world < << document
<!DOCTYPE html> <html> <head></head> <body> <h1>Hello, world</h1> </body> </html>

Hello, world

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.

§ 02

Three flavors of element

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 — multi-line structure

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.

JOTL
HTML
Rendered
> section [id=hero]: > heading.1: Welcome < > text: A paragraph inside the section. < < section
<section id="hero"> <h1>Welcome</h1> <p>A paragraph inside the section.</p> </section>

Welcome

A paragraph inside the section.

Inline — single-line flow

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.

JOTL
HTML
Rendered
> text: This paragraph has /> span [style=highlight]: emphasized </ text, plus /> badge.warning: a badge </. <
<p>This paragraph has <span class="highlight">emphasized </span> text, plus <span class="jotl-badge warning">a badge</span>. </p>

This paragraph has emphasized text, plus a badge.

Void — no body, no closer

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.

JOTL
HTML
Rendered
> form: !> field [type=email label="Email" required] !> break > button.primary: Sign up < < form
<form> <label>Email <input type="email" required> </label> <br> <button class="primary"> Sign up</button> </form>
Why three flavors?

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.

§ 03

Attributes in brackets

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.

Unquoted values

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.

JOTL
HTML
Rendered
% unquoted, quoted, and boolean forms > field [type=email name=work-email]: Email < > grid [cols=3 gap=md]: ... < > link [url="/about us"]: About < > input [disabled]: <
<label>Email<input type="email" name="work-email" ></label> <div class="jotl-grid" style="--cols:3; --gap:md"> ...</div> <a href="/about us"> About</a> <input disabled>
cell
cell
cell
About
Important — bare identifiers are strings

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.

Multi-line attribute lists

When you have more than three attributes, or any value is long, break them onto their own lines. Alignment matters for readability.

JOTL
HTML
Rendered
> button.primary [ type=submit on_press=@submit_form disabled=&is_submitting title="Click to sign up" ]: Sign up <
<button type="submit" class="primary" title="Click to sign up" > Sign up </button>
§ 04

Tag variants

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.

JOTL
HTML
Rendered
> heading.1: Page title < > heading.2: Section < > heading.3: Subsection < > button.primary: Sign up < > button.secondary: Cancel < > button.ghost: Learn more < % stacked variants > button.primary.lg: Start free trial <
<h1>Page title</h1> <h2>Section</h2> <h3>Subsection</h3> <button class="primary"> Sign up</button> <button class="secondary"> Cancel</button> <button class="ghost"> Learn more</button> <button class="primary jotl-size-lg"> Start free trial</button>

Page title

Section

Subsection

Variants versus attributes

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.

§ 05

Headings, lists, navigation

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.

Lists, both kinds

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.

JOTL
HTML
Rendered
> list: - Apples - Bananas - Cherries < > list.number: - Open the door - Walk through - Close it behind you <
<ul> <li>Apples</li> <li>Bananas</li> <li>Cherries</li> </ul> <ol> <li>Open the door</li> <li>Walk through</li> <li>Close it behind</li> </ol>
  • Apples
  • Bananas
  • Cherries
  1. Open the door
  2. Walk through
  3. Close it behind you

Navigation with semantic parts

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.

JOTL
HTML
Rendered
> nav [id=main-nav]: > brand: Streamline < > links: - #'Home'[url="/"] - #'Docs'[url="/docs"] - #'Pricing'[url="/pricing"] < > button.primary: Start trial < < nav
<nav id="main-nav"> <div class="jotl-brand"> Streamline</div> <ul> <li><a href="/"> Home</a></li> <li><a href="/docs"> Docs</a></li> <li><a href="/pricing"> Pricing</a></li> </ul> <button class="primary"> Start trial</button> </nav>
§ 06

Tables and forms

The places where HTML is most verbose. JOTL cuts the line count roughly in half while making the visual structure match the data structure.

Tables by shape

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.

JOTL
HTML
Rendered
> table: > columns: - [key=name label="Name"] - [key=role label="Role"] - [key=email label="Email"] < > row: - Alice Chen - Engineer - alice\@co.io < > row: - Ben Torres - PM - ben\@co.io < < table
<table> <thead><tr> <th>Name</th> <th>Role</th> <th>Email</th> </tr></thead> <tbody> <tr> <td>Alice Chen</td> <td>Engineer</td> <td>alice@co.io</td> </tr> <tr> <td>Ben Torres</td> <td>PM</td> <td>ben@co.io</td> </tr> </tbody> </table>
Name Role Email
Alice ChenEngineeralice@co.io
Ben TorresPMben@co.io

Forms with void fields

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.

JOTL
HTML
Rendered
> form [id=contact]: !> field [ type=text label="Your name" name=name required ] !> field [ type=email label="Email" name=email required ] !> field [ type=textarea label="Message" name=msg rows=4 ] > button.primary [type=submit]: Send < < form
<form id="contact"> <label>Your name<input type="text" name="name" required></label> <label>Email<input type="email" name="email" required></label> <label>Message<textarea name="msg" rows="4"></textarea> </label> <button type="submit" class="primary"> Send</button> </form>
§ 07

Text, Markdown-style

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.

JOTL
HTML
Rendered
> text: This paragraph has **bold text**, _italic text_, and `inline code`. < % fenced code block — opaque ```typescript function greet(name: string) { return `Hello, ${name}!` } ```
<p>This paragraph has <strong>bold text</strong>, <em>italic text</em>, and <code>inline code</code>. </p> <pre><code class="language-typescript" >function greet(name: string) { return `Hello, ${name}!` }</code></pre>

This paragraph has bold text, italic text, and inline code.

function greet(name: string) {
  return `Hello, ${name}!`
}
Fenced code is opaque

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.

§ 08

Links, three ways

A short form for inline links in prose, a full inline form for complex link content, and a block form for wrapping whole sections.

JOTL
HTML
Rendered
% short form — inline prose links > text: Read the #'docs'[url="/docs"] and see the #'changelog'[url="/log"]. < % full inline form — icon + text /> link [url="/profile"]: /> icon [name=user]: </ Profile </ % block form — wrapping a card > link [url="/intro"]: > card.elevated: > heading.3: Introduction < < < link
<p>Read the <a href="/docs"> docs</a> and see the <a href="/log"> changelog</a>.</p> <a href="/profile"> <i class="icon" data-icon="user"> </i> Profile </a> <a href="/intro"> <div class="jotl-card elevated"> <h3>Introduction</h3> </div> </a>
§ 09

Directives — head, style, script

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.

JOTL
HTML
Rendered
>> meta [ title="My page" description="A description" icon="/favicon.svg" ] >> style [type=css ref="/style.css"] >> import [from="./button.jot"]: Button >> document: > heading.1: My page < > Button: Click me < << document
<!DOCTYPE html> <html> <head> <title>My page</title> <meta name="description" content="A description"> <link rel="icon" href="/favicon.svg"> <link rel="stylesheet" href="/style.css"> </head> <body> <h1>My page</h1> <button>Click me</button> </body> </html>
Browser tab
My page

My page

The colon rule, applied uniformly

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.

§ 10

Comments and escaping

JOTL uses % for comments — not // or # — and \ to escape any sigil. Simple, consistent, unusual in a good way.

JOTL
HTML
Rendered
% a single-line comment %{ A block comment can span multiple lines. Block comments don't nest. %} > text: To show a literal sigil, escape it. Use \*asterisks\* for **bold**. The \& character escapes a sigil, \_ escapes underscore. <
<!-- comments are stripped at compile time --> <p>To show a literal sigil, escape it. Use *asterisks* for <strong>bold</strong>. The &amp; character escapes a sigil, _ escapes underscore. </p>

To show a literal sigil, escape it. Use *asterisks* for bold. The & character escapes a sigil, _ escapes underscore.

§ 11

Responsive, built in

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.

Live responsive preview · drag to resize Loading…
> grid [ cols=1 ^(500px)=2 ^(1024px)=4 gap=sm ^(768px)=md ]: > card: A < > card: B < > card: C < > card: D < > card: E < > card: F < > card: G < > card: H < <
Drag the orange handle on the right edge to resize

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:

JOTL
HTML + CSS
At 900px+
> group [ layout=column layout^(900px)=row gap=md gap^(900px)=xl ]: > article: Main < > aside: Related < <
<div data-jotl-r="r1" class="jotl-group"> <article>Main</article> <aside>Related</aside> </div> <style> [data-jotl-r="r1"] { --layout: column; --gap: md; } @media (min-width: 900px) { [data-jotl-r="r1"] { --layout: row; --gap: xl; } } </style>
Main
The article's main column.
Layout versus style

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.

Versus Tailwind

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.

§ 12

Components & references

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.

Three reference sigils

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.

JOTL source
After evaluation
Rendered
>> script [type=typescript]: { let count = 0 let users = [ { id: 1, name: "Alice" }, { id: 2, name: "Ben" } ] increment () { count = count + 1 } } << >> document: > text: Count: &count < > button.primary [on_press=@increment]: Add one < ~ for (&users as $user) > card: $user.name < ~< << document
// after count = 3 and // two iterations: <p>Count: 3</p> <button class="primary" onclick="increment()"> Add one </button> <div class="jotl-card"> Alice </div> <div class="jotl-card"> Ben </div>

Count: 3

Alice
Ben
The three-sigil discipline

&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.

§ 13

Idiomatic style

A collection of small conventions that make JOTL pleasant to read. Follow them when you can; violate them when you have a reason.

Use variants for sub-types, attributes for properties

> heading.2 is idiomatic. > heading [level=2] works but reads awkwardly. Variants express identity; attributes express metadata.

Use named closers on long blocks

< section is useful when the opening > section [id=hero]: is twenty lines above. For three-line blocks, bare < is fine.

Break long attribute lists onto their own lines

When you have more than three attributes, or any value is long, multi-line formatting is the norm. Readability wins over compactness.

Prefer unquoted values when possible

type=email reads better than type="email". Quotes are for strings with spaces or special characters.

Keep each concern in its own directive

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.

Write mobile-first

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.

Use responsive overrides for layout, not appearance

Changing columns at different viewport sizes is right. Changing colors at different viewport sizes is probably wrong — reach for a theme system instead.

§ 14

HTML → JOTL cheat sheet

A quick translation table for people moving between the two. Bookmark this page and come back when you've forgotten how something maps.

Document structure

HTML
JOTL
<!DOCTYPE html>
(compiler generates)
<html> / </html>
(compiler generates)
<head>...</head>
>> meta, >> style, >> import
<body>...</body>
>> document: ... <<

Block elements

HTML
JOTL
<h1>Text</h1>
> heading.1: Text <
<h2>Text</h2>
> heading.2: Text <
<p>Text</p>
> text: Text <
<section id="x">...</section>
> section [id=x]: ... < section
<ul><li>A</li></ul>
> list: - A <
<ol><li>A</li></ol>
> list.number: - A <
<nav>...</nav>
> nav: ... < nav
<button class="primary">Go</button>
> button.primary: Go <
<div class="card">...</div>
> card.elevated: ... <

Inline elements

HTML
JOTL
<strong>bold</strong>
**bold** or /> strong: bold </
<em>italic</em>
_italic_ or /> em: italic </
<code>x</code>
`x` or /> code: x </
<a href="/x">text</a>
#'text'[url="/x"]
<span class="x">text</span>
/> span [style=x]: text </

Void elements

HTML
JOTL
<img src="/x.png" alt="y">
!> image [src="/x.png" alt=y]
<br>
!> break
<hr>
!> divider
<input type="email">
!> field [type=email]

Responsive — unique to JOTL

Tailwind
JOTL
class="grid-cols-1 md:grid-cols-2 lg:grid-cols-4"
cols=1 ^(768px)=2 ^(1024px)=4
class="flex-col md:flex-row"
layout=column ^(768px)=row
class="gap-2 md:gap-4 lg:gap-6"
gap=sm ^(768px)=md ^(1024px)=lg
§ 15

CLI & tooling

JOTL ships as a small command-line compiler, a development server with live reload, and a syntax-highlighting extension for VS Code and Cursor.

Install the compiler

The compiler is published to npm as jotl. Install it globally to get the jotl command on your PATH.

Goal
Command
Install globally
npm i -g jotl
Compile one file
jotl compile page.jot -o page.html
Watch a folder
jotl watch src/ -o build/
Local dev server
jotl serve page.jot --port 4321
Print version
jotl --version

Develop with jotl serve

Pointing 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.

What gets served at /

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.

Editor support

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.

Editor
Install command
VS Code
code --install-extension jotl.jotlang-language
Cursor
cursor --install-extension jotl.jotlang-language
Either, manually
open the .vsix file from the marketplace

Default styles

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.

How you use it
What it does
(do nothing)
The preset auto-injects as the first <style> in <head>
>> style [preset=base]
Same as above, just signposted in source
>> style [preset=none]
Opt out — no preset CSS is injected
>> style [ref="/jotl.css"]
Link your own copy (a fork from node_modules/jotl/styles/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.

Forking the preset

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.

Project layout

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.

Path
What's inside
packages/compiler/
jotl — parser, codegen, CLI
packages/lsp/
jotlang-lsp — diagnostics & completions
packages/vscode-extension/
jotlang-language — TextMate grammar & client
packages/compiler/tests/examples/
Source fixtures, one per feature
packages/compiler/tests/expected/
Golden HTML snapshots used in CI
Where to start

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.

§ 16

Reactivity with Solid

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.

What it is

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 component-mode file

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.

>> component Counter: >> props: { initial: number } >> script [type=typescript]: { import { signal } from "solid-js" let count = signal(props.initial) increment () { count = count() + 1 } reset () { count = 0 } } << >> document: > heading.1: Counter < > text: Clicks: &count < > button [on_press=@increment]: Add < > button [on_press=@reset]: Reset < << document << component

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.

Explicit reactivity

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.

You write
Compiler emits
let count = signal(0)
const [count, setCount] = createSignal(0)
let total = memo(() => a() + b())
const total = createMemo(() => a() + b())
let user = resource(fetchUser)
const [user] = createResource(fetchUser)
effect(() => console.log(count()))
createEffect(() => console.log(count()))
count = count() + 1
setCount(count() + 1)

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.

Control flow lowers to Solid components

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.

Use it in Vite

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.

// vite.config.ts import { defineConfig } from 'vite' import solid from 'vite-plugin-solid' import solidJotlang from 'solid-jotlang/vite' export default defineConfig({ plugins: [ solidJotlang(), solid({ extensions: ['.jot'] }), ], })
// src/main.tsx import { render } from 'solid-js/web' import { Counter } from './Counter.jot' render(() => <Counter initial={0} />, document.getElementById('root')!)
When to pick which compiler

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.

JOTL

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.

Typography Fraunces & Inter Tight Code JetBrains Mono Styling Tailwind CSS v3 Version tutorial 0.5.0