Skip to content
SemText

Semantic markup that reads three ways.

SemText is a markup vocabulary for HTML documents. You write what the content is — a fact, a step, a caveat, a property — and the same file renders as a styled interactive page in a browser, extracts as structured records for a language model, and prints as annotated text in a terminal.

A document is one standalone .html file. Double-click it. There is no server, no build step and no framework to install.

See it work Read the spec Open the demo

This page is itself a SemText document. Everything below is the real vocabulary, rendered by the real bundles. View source.

Why this exists

One document, three readers who want different things.

A rich document usually ends up as three artifacts that drift: a page for people, a JSON export for tooling, and a plain-text dump for anything without a browser. SemText's bet is that one file can serve all three, if the markup says what each piece of content means instead of how it should look.

Pick a reader to see what they get from the same file.

The theme CSS styles the vocabulary directly, so a document is readable the moment it opens. Flashcards flip, quizzes score, tabs switch and disclosures expand — driven by roughly 7 KB of vanilla JavaScript with no dependencies and no network calls.

The same file read as XHTML. Attributes qualify every datum: data-kind says what a thing is, data-status says where it stands, .sem-conclusion and .sem-distractor distinguish an answer from a decoy. A model does not have to infer the structure, because the structure is written down.

extractText(document) walks the authored markup and returns annotated lines — one per record, indented by containment, tagged with ids and kinds. Generated chrome is skipped, so the output does not depend on whether scripts ran or which view is on screen.

The idea, in one control

Change the view. The content does not move.

These four facts are authored once, as div.sem-fact elements holding a .sem-statement and a .sem-conclusion. data-view-as selects how they render. Switching it rewrites nothing about what they are, and the records a machine reads stay identical.

Rendering as list. In flashcard view, click a card to flip it; in quiz view, pick the conclusion.

What a person sees

view-as is a parameter, not an identity
A fact is a fact in every view; the attribute only selects a rendering.
Each view is a different element type.
Components render into light DOM
Content stays in the page, so find-in-page, copy and plain CSS all still reach it.
Content moves into a shadow root for encapsulation.
Extraction reads the authored document
Generated chrome is skipped, so records do not depend on the view on screen.
Extraction reads whatever is currently rendered.
Authoring needs no build step
Two stylesheets and one script tag in a plain HTML file.
A bundler compiles the vocabulary down to HTML.

What a machine reads

