QFlowLearn: QTI 3.0 authoring
A Native TypeScript QTI 3 Player
May 20, 2026 Open Source

A Native TypeScript QTI 3 Player

A strict TypeScript QTI 3 runtime and native Web Component player with typed models, response processing, scoring, saved state, fixtures, and Playwright-tested rendering.

As QFlowLearn added more QTI 3 question types, it needed a player that could support the full QTI 3 interaction set.

Once the authoring environment needed to support every defined item in the official spec, the player requirements became clear: native Web Components, zero runtime dependencies in the core package, good tests, and strict TypeScript. That became longsightgroup/qti3.

React and Vue are useful frameworks, but they are heavy for a reference item player, and they wrap implementation choices around their own component model. We wanted parsing, validation, response processing, rendering, scoring, and saved-state behavior to be readable without a framework runtime.

QTI 3 is a contract

QTI 3 gives assessment content a concrete contract. It covers item structure, response declarations, response processing, shared CSS vocabulary, accessibility metadata, Personal Needs and Preferences (PNP), package relationships, and results reporting expectations. When an implementation gets those details right, content can move between systems without being rewritten around one vendor’s player.

Where TypeScript helps

The XML import boundary is where TypeScript matters most. XML arrives as strings: element names, attributes, response identifiers, cardinalities, base types, coordinates. The parser still has to validate those at runtime; TypeScript is not a schema validator. But once the XML is translated into a QTI model, the rest of the code can stop passing anonymous nodes around. A response declaration has a known cardinality and base type. An interaction has a known QTI interaction type. Body content is text, an element, an interaction, feedback, or a printed variable. That gives the renderer, response processor, support matrix, and saved state code the same model to work from.

A small core API

This is the kind of core API we want:

import {
  createItemSession,
  parseQtiXml,
  validateAssessmentItem,
} from "@longsightgroup/qti3-core";

const parsed = parseQtiXml(xml);
if (!parsed.ok || !parsed.document) {
  throw new Error(parsed.diagnostics.map((item) => item.message).join("; "));
}

const validation = validateAssessmentItem(parsed.document);
const session = createItemSession(parsed.document);

session.respond("RESPONSE", "A");
const result = session.score();

console.log(validation.diagnostics);
console.log(result.outcomes);
console.log(result.state);

Learning from existing work

Amp-up’s Vue 3 QTI player has been a useful reference. We have learned from its rendering behavior, response processing, PNP/session control, catalog events, and the messy places where a clean XML spec turns into a real question on a screen.

Studying it helped clarify where longsightgroup/qti3 could take a different path: framework-neutral packages, core logic that runs without browser DOM APIs, and a native Web Component player. Imports, validation, response processing, scoring, fixtures, and saved-state checks can run in CLIs, CI, Node, or Deno without rendering an item first.

PNP belongs in core

QTI accessibility is not only a rendering concern. A host may need to know that an item contains catalog cards, companion materials, Data-SSML, text-to-speech metadata, or shared vocabulary classes such as qti-keyword-emphasis before a Web Component is ever defined.

That is why the core package now carries more of the PNP-adjacent surface area. The parser preserves catalog and companion-material metadata. The shared vocabulary registry can tell authoring tools and delivery hosts which classes are understood, which are conditional, and which need host policy. The Data-SSML and text-to-speech helpers give the same model to server-side validation, authoring previews, and browser rendering.

Why now

It also lands at a good time for JavaScript. More packages are ESM first. TypeScript is the default for serious library APIs. Vitest makes fast unit tests normal. Playwright makes browser behavior testable without a separate Selenium-era apparatus. Tools like oxlint and oxfmt help for the same practical reason: fewer slow toolchains, fewer plugins, and fewer decisions that have to be rediscovered by every project.

The browser side should stay just as explicit:

import { defineQtiAssessmentItemPlayer } from "@longsightgroup/qti3-player";

defineQtiAssessmentItemPlayer();

const player = document.querySelector("qti-assessment-item-player");

await player?.loadXml(xml, {
  status: "interacting",
  sessionControl: {
    validateResponses: true,
    showFeedback: true,
  },
});

The public manual harness is the fastest way to try the current player against fixture items without cloning the repository.

Keeping the stack small

As of @longsightgroup/qti3-core 0.9.1, the core package has zero third-party runtime dependencies. XML parsing is handled inside the package by a small dependency-free parser that records source ranges for redaction, does not resolve external entities, does not process DTD entity declarations, and does not touch the network or filesystem.

That matters for the trust boundary. A QTI runtime should be easy to audit, easy to run in Node, Deno, browsers, and edge runtimes, and boring to install in locked-down environments. The core should not need a browser framework, an HTTP client, an alert library, a drag-and-drop package, or a general-purpose XML dependency. If a runtime dependency enters, it should make conformance, security, accessibility, or maintenance measurably better.

The development dependency list is still pragmatic: TypeScript, Vite for the manual harness, Vitest, Playwright, axe-core, oxfmt, and oxlint. axe-core is there to catch accessibility regressions early in CI. It is not the whole accessibility story, so the support matrix also needs keyboard, focus, forced-colors, and manual assistive-technology evidence.

What is next

Recent releases have focused on public fixtures, package metadata, release checks, dependency-free XML parsing, delivery-safe XML redaction, server-side scoring, shared-vocabulary authoring helpers, PNP-adjacent metadata, inline gap-match rendering, match rows, select-point and position-object behavior, point coordinate tolerances, and less brittle graphical interaction tests.

Certification is next. Before we submit, the support matrix, fixture suite, browser tests, package inspection, and accessibility checks need to match the claims. We also need more manual accessibility testing and better ergonomics for drag and graphical interactions. The repo is public at github.com/LongsightGroup/qti3.

Related Articles

Sakai 25 Patch Highlights: February 2026
February 27, 2026 Sakai

Sakai 25 Patch Highlights: February 2026

February’s Sakai patches focus on grading edge cases, more dependable quiz behavior, and a round of smaller usability fixes that make the platform feel steadier day to day.

Work with Longsight

Bring us the problems that cross your LMS, identity, and compliance systems.

We can host the service, help you run it on campus, answer security review, integrate with your LMS, and work through accessibility requirements.