Skip to content

QTI Interactions

QTI Editor ships its interactions as focused subpath exports. Each interaction has its own package subpath and a matching register entrypoint, and the smallest useful integration is usually to import and register one interaction at a time.

These packages are closely coupled to our other open source library, qti-components. In practice, QTI Editor is responsible for authoring behavior and document structure, while qti-components supplies the underlying QTI web components that render and encapsulate the interaction behavior itself. That split keeps the integration framework agnostic while letting each interaction package stay small and explicit.

When you install @citolab/prose-qti, the matching @qti-components/* dependencies are pulled in automatically. You typically import only the interactions your product needs.

Available interactions

InteractionWhat it doesPackage
Associate InteractionAssociating options from a shared set according to configured matching rules@citolab/prose-qti/components/associate
Choice InteractionSingle- or multiple-choice questions with a prompt and selectable choices@citolab/prose-qti/components/choice
Gap Match InteractionDragging or placing predefined gap texts into matching gaps in a passage@citolab/prose-qti/components/gap-match
Hottext InteractionMarking words or phrases inside a text passage as selectable response options@citolab/prose-qti/components/hottext
Inline Choice InteractionDropdown choices embedded directly in a sentence@citolab/prose-qti/components/inline-choice
Text Entry InteractionShort free-text answers entered inline@citolab/prose-qti/components/text-entry
Extended Text InteractionLonger free-text answers in a multi-line response area@citolab/prose-qti/components/extended-text
Match InteractionMatching items across two sets@citolab/prose-qti/components/match
Order InteractionOrdering a list of choices into the correct sequence@citolab/prose-qti/components/order
Select Point InteractionClicking a coordinate or hotspot on an image@citolab/prose-qti/components/select-point

Installing interactions

Install @citolab/prose-qti, then import each interaction’s descriptor, node spec, and register.js module explicitly:

Terminal window
pnpm add @citolab/prose-qti
import { choiceInteractionDescriptor, qtiChoiceInteractionNodeSpec } from '@citolab/prose-qti/components/choice';
import '@citolab/prose-qti/components/choice/register.js';

Each interaction subpath exports:

  • *InteractionDescriptortagName, insertCommand, enterCommand, backspaceCommand, pluginFactories, attributePanelMetadata, nodeSpecs
  • qti*InteractionNodeSpec — ProseMirror node spec (also reachable via descriptor.nodeSpecs)
  • register.js — side-effect module that defines the Lit custom element used by the node view

Shared building blocks (qti-prompt, qti-simple-choice, qti-simple-associable-choice, qti-simple-match-set, qti-gap, qti-gap-text) live under @citolab/prose-qti/components/shared and have their own per-component register.js modules.

Assembling

Collect the descriptors you want to support into a list and derive the schema, plugins, and attribute allowlist from them. See the TypeScript Integration guide for the full pattern.

import { chainCommands } from 'prosemirror-commands';
import { keymap } from 'prosemirror-keymap';
import { choiceInteractionDescriptor } from '@citolab/prose-qti/components/choice';
import { textEntryInteractionDescriptor } from '@citolab/prose-qti/components/text-entry';
// …import the rest
const descriptors = [
choiceInteractionDescriptor,
textEntryInteractionDescriptor,
// …
];
const enterCommand = chainCommands(...descriptors.flatMap(d => d.enterCommand ?? []));
const backspaceCommand = chainCommands(...descriptors.flatMap(d => d.backspaceCommand ?? []));
const qtiPlugins = [
keymap({ Enter: enterCommand, Backspace: backspaceCommand }),
...descriptors.flatMap(d => d.pluginFactories?.map(factory => factory()) ?? []),
];

Inserting an interaction

Each descriptor exposes an insertCommand — a standard ProseMirror command. Use it from your toolbar or programmatically:

import { choiceInteractionDescriptor } from '@citolab/prose-qti/components/choice';
choiceInteractionDescriptor.insertCommand!(view.state, view.dispatch, view);

The TypeScript Integration guide shows how to build an Insert dropdown from the full descriptors list using prosemirror-menu.

Attributes

Each descriptor’s attributePanelMetadata declares which attributes are user-editable per node type. Common attributes:

AttributeDescription
response-identifierLinks the interaction to a response declaration
max-choicesMaximum number of selectable options (choice interaction)
expected-lengthCharacter limit hint (text entry / extended text)
case-sensitiveWhether the response match is case-sensitive (text entry)

The boilerplate ships a small attributesPanelPlugin that renders these into a side panel; the TypeScript Integration guide walks through it.