sem-facts (#try-deck kind=capability)
  sem-fact (#f-view-as kind=rule): view-as is a parameter, not an identity :: A fact is a fact in every view; the attribute only selects a rendering.
    distractors: Each view is a different element type.
  sem-fact (#f-light-dom kind=rule): Components render into light DOM :: Content stays in the page, so find-in-page, copy and plain CSS all still reach it.
    distractors: Content moves into a shadow root for encapsulation.
  sem-fact (#f-invariant kind=rule): Extraction reads the authored document :: Generated chrome is skipped, so records do not depend on the view on screen.
    distractors: Extraction reads whatever is currently rendered.
  sem-fact (#f-no-build kind=rule): Authoring needs no build step :: Two stylesheets and one script tag in a plain HTML file.
    distractors: A bundler compiles the vocabulary down to HTML.

Output of SemTextExtract.extractText() over the deck above, authored into the page so it is here with scripts off too.

Degradation

Three tiers, and the bottom one is the contract.

Every hide rule in the vocabulary CSS is gated on an enhancement marker that only a script sets. With no script, nothing is hidden and the document reads as prose. Each tier above that adds behaviour without moving content.

Tier 1 — CSS only
Two stylesheets render every element readably. Quiz answers, collapsed notes and inactive views all stay on the page, because the rules that would hide them never match.
Tier 2 — the fallback script
About 7 KB of vanilla JavaScript wires flashcards, quizzes, tabs, disclosure and meters. No dependencies, no network, no build. It is the tier a file you email still has.
Tier 3 — Lit components
When the component bundle loads, elements claim data-sem-upgraded, drop the fallback marker and take over in place. Content is never re-parented into a shadow root.

The notes further down this page are <sem-note> custom elements running tier 3. The deck above is running tier 2.

Quick start

What you actually write.

A SemText document is an HTML file. The vocabulary is classes on plain elements, so any editor, template language or generator that emits HTML can emit it.

<!doctype html>
<html lang="en" data-sem-theme="minimal-tech-light">
<head>
  <meta charset="utf-8">
  <title>Auth notes</title>
  <link rel="stylesheet" href="semtext/themes/_vocabulary.css">
  <link rel="stylesheet" href="semtext/themes/minimal-tech-light.css">
  <script src="semtext/semtext-fallback.js"></script>
</head>
<body>
<div class="sem-enhanced-document">

  <h1>Auth notes</h1>

  <div class="sem-note" data-variant="warning">
    Refresh tokens rotate per use — each exchange retires the last one.
  </div>

  <div class="sem-facts" data-view-as="flashcards">
    <div class="sem-fact" id="f-jwt" data-kind="concept">
      <div class="sem-statement">JWTs rotate per session</div>
      <div class="sem-conclusion">Short-lived access tokens; the refresh grant issues a new pair.</div>
      <div class="sem-distractor">Store the token in localStorage for durability.</div>
    </div>
  </div>

</div>
</body>
</html>

Save it, double-click it. That is the whole workflow.

Adding it to a project

Copy themes/_vocabulary.css and one theme file next to your document, and set data-sem-theme on <html> to that theme's name.
Add semtext-fallback.js. Link it, or paste it inline for a document you intend to send as a single file.
Write the content with sem-* classes where interactivity or machine-readability pays. Ordinary HTML stays ordinary HTML.
Optional: add semtext.js for the Lit tier, and semtext-extract.js where a tool needs the records.

Surface

Everything that ships.

Three independent classic scripts, each installing one global, plus two stylesheets. They are separate on purpose: the fallback tier has to run in a document that never loads Lit.

Lit 3 components · global SemText · 24.4 KB minified, 8.9 KB gzipped
Vanilla interactivity, no dependencies · global SemTextFallback · 7.1 KB minified, 2.6 KB gzipped
DOM to records and annotated text · global SemTextExtract · 6.6 KB minified, 2.5 KB gzipped
Structure for the sem-* classes. Reads --sem-* tokens, declares none.
Token values only, plus a prefers-color-scheme block. A new theme is a copy with new values.

The vocabulary

Fourteen element types mint records: sem-agent, sem-note, sem-facts and sem-fact, sem-details and sem-detail, sem-procedure and sem-step, sem-properties and sem-property, sem-views and sem-view, sem-reveal, and sem-progress. This page mints records for eleven of them; the three it leaves out are sem-details, sem-detail and sem-progress, because nothing here is an occlusion exercise and there is no honest number to meter.

SemText Guide
Answers questions about SemText from the facts on this page.
Answer only from sem-fact records in this document; cite ids.

A sem-agent block, above: document-level metadata aimed at a model, carried by the same file a person is reading. It is inert to a browser and it is a record to an extractor.

Scope

What it is not.

SemText is a markup vocabulary and three small scripts. It is not a CMS, a site generator or an application framework, and it has no opinion about how you produce the HTML.
The spec is a draft at v0.4. The class-based vocabulary is the current baseline and the custom-element form is arriving alongside it, so element names and attributes can still change. Pin a version.
There is no React in the dependency tree and there is not going to be one. Lit 3 is the only runtime dependency, and it is optional — the fallback tier has none.

Reasonable questions

Markdown has nowhere to put the attributes that make a datum machine-readable — no way to mark a conclusion as distinct from a decoy, or a step as blocked. An authoring aid that compiles Markdown into SemText is a reasonable thing to build, but the canonical artifact stays XHTML, because that is the artifact all three readers consume.
A model can read any HTML. What it cannot do reliably is tell a conclusion from a distractor, or a caveat from a heading, when nothing in the markup says so. The claim here is narrow: the attributes remove that guesswork, and the extraction contract makes the result stable enough to cite.
Not yet. This is a draft spec, a reference implementation and a Cypress suite. There are no users to point at, no benchmarks to quote and no production deployments beyond this page and its demos.