qti3 is a dependency-light, framework-neutral TypeScript reference implementation for
QTI 3 assessment items. The core engine and CLI ship with zero third-party runtime dependencies.
Use the interactive qti3 manual to load reference fixtures, QTI packages, or item XML and inspect rendering, scoring, validation, state, diagnostics, and accessibility evidence. The shared vocabulary gallery provides runnable matrix fixtures for presentation-class review.
The project publishes packages on npm for parsing, validating, rendering, scoring, serializing, restoring, and testing QTI 3 items. The core has no UI framework dependency.
qti3 covers QTI item and question-type conformance. Host products provide runners, controllers, LMS shells, candidate attempt policy, analytics, proctoring, rostering, and gradebook integrations.
Longsight maintains qti3 as part of its open-source educational software work. The implementation also supports the open-standards direction used by QFlowLearn's QTI 3 assessment platform. Product-specific authoring and delivery remain outside this repository.
qti3 is item-focused: the core owns QTI semantics, the player renders one item at a
time, tooling proves conformance and accessibility behavior, and host products own the
surrounding assessment application.
flowchart LR
content["QTI item XML<br/>and package assets"]
host["Host product<br/>runner, policy, LMS shell, analytics"]
review["CI, release, and<br/>certification checks"]
subgraph qti3["qti3 packages"]
core["core<br/>parse, validate, process,<br/>score, serialize state"]
player["player<br/>native web component<br/>item renderer"]
fixtures["fixtures<br/>synthetic reference items"]
conformance["conformance<br/>fixture runner and<br/>support matrix"]
a11y["a11y<br/>keyboard and accessibility<br/>proof contracts"]
cli["cli<br/>validate, score, inspect,<br/>write fixtures"]
pnp["pnp<br/>normalize and resolve<br/>candidate preferences"]
writer["writer<br/>QTI-shaped authoring<br/>XML output"]
migrator["migrator<br/>QTI 1.2 and 2.x<br/>to QTI 3 authoring"]
transcoder["transcoder<br/>QTI 3 to versioned<br/>standard and LMS profiles"]
end
content --> core
content --> cli
content --> migrator
content --> transcoder
core --> player
core --> cli
core --> pnp
core --> writer
writer --> migrator
writer --> transcoder
fixtures --> conformance
fixtures --> cli
conformance --> review
a11y --> review
cli --> review
pnp --> host
writer --> host
migrator --> host
transcoder --> host
player --> host
core --> host
The parser stores each interaction in the normalized QtiInteraction model and validates its
response contract. The browser player selects a renderer from the response shape and interaction
model.
| Family | Interactions | Shared implementation |
|---|---|---|
| Identifier choices | Choice, Inline Choice, Hot Text, Hotspot | Choice parsing, identifier response checks, choice metadata |
| Ordered choices | Order, Graphic Order | Ordered identifier contract and reorder behavior |
| Pairing and matching | Associate, Match, Graphic Associate | Source/target choices, token controls, selected pair chips |
| Gap assignment | Gap Match, Graphic Gap Match | Source choices assigned to text or graphic gaps |
| Graphic and coordinate UI | Hotspot, Graphic Order, Graphic Associate, Select Point, Position Object | Responsive surfaces, object images, hotspot/point placement |
| Text responses | Text Entry, Extended Text | String response contract and text-entry renderer |
| File responses | Upload, Drawing | File response contract |
| Scalar and host-controlled | Slider, Media, End Attempt, Portable Custom | Small specialized renderers and host event bridges |
The support matrix tracks the current public QTI 3 item interaction set described by the
1EdTech QTI 3 Implementation Guide
with element names from the
QTI 3 XML Binding and tracked
internally as the QTI 3.0.1 ASI item profile.
In this README, "Supported" has a specific meaning. The interaction must parse into the typed model, validate against its response and element contract, render in the browser player, score in the core runtime, ship with a public reference fixture, pass fixture and conformance tests, include accessibility metadata, and run through browser rendering tests.
| Spec interaction | QTI element | qti3 status | Evidence |
|---|---|---|---|
| Choice | qti-choice-interaction |
Supported | choice-reference.xml; core, fixture, conformance, a11y, browser tests |
| Text Entry | qti-text-entry-interaction |
Supported | textEntry-reference.xml; core, fixture, conformance, a11y, browser tests |
| Extended Text | qti-extended-text-interaction |
Supported | extendedText-reference.xml; core, fixture, conformance, a11y, browser tests |
| Gap Match | qti-gap-match-interaction |
Supported | gapMatch-reference.xml; core, fixture, conformance, a11y, browser tests |
| Hotspot | qti-hotspot-interaction |
Supported | hotspot-reference.xml; core, fixture, conformance, a11y, browser tests |
| Hot Text | qti-hottext-interaction |
Supported | hottext-reference.xml; core, fixture, conformance, a11y, browser tests |
| Inline Choice | qti-inline-choice-interaction |
Supported | inlineChoice-reference.xml; core, fixture, conformance, a11y, browser tests |
| Match | qti-match-interaction |
Supported | match-reference.xml; core, fixture, conformance, a11y, browser tests |
| Order | qti-order-interaction |
Supported | order-reference.xml; core, fixture, conformance, a11y, browser tests |
| Graphic Order | qti-graphic-order-interaction |
Supported | graphicOrder-reference.xml; core, fixture, conformance, a11y, browser tests |
| Associate | qti-associate-interaction |
Supported | associate-reference.xml; core, fixture, conformance, a11y, browser tests |
| Graphic Associate | qti-graphic-associate-interaction |
Supported | graphicAssociate-reference.xml; core, fixture, conformance, a11y, browser tests |
| Graphic Gap Match | qti-graphic-gap-match-interaction |
Supported | graphicGapMatch-reference.xml; core, fixture, conformance, a11y, browser tests |
| Media | qti-media-interaction |
Supported | media-reference.xml; core, fixture, conformance, a11y, browser tests |
| Position Object | qti-position-object-interaction |
Supported | positionObject-reference.xml; core, fixture, conformance, a11y, browser tests |
| Select Point | qti-select-point-interaction |
Supported | selectPoint-reference.xml; core, fixture, conformance, a11y, browser tests |
| Slider | qti-slider-interaction |
Supported | slider-reference.xml; core, fixture, conformance, a11y, browser tests |
| Upload | qti-upload-interaction |
Supported | upload-reference.xml; core, fixture, conformance, a11y, browser tests |
| Drawing | qti-drawing-interaction |
Supported | drawing-reference.xml; core, fixture, conformance, a11y, browser tests |
| Portable Custom | qti-portable-custom-interaction |
Supported host contract | portableCustom-reference.xml; core, fixture, conformance, a11y, browser tests |
| Custom | qti-custom-interaction |
Deprecated diagnostic | Parsed for explicit warning; not a supported runtime target |
| End Attempt | qti-end-attempt-interaction |
Supported | endAttempt-reference.xml; core, fixture, conformance, a11y, browser tests |
For automated review, the same support matrix is available as JSON:
node packages/cli/dist/index.js support-matrix- Implement the latest public QTI 3 item behavior explicitly, tracking QTI 3.0.1 ASI documents where applicable.
- Support all QTI 3 interaction/question types in the target item profile.
- Make scoring and response processing runnable in Node without a browser.
- Publish an accessible, style-neutral web component player for host applications.
- Publish a reusable conformance test suite.
- Load QTI package ZIPs and assessment-test item references for import tests and item-focused tooling.
- Transcode QTI 3 items and packages to explicit, versioned QTI 1.2, QTI 2.1, QTI 2.2, and product-specific import profiles.
- Resolve host-provided QTI 3 PNP data into player-neutral delivery intents. Hosts retain responsibility for identity, storage, authorization, and institutional policy.
- Keep
qti3-coreandqti3-clifree of third-party runtime dependencies. - Make unsupported or invalid behavior visible through structured diagnostics.
- The core and browser player do not depend on React, Vue, or another UI framework.
- The browser player uses native custom elements without Lit.
- The project does not provide a full assessment-test runner, reusable LMS controller, navigation UI, or delivery shell. The LMS, assessment engine, or harness owns that.
- The project does not provide shared stimulus delivery (
S-*), full test delivery (T-*), timing policy, proctoring, analytics, rostering, gradebook, or LTI integration. - The PNP package does not fetch, store, transmit, or authorize candidate preference records. Hosts provide PNP data and own privacy, consent, policy, and service access.
- The project does not provide product chrome, branding, candidate navigation, or a host product design system.
- Production configuration must be explicit. The project should fail fast instead of using hidden fallbacks.
- QTI XML is not compiled as framework templates.
- There is no global singleton state store. Multiple players should not share a brain.
- Runtime XSD validation is out of scope.
| Package | Path | Purpose |
|---|---|---|
@longsightgroup/qti3-core |
packages/core |
Zero-third-party-runtime-dependency parser, typed model, validation, processing, scoring, state |
@longsightgroup/qti3-player |
packages/player |
Native custom element browser player |
@longsightgroup/qti3-player-react |
packages/player-react |
React adapter for the native web component |
@longsightgroup/qti3-player-preact |
packages/player-preact |
Preact adapter for the native web component |
@longsightgroup/qti3-conformance |
packages/conformance |
Fixture runner and support matrix tooling |
@longsightgroup/qti3-a11y |
packages/a11y |
Accessibility contracts and automated checks |
@longsightgroup/qti3-fixtures |
packages/fixtures |
QTI item fixtures and expected outcomes |
@longsightgroup/qti3-pnp |
packages/pnp |
Dependency-free QTI 3 PNP parser, normalizer, resolver, and diagnostics |
@longsightgroup/qti3-writer |
packages/writer |
Framework-neutral QTI-shaped authoring XML and item-bank package writer with typed diagnostics |
@longsightgroup/qti3-migrator |
packages/migrator |
QTI 1.2 and QTI 2.x package/item migration into QTI 3 authoring items, XML, and package input |
@longsightgroup/qti3-transcoder |
packages/transcoder |
Profile-driven QTI 3 output for QTI 1.2, QTI 2.1, QTI 2.2, Canvas Classic/New Quizzes, and Moodle XML |
@longsightgroup/qti3-cli |
packages/cli |
Zero-third-party-runtime-dependency validation, trusted scoring, delivery preparation, package inspection, and evidence CLI |
QTI package and assessment-test support belongs in tooling, fixtures, and examples for import, inspection, validation, and item loading. The browser player renders one assessment item at a time.
The transcoder converts QTI 3 items and packages through versioned profiles. Standard QTI output and product-specific compatibility use separate profiles. Each conversion returns typed mapping diagnostics instead of selecting an LMS dialect or fallback automatically.
Framework adapters wrap the native web component or core API. The repository includes React and
Preact adapters. Other adapters can be added without moving framework dependencies into
qti3-core.
The browser player is a native web component:
<script type="module" src="/qti3-player.js"></script>
<qti-assessment-item-player id="player"></qti-assessment-item-player>const player = document.getElementById("player");
await player.loadXml(xml, {
status: "interacting",
sessionOptions: {
randomSeed: "candidate-42-item-1",
},
sessionControl: {
validateResponses: true,
showFeedback: false,
},
});
// Attempt JSON stores QTI state, not host functions or capability registries. Pass the same
// sessionOptions again when loading saved state into a new player instance. reset() and restore()
// on this player retain the options object supplied above.
await player.loadUrl("/items/item-1.xml", {
fetchXml: async (url) => {
const response = await fetch(url);
if (!response.ok) throw new Error(`Unable to load ${url}`);
return response.text();
},
});
await player.loadXml(packageItemXml, {
resolveAsset: (url) => packageAssetUrlFor(url),
resolveStylesheet: (stylesheet) => ({
href: packageStylesheetUrlFor(stylesheet.href),
}),
});
// Host-controlled opt-in. qti3 preserves qti-keyword-emphasis by default, but only
// applies extra visual emphasis when the host has resolved candidate AfA/PNP support.
player.keywordEmphasisEnabled = true;
// Equivalent DOM API:
player.setAttribute("data-keyword-emphasis", "true");
player.addEventListener("qti-statechange", (event) => {
saveState(event.detail.state);
});
player.addEventListener("qti-responsechange", (event) => {
console.log(event.detail.responseIdentifier, event.detail.value);
});
player.addEventListener("qti-validation", (event) => {
console.log(event.detail.validationMessages, event.detail.state);
});resolveAsset is a host hook for package or virtual-file environments. The player calls it
for relative src, href, and data asset URLs after rendering the item, and also when hosts
resolve companion-material file references through getCompanionMaterialsResolution() or catalog
support content through getCatalogDeliveryResolution(). Items whose assets are available at
browser URLs can omit it. Use the hook for package-backed media, graphic, and drawing assets so
controls and serialized responses resolve authored references.
qti-stylesheet delivery is a separate host contract. Core preserves stylesheet metadata, and the
browser player attaches item stylesheets only when loadXml / loadUrl receives
resolveStylesheet. The hook must return a candidate-safe stylesheet URL for package-local CSS or
undefined to decline delivery. Package import, path validation, authorization, immutable asset
preservation, and unsafe URL rejection remain host responsibilities.
When resolveStylesheet is omitted, the player treats stylesheets as disabled. It does not attach
them or emit player.stylesheet.unresolved diagnostics.
qti-media-interactionrecords play experiences as asingle/integerresponse. The player supports the QTI shared vocabularydata-qti-media-player-controlstokensnone,default,play,rewind,captions, andaudioDescription.nonesuppresses native media controls; all other recognized tokens render native browser controls and are preserved on the rendered<audio>or<video>element for host styling or custom enhancement. The player also supportsdata-qti-media-player-pause-delayanddata-qti-media-player-pause-durationas second-based timers around playback start and user-initiated pauses.qti-slider-interactionuses a typed numeric definition for bounds, step size, orientation, reverse direction, response validation, and keyboard operation. The player keeps display values and scored values on the same decimal-safe scale.qti-drawing-interactionrequires asingle/fileresponse and serializes candidate drawings as image file data URLs. The player exposes a native pen color input; thetoolbar-palette-noneinteraction class hides it and locks the pen to black.qti-portable-custom-interactionsupports the Portable Custom Interaction (PCI) host contract: parsing and validating launch metadata, interaction markup, template/context bindings, stylesheets, catalog info, opaque suspend/resume state, and response/state events. The player exposes aqti3-portable-custom-hostelement with small launch metadata and emitsqti-portable-custom-mountwith the full parsed definition so a host-provided PCI runtime can attach the module. Production module loading, sandboxing, CSP, tenant allowlists, and audit policy remain host responsibilities.
The browser player ships structural styles for layout, focus visibility, forced-colors support, and accessible interaction behavior. Host products own product chrome, branding, layout density, typography, colors, page-level spacing, candidate navigation, and broader candidate experience styling.
The player renders in light DOM, so host CSS can style it directly:
qti-assessment-item-player {
font:
16px/1.5 system-ui,
sans-serif;
color: #1f2937;
}
qti-assessment-item-player .qti3-interaction {
margin-block: 1rem;
}
qti-assessment-item-player .qti3-choice-option[data-selected="true"] {
border-color: currentColor;
}Rendered elements use qti3-* class names for player structure, such as
qti3-player, qti3-item-body, qti3-interaction, and interaction-specific classes
like qti3-choice, qti3-textEntry, and qti3-hotspot. Authored QTI shared-vocabulary
classes that start with qti- are preserved on rendered interactions where applicable.
The player exposes theme-aware CSS custom properties for structural controls. For order interaction
rows, hosts can set
--qti3-order-row-border-color, --qti3-order-row-background, and
--qti3-order-row-background-hover on the player or an ancestor. Their defaults use
system colors, so they work in light mode, dark mode, and forced-colors environments.
QTI shared vocabulary classes define portable item presentation preferences. Classes such as
qti-labels-none, qti-labels-decimal, qti-input-control-hidden, and qti-unselected-hidden
are parsed, validated, preserved, and implemented according to the support matrix. Host applications
provide product-specific CSS.
The machine-readable support matrix is the source of truth for shipped shared vocabulary
coverage. Inspect the sharedVocabularyClasses section for each class name, scope,
interaction surface, support level, fixture evidence, and test evidence:
node packages/cli/dist/index.js support-matrixSee the 1EdTech QTI 3 Standardized Shared Vocabulary and CSS Classes document for the normative shared vocabulary and example CSS.
- ESM-only packages.
- Node.js 22+.
- Modern browsers.
- Deno 2+.
- Light DOM for the default player, rendered into the page DOM so host CSS and tooling can inspect and style it directly.
- TypeScript 6+
- pnpm
- Vite 8+
- Vitest
- Playwright
- axe-core
- oxfmt
- oxlint
Every change should pass the same release gate locally, in CI, and before publish:
pnpm release:checkpnpm release:check runs formatting, typecheck, lint, unit and conformance tests,
accessibility checks, dependency policy, build, source-map validation, package export
checks, browser coverage, support metadata, and the built CLI fixture runner. It does
not require official 1EdTech certification artifacts.
Run certification checks separately:
QTI3_EXTERNAL_QTI_DIR=/path/to/official/qti \
pnpm certification:checkpnpm test:external remains optional for local development and skips when
QTI3_EXTERNAL_QTI_DIR is not configured. pnpm test:external:required and
pnpm certification:check fail fast unless official external QTI content is
provided. QTI3_EXTERNAL_VALIDATOR_REPORT may be supplied as supplemental evidence,
but Basic IMPORT proof is generated by importing official item and test package zips.
Start the browser harness with:
pnpm devOpen the shared vocabulary gallery from the same Vite dev server:
pnpm dev
open "http://127.0.0.1:5173/sv-gallery?case=choice-selections-dark"From a source checkout, run pnpm build before using the built CLI entry point.
Published packages expose the same commands through the qti3 binary.
The CLI can parse local QTI directories, including external reference sets:
node packages/cli/dist/index.js parse-dir /path/to/itemsUse validation when diagnostics should fail the command:
node packages/cli/dist/index.js validate-dir /path/to/itemsScore each item with its declared correct responses:
node packages/cli/dist/index.js score-correct-dir /path/to/itemsUse a server-trusted response file to score one item:
node packages/cli/dist/index.js score item.xml --responses trusted-responses.jsonPrepare static or server-materialized adaptive candidate XML with:
node packages/cli/dist/index.js prepare-delivery item.xml --out candidate.xml
node packages/cli/dist/index.js prepare-delivery adaptive.xml \
--mode server-materialized-adaptive \
--state trusted-state.json \
--out candidate.xmlResponse and state JSON are server-trusted inputs, not raw browser submissions. Hosts manage adaptive turns through the library API and versioned attempt-state contract.
Inspect a package with:
node packages/cli/dist/index.js inspect-package /path/to/package.zipThis enumerates XML files, assets, manifest/test item references, and parse diagnostics for loadable assessment items.
Use strict package validation for conformance-oriented package checks:
node packages/cli/dist/index.js validate-package /path/to/package.zipStrict package validation requires imsmanifest.xml, requires manifest or
assessment-test item references, and fails direct item XML files that are not referenced
by the package metadata.
Write standalone reference items for targeted interactions, processing patterns, and adaptive behavior:
node packages/cli/dist/index.js write-fixtures packages/fixtures/xmlThe support matrix is machine-readable. It includes evidence for supported interactions, deprecated interactions, processing elements, and shared vocabulary classes:
node packages/cli/dist/index.js support-matrixThe accessibility proof matrix is also machine-readable. It lists each interaction's role, keyboard contract, automated evidence, and manual assistive-technology scripts:
node packages/cli/dist/index.js a11y-proofBefore publishing, verify these requirements:
- Supported interactions need parser, validation, scoring, rendering, keyboard, and accessibility evidence.
- Accessibility checks cover real operation as well as automated scans.
- Dependencies stay small, exact, and reviewed.
- Published packages use explicit npm
filesallowlists so package contents stay small and deliberate. - Release checks must pass before publishing. Certification evidence is checked separately.
Serialized attempt state uses qti3.attempt-state.v1. It captures responses, outcomes,
generated template values, validation messages, lifecycle status, and QTI's built-in
completionStatus outcome. PCI suspend/resume data is stored as opaque JSON under
interactionStates keyed by response identifier.
- Hosts can save, restore, and review attempts through this state contract.
- Hosts can check restored JSON with
isQtiAttemptStateV1()orassertQtiAttemptStateV1(). - Non-adaptive items reset authored outcomes before each scoring run.
- Adaptive items retain outcome values across response-processing runs.
- For non-adaptive items,
endAttempt()completes the item after a valid score run. - For adaptive items,
endAttempt()runs response processing and leaves the item open unless processing setscompletionStatusto"completed". - Templated items restore saved template values before deriving generated correct responses, so resume does not require the original random seed.
qti3 supports QTI-native randomized item instances through template processing.
Authors can use qti-random-integer, generated template variables, printed variables,
and qti-set-correct-response to build deterministic randomized math-style items.
The canonical random-integer-template-reference fixture proves this path by rendering
a generated equation, storing the generated values in templateValues, and scoring the
generated numeric answer.
Hosts should create a variant once, persist the full qti3.attempt-state.v1 value, and
restore from that state on resume. A randomSeed is useful for deterministic initial
generation, but saved templateValues are the authority after an attempt exists. Static
candidate-safe XML redaction remains conservative for template-processing items.
Server-materialized adaptive delivery can render safe template-derived presentation
values from authoritative session state while stripping generated answer keys, template
and response processing, mappings, lookup tables, and declaration defaults from the
candidate XML.
qti3 includes public synthetic fixtures for every current, non-deprecated QTI 3 item interaction.
The canonical examples use MIT-licensed classroom prompts instead of placeholder QTI terminology.
The fixtures cover response shape, scoring, browser rendering, keyboard operation, and
accessibility evidence.
Processing coverage includes response processing, template processing, feedback, printed
variables, MathML/template variables, catalogs, shared CSS vocabulary, advanced
numeric/container/point expressions, and adaptive completionStatus behavior.
The manual harness exposes debugger panels for responses, outcomes, template values, diagnostics, validation messages, serialized state, package item navigation, action history, and accessibility proof scripts.
The packages listed above publish under the longsightgroup npm organization. Releases publish
from the longsightgroup/qti3 repository after pnpm release:check passes. Package tarballs come
from the same checked build output that CI verifies.
The project is not certified. pnpm certification:check requires official 1EdTech external
content and validator evidence.