SSOT Template Markup Language — framework-independent UI declaration.

Frontend code mixes two things: your decisions and framework wiring. Which API to call, which fields to show, in what order, under what conditions — these decisions get buried inside React hooks or Vue composables. When you switch frameworks or refactor, decisions get reinterpreted or lost.

STML separates them. Decisions stay in standard HTML with data-* attributes. Framework wiring is generated or delegated.

GitHub Repository

Structure

┌─────────────────────────────────┐
│  STML (specs/)                  │  Your decisions. Framework-independent.
│  What to fetch, show, submit    │  Survives any rewrite.
├─────────────────────────────────┤
│  Codegen / LLM                  │  Generates framework wiring.
│  React, Vue, Svelte, anything   │  Replaceable.
├─────────────────────────────────┤
│  Runtime (artifacts/)           │  React TSX, Vue SFC, etc.
│  Hooks, state, rendering        │  Generated. Do not edit.
└─────────────────────────────────┘

What Gets Preserved

Everything in STML HTML is a user decision:

  • Which API endpoints to call (data-fetch, data-action)
  • Which fields to display or collect (data-bind, data-field)
  • What order elements appear (DOM structure)
  • What it looks like (Tailwind classes, HTML tags)
  • What text users see (headings, placeholders, button labels)
  • What conditions control visibility (data-state)
  • What components handle special UX (data-component)
  • How lists behave — pagination, sorting, filtering

None of this is React. None of this is Vue. It’s what your page does, declared in standard HTML5.

Example

<main class="max-w-4xl mx-auto p-6">
  <h1 class="text-2xl font-bold mb-6">My Reservations</h1>

  <section data-fetch="ListMyReservations"
           data-paginate data-sort="StartAt:desc" data-filter="Status">
    <ul data-each="reservations" class="space-y-3">
      <li class="flex justify-between p-4 border rounded">
        <span data-bind="RoomID" class="font-semibold"></span>
        <span data-bind="Status" class="text-sm text-gray-500"></span>
      </li>
    </ul>
    <p data-state="reservations.empty" class="text-gray-400">No reservations</p>
  </section>

  <div data-action="CreateReservation">
    <input data-field="RoomID" type="number" placeholder="Room number" />
    <button type="submit">Reserve</button>
  </div>
</main>

The same file can produce different targets:

TargetHow
React TSXstml gen (built-in deterministic codegen)
Vue SFCGive STML + OpenAPI to LLM: “implement in Vue”
SvelteGive STML + OpenAPI to LLM: “implement in Svelte”
FlutterGive STML + OpenAPI to LLM: “implement in Flutter”

Decisions are preserved. Only the framework wiring changes.

Validation

The built-in validator cross-checks STML against OpenAPI before any code is generated:

  • Does the operationId exist? Is the HTTP method correct?
  • Do request fields, response fields, and parameters match the schema?
  • Are sort/filter/include columns within the allowed lists?
  • Do referenced components exist?

Catches frontend-API mismatches at CI time, not at runtime.

stml validate specs/my-project    # 12 symbolic checks against OpenAPI

data-* Attributes

AttributeWhat it declares
data-fetchLoads data from a GET endpoint
data-actionSubmits data to a POST/PUT/DELETE endpoint
data-fieldCollects a request body field
data-bindDisplays a response field
data-param-*Operation needs a path/query parameter
data-eachRepeats over an array field
data-stateShows conditionally
data-componentDelegates to a custom component
data-paginatePaginated list
data-sortSortable list (default column and direction)
data-filterFilterable list (which columns)
data-includeIncludes related resources

License

MIT — GitHub