Skip to content

DSL Syntax

TokUI DSL is minimal: brackets wrap one component. The first token is the type, then key:value attributes, trailing text is content.

Basics

text
[h1 Title]                                    ; self-closing
[card tt:Title] content [/card]               ; container, must close with [/type]
[btn tx:"Click me" v:primary clk:onClick]     ; space-separated attributes
[tr Alice,25,NYC]                             ; comma-separated values
ph:"value with spaces"                        ; quote values with spaces
v:"primary,sm"                                ; comma-separated variants
  • Containers must close with [/type]; self-closing tags hold no children.
  • Boolean attributes need no value: req, stripe, dis.
1[btn tx:Primary v:primary]
2[btn tx:Danger v:danger]
3[btn tx:Ghost v:ghost]
4[btn tx:Pill v:"primary,pill"]
Loading TokUI…
TokUI DSL · code ↔ render

Common attributes

ShortMeaningShortMeaning
idelement id (also upd target)tttitle
txtextllabel
phplaceholderuurl
ssrc / sourcenname
vvalue / variantactaction
mtdmethodclkonclick handler
subonsubmit handlerdisdisabled
ro/req/chkreadonly/required/checkedw/h/bg/fcwidth/height/bg/color

clk: / sub: handlers must be pre-registered via TokUI.registerHandler(name, fn) — the server ships no executable code.

Boolean attributes

Just the key, no value:

stripe dis ro req chk multi auto plain round closable bordered open
pill dot leaf inline rounded container

Variants

v:primary generates class tokui-{type}--primary. Combine with commas: v:"primary,sm,pill". Unknown variants are silently dropped (whitelist).

ComponentAllowed variants
btnprimary danger success warning ghost sm lg pill block
cardhighlight flat bordered center right
h1~h6left center right ribbon underline badge
pleft center right muted bold sm lg
1[h1 v:underline Decorated title]
2[p v:muted Muted text]
3[p v:bold Bold text]
4[dv v:dashed Divider with text]
Loading TokUI…
TokUI DSL · code ↔ render

Badging a heading: h3 and other headings are self-closing — never nest [h3 text [badge]]. Two official patterns — inline pill via row v:inline (flex; must use v:inline — the default row is a 12-col grid that squeezes the heading): [row v:inline][h3 SaaS Pro][badge tx:v2.4 pill][/row]; top-right corner badge via badge-box: [badge-box tx:v2.4][h3 SaaS Pro][/badge-box]. Use tx for versions/decimals (count runs through parseInt and truncates 2.4 to 2). badge-box text badge uses tx (legacy label still works).

Dynamic update

[upd] updates an already-rendered component:

text
[progress id:prog v:0 l:Progress]
[upd id:prog v:50]
[upd id:prog v:100 status:success]
1[progress id:prog v:0 l:Progress]
2[upd id:prog v:50]
3[upd id:prog v:100 status:success]
Loading TokUI…
TokUI DSL · code ↔ render

[upd] shines in async incremental updates: the server first sends [progress id:prog v:0], then pushes [upd id:prog v:50] as it advances. Click ⚡ Stream at the bottom-right to replay token by token — the bar jumps 0% → 50% → 100% live.

Component categories

The 150+ components are organized into seven categories, each with full prop tables and editable examples:

CategoryRepresentative componentsDocs
Basicheadings, buttons, tags, callouts, progress, stats, Markdown, codebasic
Forminput, select, switch, slider, rate, date, cascader, transfer, uploadform
Layoutcard, grid, tabs, collapse, drawer, dialog, timeline, treelayout
Data Displaytable, descriptions, pagination, badge, avatar, skeleton, result, emptydata
Chartbar, line, pie, radar, scatter, gantt, funnel (pure SVG, zero deps)chart
AI Chatbubble, tool-call, thought chain, diff, plan, terminal, sandbox, artifactai-chat
Showcasesignup form, CRUD, form+table linkage, report-style cardsshowcase

Raw content mode

