.tibi file format
.tibi file. The single-file source of every page and every component. Read this if you're hand-authoring (or letting an LLM author) .tibi sources — the parser is strict about the wrapper blocks and the script attribute, and a missing wrapper is the most common authoring mistake.The rules in one block
- Required: exactly one
<template>…</template>block. Everything you want rendered as HTML lives inside it. A file without<template>fails to compile (missing required <template>…</template> block). - Optional: at most one
<style>…</style>block — scoped CSS for the component. - Optional: at most one
<script>…</script>block. Plain<script>= passthrough;<script tibi>opts into the reactive surface (state/computed/effect/ template directives). Only code scripts count:<script src>, data scripts (type="application/json"and similar) and scripts in the<head>block are passed through as written. - Optional: a
<head>…</head>block, injected into the page's<head>as written:<title>,<meta>,<link>, external<script src>. In a_layout.tibiit applies to every page; a page's own head comes first. - Script attributes:
lang="ts"writes the script in TypeScript, and scripts mayimportnpm packages and local modules. See Client modules. - Value props:
<Child .items="list" .onpick="pick"/>passes objects, arrays and functions instead of strings. See Shared state. - Template attribute:
<template shadow="none">renders a component into the light DOM instead of a shadow root (for editors, popovers, global CSS). - Optional: a frontmatter block opening the file, fenced by
---. Tag the fence to pick the language:---js,---ts,---py,---rs(or bare---= Rust default).
Block order
If present, blocks appear in this order top-to-bottom: frontmatter → <template> → <style> → <script>. The parser doesn't strictly enforce the relative order of template / style / script (it pulls each by tag), but conventional order keeps files diff-friendly.
Minimal valid page
The smallest legal .tibi file. Put it under pages/ and the file-based router serves it.
Page with style
Component with props + slot
Components live under components/ at the project root (sibling of pages/). Use them from any template with an uppercase tag — <Greeting name="Alice"/>. Each compiles to a real <tibi-{kebab-tag}> Web Component with declarative shadow DOM, so styles never leak.
String attributes flow in via {name} tokens (HTML-escaped at substitution time). Children passed in the paired-tag form (<Greeting>…children…</Greeting>) project through <slot/>.
Interactivity: <script tibi>
The single most common authoring mistake is using reactive helpers without the tibi attribute on the script tag. The compiler only emits the state / computed / effect / text / attr / show / bind / onMount / onUnmount / emit prelude when the opening tag is literally <script tibi>. A plain <script> tag with reactive code throws ReferenceError: state is not defined at runtime.
Template directives the compiler rewrites at build:
{{expr}}— reactive text interpolation.@event="handler"— event listener (auto-removed on unmount).:attr="expr"— reactive attribute (set / remove on truthiness).:classmerges with a staticclassand accepts a string, an array or{ name: condition }, insidet-foritems too.t-show="expr"— togglesdisplay.t-bind="ref"— two-way input binding.t-if/t-else-if/t-else— conditional sibling chain. Inactive branches are kept out of the first paint with an inlinedisplay:none(so authordisplayrules can't reveal them) and removed from the DOM once the script runs.t-for="item of list"— list render. Rows can hold{{ }},:attr,t-show,@event,.propbindings and furthert-forloops; in a nested loop, handlers and bindings see the inner item andidx.quid="name"— surfaces the element as$nameinside the script.
Component props at runtime
<script tibi> exposes a reactive props proxy that reads the host element's attributes. Pair with effect / computed to react when the parent flips an attr:
Reads tracked by effect re-fire when attributeChangedCallback bumps internally — no manual subscribe needed. props.has('flag') mirrors el.hasAttribute. A camelCase name also reads its kebab-case attribute (props.sectionId reads section-id, since HTML lowercases attribute names), and a prop that isn't set is undefined, so props.x ?? fallback works. Templates read a bare prop name as props.name, unless the script declares its own variable of that name, which then wins (as in plain JavaScript). All values are HTML attribute strings; the helpers (str / num / bool / json) coerce. props.str('name') is identical to props.name — just there for symmetry with the typed accessors.
Plain <script> (no tibi attr)
For escape-hatch DOM code or third-party libraries that don't need the reactive runtime. Tibi runs the script once with el bound to the host element and root bound to its shadow root (component scope only). Query into the shadow with root.querySelector(...).
Frontmatter
Open a file with ---<lang> to drop a compile-time script. Its top-level result object's keys become {token} substitutions in the template that follows. See Frontmatter for the full per-language contract.
Component shape: full example
Every section a component can carry — frontmatter, template, style, reactive script — in one file.
Server channels via __channels
Components subscribe to server-pushed channels by declaring them in their own frontmatter via a reserved __channels key. Tibi picks them up at boot, spawns one tick task per channel, and the component subscribes client-side.
For agents / LLMs: validate via MCP
The bundled MCP server exposes two tools tailored to remote authoring:
tibi_file_format— returns this spec as text. Call it before hand-writing a.tibifile.tibi_validate— accepts a{ "source": "<raw .tibi contents>" }argument and returns eitherOKor one issue per line. Runs the real parser plus a heuristic that flags the most common LLM mistake: usingstate/effect/onMountin a<script>tag that's missing thetibiattribute. More than one code<script>is an error, since only the first would run.
Common mistakes
- HTML at the top level, no
<template>wrapper. The parser refuses the file. Wrap everything in<template>…</template>. - Reactive code in a plain
<script>. Add thetibiattribute:<script tibi>. Otherwisestate/effect/onMount/ etc. aren't in scope. - Frontmatter opened but not closed. Every
----fenced block needs a matching---on its own line. - Lowercase component tag.
<greeting/>is a regular HTML element; only uppercase tags (<Greeting/>) resolve to.tibifiles. - Component file outside
components/. The resolver walks up looking for the first ancestor with acomponents/subdir — files nested elsewhere don't get picked up. - Multiple
<template>/<style>/<script>blocks. Each file has at most one of each.