qti3 is a dependency-light, framework-neutral TypeScript reference implementation for
QTI 3 assessment items.
The project ships public releases on npm. The target is a clean, auditable item engine for parsing, validating, rendering, scoring, serializing, restoring, and testing QTI 3 items across products. The core stays independent of any UI framework.
This is not another framework-specific item player. The public project focuses on QTI item and question-type conformance. Host products own runners, controllers, LMS shells, candidate attempt policy, analytics, proctoring, rostering, and gradebook integrations.
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"]
end
content --> core
content --> cli
core --> player
core --> cli
fixtures --> conformance
fixtures --> cli
conformance --> review
a11y --> review
cli --> review
player --> host
core --> host
Most interaction support starts from the same parser, normalized QtiInteraction model,
and validation gates. The browser player then routes each interaction to a renderer or
helper family based on response shape and user interaction model.
flowchart LR
xml["QTI interaction XML"]
parser["parseInteraction<br/>parseChoices"]
model["QtiInteraction<br/>normalized model"]
validation["common validation<br/>response, choice, limits,<br/>mapping checks"]
registry["player registry"]
renderer["family renderer<br/>or helper layer"]
xml --> parser --> model --> validation --> registry --> renderer
| 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 |
0.2.x established the reference item engine. 0.3.0 adds internal Basic item-player
readiness evidence and a maintainable player architecture. Work after 0.3.0 focuses on
certification evidence, host integration depth, and hardening — not building assessment
runners or LMS delivery shells.
- Strict TypeScript core for parsing, validation, response and template processing, scoring, saved attempt state, and host-readable metadata.
- Native web component player that host products can embed without adopting a framework.
- Public synthetic fixtures and browser tests for every supported current QTI 3 item interaction.
- Machine-readable support, accessibility, catalog, read-aloud, and media metadata.
- Explicit diagnostics for unsupported, deprecated, invalid, or ambiguous item behavior.
- Internal QTI 3 Basic item-player readiness profile with item-only package fixtures,
readiness:basic:item-player, CLIbasic-item-player-report, and Playwright coverage for the narrow Basic scope. - Modular player implementation with unified interaction dispatch, load-time diagnostics mirrored into validation UI, and domain-split styles.
- Type-aware oxlint and explicit workspace package exports (types from source, runtime from
dist).
- Collect official 1EdTech certification evidence by running
certification:checkagainst licensed external QTI content and a non-empty validator report. Internal Basic readiness is not certification. - Expand external corpus coverage in
test:externalas official item banks become available, while keepingtest:external:requiredstrict for explicit certification runs. - Deepen host integration for portable custom interactions: clearer mount contracts, harness examples, and integration guidance. Production module loading, CSP, sandboxing, and tenant policy remain host-owned.
- Keep framework adapters (
@longsightgroup/qti3-player-react,@longsightgroup/qti3-player-preact) aligned with the native web component API as player events and load options evolve. - Keep accessibility evidence current: manual assistive-technology scripts, localized player chrome, and keyboard, reflow, and forced-colors coverage as interactions evolve.
These belong in host products, not in the open-source item engine:
- Full assessment-test runner, reusable LMS controller, or test navigation UI.
- Shared stimulus delivery (
S-*), full test delivery (T-*), timing policy, proctoring, analytics, rostering, LTI, and gradebook integration. - Runtime XSD or schema validation.
The target is 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.
- Provide an accessible, style-neutral web component player that can be embedded in any product.
- Publish a reusable conformance test suite.
- Load QTI package zips and assessment-test item references where useful for item-focused testing.
- Keep dependencies as small as possible.
- Make unsupported or invalid behavior visible through structured diagnostics.
- The project does not depend on a heavy UI framework such as React or Vue.
- The browser player does not use Lit. Native custom elements keep the surface small.
- The project does not provide a reusable LMS runner or controller. The LMS or harness owns that.
- The project does not handle attempt policy, proctoring, analytics, rostering, gradebook, or LTI integration.
- 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.
- The runtime does not run XSD or schema validation. Semantic diagnostics stay fast and embeddable.
packages/
core/ # parser, typed model, validation, processing, scoring, state
player/ # native custom element browser player
conformance/ # fixture runner and support matrix tooling
a11y/ # accessibility contracts and automated checks
fixtures/ # QTI item fixtures and expected outcomes
cli/ # validation, scoring, fixture, and support-matrix CLI
Assessment-test/package support belongs in tooling and examples, and only when it helps discover, load, and verify item references. The player package renders one item at a time and exposes state/events for host-owned runners.
The browser player surface 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",
sessionControl: {
validateResponses: true,
showFeedback: false,
},
});
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),
});
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. Normal
web-served items can omit it. Package-backed media, graphic, and drawing assets should use
this hook so rendered controls and serialized response exports can resolve authored asset
references.
Two response-bearing interactions have format-specific contracts worth calling out:
qti-media-interactionrecords play experiences as asingle/integerresponse.qti-drawing-interactionrequires asingle/fileresponse and serializes candidate drawings as image file data URLs.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 is style-neutral by design. It ships only the structural styles needed for layout, focus visibility, forced-colors support, and accessible interaction behavior. Product typography, spacing, borders, colors, and surrounding chrome belong to the host application.
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.
QTI shared vocabulary classes are authoring hints defined by the specification, not
product theme classes. Classes such as qti-labels-none,
qti-labels-decimal, qti-selections-light, and qti-unselected-hidden describe
portable item-level presentation preferences. qti3 preserves those classes so host
products can reflect the item author's choices while still applying their own visual system.
See the 1EdTech
QTI 3 Standardized Shared Vocabulary and CSS Classes
document for the normative shared vocabulary and example CSS.
Framework adapters may be added later, but they should wrap the web component or core API. They must not own the QTI implementation.
The initial player should use native custom elements directly. Lit is not part of the initial stack and should come back into scope only if plain custom element code creates a maintenance problem that outweighs the dependency and abstraction cost.
- 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 checks locally and in CI:
pnpm format:check
pnpm lint
pnpm typecheck
pnpm test
pnpm test:conformance
pnpm test:a11y
pnpm check:deps
pnpm build
pnpm check:maps
pnpm check:exports
pnpm test:browserThe publish gate is the release check:
pnpm release:checkpnpm release:check uses the public fixture set, browser coverage, support metadata,
package exports, and built CLI fixture runner. It does not require official 1EdTech
certification artifacts.
The certification-oriented gate is available separately:
QTI3_EXTERNAL_QTI_DIR=/path/to/official/qti \
QTI3_EXTERNAL_VALIDATOR_REPORT=/path/to/validator-report.json \
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 and a
non-empty validator report artifact are provided.
The browser harness is available with:
pnpm devFrom 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/itemsIt can also score each item by applying its declared correct responses:
node packages/cli/dist/index.js score-correct-dir /path/to/itemsDelivery-safe XML generation and server-style scoring are library APIs in 0.5.x.
Dedicated CLI commands for those operations are planned separately, not shipped in this
release line.
For package-level inspection without creating an open-source runner, use:
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.
It can also write standalone canonical 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, and processing elements:
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-proofThe release bar is:
- 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 remains a separate future gate.
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 includes public synthetic fixtures for every current, non-deprecated QTI 3 item
interaction. 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.
Package and assessment-test support is item-focused: discovery, item-reference traversal, asset resolution, validation, and item loading. A full runner/controller remains a host product concern.
Packages publish under the longsightgroup npm organization:
@longsightgroup/qti3-core@longsightgroup/qti3-player@longsightgroup/qti3-player-preact@longsightgroup/qti3-player-react@longsightgroup/qti3-fixtures@longsightgroup/qti3-conformance@longsightgroup/qti3-a11y@longsightgroup/qti3-cli
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 currently certified. We plan to pursue relevant QTI certification once
the implementation, fixtures, conformance tests, and public API are stable enough for
review. The certification:check script is intentionally strict so certification work
cannot pass without official 1EdTech external content and validator evidence.