Inside code, md, diff, terminal, sandbox, and artifact-code containers, [ is treated as a literal character (not parsed as a tag) until the matching [/type]. So brackets inside code snippets never break parsing.

The two modes of paragraph p (leaf vs container)

p is a dual-mode tag: it switches automatically based on whether there is body text inside the tag. Getting it wrong makes child nodes break out as siblings or body text disappear (same trap as card's tx self-closing).

  • Leaf mode (body text present): [p text], [p v:bold text]. The text becomes the paragraph body; it auto-closes when the next block-level sibling arrives (mirrors HTML <p>). The body stream may contain inline children — inline whitelist only: a, tag, b/strong, em, mark, spin, sub/sup, code.
dsl
[p First [a u:# register], then run [b npm install].]   ✅ inline children sit inside the body
  • Container mode (no body text, only attrs or empty): [p]...[/p], [p v:muted]...[/p]. It collects all children until [/p]. When a paragraph must hold block-level components like btn/form/card/list/table/another [p], use this mode and list children one per line.
dsl
[p]
[btn tx:Agree clk:agree]
[btn tx:Decline v:danger clk:reject]
[/p]

Common mistake — stuffing a block component into a leaf-mode body stream:

dsl
[p Click [btn tx:Submit] to continue.]   ❌ btn is not in the inline whitelist; leaf p auto-closes on [btn], which breaks out as a top-level sibling

Rule of thumb: paragraph holds btn/form/card/list/table or any container → container mode [p]...[/p]; holds a/tag/strong inline or plain text → leaf mode [p text]. Full inline whitelist: src/core/parser.js P_INLINE_CHILDREN.

1[p Leaf mode allows [a u:# tx:inline link]
2and
3[b inline bold]
4children.]
5[p]
6[btn tx:block btn in container clk:ok]
7[btn tx:another v:danger clk:cancel]
8[/p]
Loading TokUI…
TokUI DSL · code ↔ render

Body text shaped like word:value (e.g. Q: A:) is parsed as an attribute

For every token after [, the parser checks for an ASCII :: as long as the part before : is an English identifier (Q, A, step, note, id… all qualify), the whole key:value is treated as an attribute. So a "Q&A prefix" like this loses all its body text:

dsl
[p Q:How to submit?]            ❌ no space: Q becomes the attr name, the whole sentence its value — paragraph body empty, nothing renders
[p Q: How to submit?]            ❌ with space: the Q: prefix is eaten as an attribute, only "How to submit?" remains

This applies to any text component: item, h1~h6, callout's tt, etc. Three fixes:

dsl
[p Q:How to submit?]       ✅ full-width colon :(parser only matches ASCII :, full-width counts as text)
[p Q How to submit?]         ✅ drop the colon, fold the prefix into the body
[p "Q: How to submit?"]      ✅ wrap the whole body in double quotes (literal text)

For Q&A scenarios prefer the full-width or no prefix at all — don't write Q:/A: as a Markdown-style prefix into the DSL.

Text containing literal [ ] must be wrapped in double quotes

[ is the tag-start character. When the body text of a normal component (item, p, h1–h6, card title tt, etc.) contains a literal [ or ], it is misread as a nested child tag — the content gets truncated and the tail becomes an orphan node:

dsl
[item Math.random() returns [0, 1) float]   ❌ [0 parsed as a child tag, item body truncated to "Math.random() returns"

Fix: wrap the whole body in double quotes — brackets inside the quotes are kept literal and do not trigger tag parsing:

dsl
[item "Math.random() returns [0, 1) float"]
[item "array arr[0] and arr[1]"]              ✅
[p "coefficient range [0, 1) half-open"]      ✅

Note: text inside double quotes is treated as a literal string in full — any [tag] within is also no longer parsed as a child. If you need both a literal bracket and a real nested child, quote only the bracketed literal and keep the child tag outside the quotes.

Body text without [ ] needs no quoting.

Full reference

  • In-site: the seven category docs above — every component has a prop table and a live example.
  • Repo: full attribute tables and the container-type list live in TOKUI_DSL_REFERENCE.md.
  • Source of truth: containers in src/core/parser.js (CONTAINERS), boolean attrs in BOOLEAN_ATTRS, variant whitelist in src/core/renderer.js (VARIANTS).