ExklusivBeta

Trio Check <mv-trio-check>

Stapelprüfung für Quizze, Wissenstests und Zuordnungsübungen, die Raten nutzlos macht: Antworten werden frei eingegeben (deine eigenen Inputs, Selects, Radios oder Checkboxen mit data-trio-item oder Zeilen, die aus einer Eigenschaft items mit Textfeldern, Selects und Auswahl-Chips gerendert werden), und die Komponente verrät nie, ob eine einzelne Antwort richtig ist. Erst wenn ein ganzer Satz Antworten (standardmäßig 3, batch) auf einmal richtig ist, wird er festgeschrieben: Die Zeilen werden schreibgeschützt und erhalten ein gestempeltes Siegel mit „Confirmed“, die Anzeige füllt einen Satz, und der Rest bleibt mehrdeutig (4 richtige Antworten schreiben 3 fest; der letzte Satz schrumpft auf das, was übrig ist). Ein Fehlversuch sagt nur „Not enough correct yet. Nothing was locked.“, mit einem kleinen Schütteln der Anzeige, eine identische erneute Abgabe wird abgelehnt, ohne einen Versuch zu verbrauchen, und eine optionale Abkühlzeit leert den Verify-Button vor dem nächsten Versuch. Die Korrektheit kommt aus einem asynchronen Callback check(answers) (dein Server, der dieselbe Regel anwenden und nur die bestätigten IDs zurückgeben kann) oder aus gesalzenen SHA-256-Hashes in data-answer-hash (mit statischem Helfer hash() und Normalisierung von Groß-/Kleinschreibung, Leerzeichen und Akzenten), sodass Antworten nie im Klartext an den Browser gehen. Eine fixierte Leiste zeigt „6 of 12 confirmed · 2 answered“, die Prüfung kann bei jeder Änderung automatisch laufen, der Fortschritt lässt sich vom Server wiederherstellen, und ein Abschlusszustand (großes Siegel, Anzahl der Versuche, dein eigener Weiter-Button) schließt die Übung ab.

KategorieFormulare
TypWeb Component (<mv-trio-check>)
StatusBeta
Installiert auchbutton, input, select
Keywordsexclusive, culture, quiz, assessment, knowledge-check, compliance-training, onboarding, matching, batch-verification, anti-guessing, language-learning, puzzle, form, answers, hash, cooldown, progress, seal

When to use

  • A compliance or onboarding knowledge check must show real understanding, not answers found by trial and error one field at a time
  • A matching exercise pairs terms with definitions, tickets with actions or people with roles, and each pair should not be checkable alone
  • A language or vocabulary drill checks typed answers in the browser without shipping them in clear
  • A puzzle hunt or escape game confirms deductions in sets so players have to reason rather than brute-force

Avoid when

  • Each answer should get immediate right or wrong feedback, as in a practice drill or a form validation → use Field instead
  • The questions are a guided sequence where each step depends on the previous answer → use Stepper instead
  • A graded, high-stakes exam: client-side hashes of short or multiple-choice answers can be brute-forced, so check on the server only

Installation

node scripts/add.mjs trio-check --out ./src/marvelous

KI-Agent mit dem MCP-Server von Marvelous UI: install_components({ slugs: ["trio-check"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Kopierte Dateien (inklusive Abhängigkeiten): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/motion.js, components/trio-check/trio-check.js, components/trio-check/trio-check.css, components/button/button.css, components/input/input.css, components/select/select.css.

Verwendung

Referenz-Markup zum Starten und Anpassen mit Attributen, data-* und CSS-Variablen:

<div id="tc-demo" style="width:min(100%,66rem);margin-inline:auto">
  <style>
    #tc-demo { display:grid; gap:1.5rem }
    #tc-demo .tc-card { border:1px solid var(--mv-border); border-radius:var(--mv-radius-xl); background:var(--mv-surface); box-shadow:var(--mv-shadow-sm) }
    #tc-demo .tc-head { display:flex; align-items:flex-start; justify-content:space-between; gap:.75rem 1rem; flex-wrap:wrap; padding:1.125rem 1.25rem 0 }
    #tc-demo .tc-head h3 { margin:0; font-size:1.0625rem; letter-spacing:-.01em }
    #tc-demo .tc-head p { margin:.25rem 0 0; color:var(--mv-fg-muted); font-size:.8125rem; line-height:1.45 }
    #tc-demo .tc-body { display:grid; grid-template-columns:17.5rem minmax(0,1fr); gap:1.25rem; align-items:start; padding:1.125rem 1.25rem 1.25rem }
    #tc-demo .tc-policy { position:sticky; top:1rem; padding:1rem 1.125rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-lg); background:var(--mv-bg-subtle); font-size:.8125rem; line-height:1.5 }
    #tc-demo .tc-policy h4 { display:flex; align-items:center; gap:.5rem; margin:0 0 .125rem; font-size:.8125rem }
    #tc-demo .tc-policy h4 svg { width:1rem; height:1rem; color:var(--mv-fg-muted) }
    #tc-demo .tc-policy > p { margin:0 0 .75rem; color:var(--mv-fg-subtle); font-size:.75rem }
    #tc-demo .tc-policy ol { display:grid; gap:.625rem; margin:0; padding:0 0 0 1.125rem }
    #tc-demo .tc-policy li::marker { color:var(--mv-fg-subtle); font-variant-numeric:tabular-nums }
    #tc-demo .tc-policy b { font-weight:600 }
    #tc-demo .tc-foot { display:flex; align-items:center; justify-content:space-between; gap:.75rem 1.25rem; flex-wrap:wrap; padding:.75rem 1.25rem; border-top:1px solid var(--mv-border); background:var(--mv-bg-subtle); border-radius:0 0 var(--mv-radius-xl) var(--mv-radius-xl) }
    #tc-demo .tc-log { margin:0; min-height:1.25rem; color:var(--mv-fg-subtle); font:.75rem/1.4 var(--mv-font-mono); flex:1 1 18rem }
    #tc-demo .tc-actions { display:flex; align-items:center; gap:1rem; flex-wrap:wrap }
    #tc-demo .mv-choice { font-size:.8125rem }
    #tc-demo .tc-lang { padding:1.125rem 1.25rem 1.25rem; display:grid; gap:1rem }
    #tc-demo .tc-lang .tc-head { padding:0 }
    @media (max-width:52rem) {
      #tc-demo .tc-body { grid-template-columns:minmax(0,1fr) }
      #tc-demo .tc-policy { position:static }
    }
  </style>

  <!-- Compliance training: a matching exercise checked by a (fake) server that only knows salted hashes -->
  <section class="tc-card" aria-labelledby="tc-desk-title">
    <div class="tc-head">
      <div>
        <h3 id="tc-desk-title">Refund desk certification</h3>
        <p>Pick the right action for each ticket using only the handbook. Guessing one at a time won’t help.</p>
      </div>
      <span class="mv-badge" data-variant="secondary" data-shape="pill">Module 2 of 4</span>
    </div>
    <div class="tc-body">
      <aside class="tc-policy" aria-labelledby="tc-policy-title">
        <h4 id="tc-policy-title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 5a2 2 0 0 1 2-2h12v18H6a2 2 0 0 1-2-2z"/><path d="M8 7h6M8 11h6"/></svg>Returns handbook</h4>
        <p>Northwind Outfitters · Support policy v3.2</p>
        <ol>
          <li>Unworn items returned <b>within 30 days</b> of delivery: refund to the original payment method.</li>
          <li>Returns between <b>31 and 60 days</b>: store credit only.</li>
          <li>After <b>60 days</b>, or any <b>final-sale</b> item: decline.</li>
          <li>Damaged or wrong item <b>with a photo</b>: send a free replacement, no return needed.</li>
          <li>Any refund <b>over $500</b> goes to a team lead first. This overrides rules 1 and 2.</li>
        </ol>
      </aside>

      <mv-trio-check id="tc-desk" label="Refund tickets">
        <ol class="mv-trio-check-list">
          <li class="mv-trio-check-item" data-trio-item="t4127" data-trio-state="confirmed">
            <span class="mv-trio-check-num" aria-hidden="true">1</span>
            <div class="mv-trio-check-text">
              <label class="mv-trio-check-prompt" for="tc-t4127">Unworn trail shoes, delivered 12 days ago</label>
              <span class="mv-trio-check-hint" id="tc-t4127-h">T-4127 · Priya Nair · $129.00</span>
            </div>
            <div class="mv-trio-check-control">
              <select class="mv-select" id="tc-t4127" aria-describedby="tc-t4127-h">
                <option value="" disabled>Choose an action…</option>
                <option value="refund" selected>Refund to original payment</option>
                <option value="credit">Refund as store credit</option>
                <option value="replace">Send a free replacement</option>
                <option value="escalate">Escalate to a team lead</option>
                <option value="decline">Decline the request</option>
              </select>
            </div>
          </li>
          <li class="mv-trio-check-item" data-trio-item="t4131" data-trio-state="confirmed">
            <span class="mv-trio-check-num" aria-hidden="true">2</span>
            <div class="mv-trio-check-text">
              <label class="mv-trio-check-prompt" for="tc-t4131">Jacket with tags on, delivered 45 days ago</label>
              <span class="mv-trio-check-hint" id="tc-t4131-h">T-4131 · Lucas Moreau · $210.00</span>
            </div>
            <div class="mv-trio-check-control">
              <select class="mv-select" id="tc-t4131" aria-describedby="tc-t4131-h">
                <option value="" disabled>Choose an action…</option>
                <option value="refund">Refund to original payment</option>
                <option value="credit" selected>Refund as store credit</option>
                <option value="replace">Send a free replacement</option>
                <option value="escalate">Escalate to a team lead</option>
                <option value="decline">Decline the request</option>
              </select>
            </div>
          </li>
          <li class="mv-trio-check-item" data-trio-item="t4138" data-trio-state="confirmed">
            <span class="mv-trio-check-num" aria-hidden="true">3</span>
            <div class="mv-trio-check-text">
              <label class="mv-trio-check-prompt" for="tc-t4138">Tent pole snapped in the box, photo attached</label>
              <span class="mv-trio-check-hint" id="tc-t4138-h">T-4138 · Aiko Tanaka · $340.00</span>
            </div>
            <div class="mv-trio-check-control">
              <select class="mv-select" id="tc-t4138" aria-describedby="tc-t4138-h">
                <option value="" disabled>Choose an action…</option>
                <option value="refund">Refund to original payment</option>
                <option value="credit">Refund as store credit</option>
                <option value="replace" selected>Send a free replacement</option>
                <option value="escalate">Escalate to a team lead</option>
                <option value="decline">Decline the request</option>
              </select>
            </div>
          </li>
          <li class="mv-trio-check-item" data-trio-item="t4142">
            <span class="mv-trio-check-num" aria-hidden="true">4</span>
            <div class="mv-trio-check-text">
              <label class="mv-trio-check-prompt" for="tc-t4142">Two unworn parkas, delivered 9 days ago</label>
              <span class="mv-trio-check-hint" id="tc-t4142-h">T-4142 · Mateo García · $1,180.00</span>
            </div>
            <div class="mv-trio-check-control">
              <select class="mv-select" id="tc-t4142" aria-describedby="tc-t4142-h">
                <option value="" disabled>Choose an action…</option>
                <option value="refund">Refund to original payment</option>
                <option value="credit">Refund as store credit</option>
                <option value="replace">Send a free replacement</option>
                <option value="escalate" selected>Escalate to a team lead</option>
                <option value="decline">Decline the request</option>
              </select>
            </div>
          </li>
          <li class="mv-trio-check-item" data-trio-item="t4150">
            <span class="mv-trio-check-num" aria-hidden="true">5</span>
            <div class="mv-trio-check-text">
              <label class="mv-trio-check-prompt" for="tc-t4150">Final-sale swimsuit, never worn</label>
              <span class="mv-trio-check-hint" id="tc-t4150-h">T-4150 · Amara Okafor · $48.00</span>
            </div>
            <div class="mv-trio-check-control">
              <select class="mv-select" id="tc-t4150" aria-describedby="tc-t4150-h">
                <option value="" disabled>Choose an action…</option>
                <option value="refund">Refund to original payment</option>
                <option value="credit" selected>Refund as store credit</option>
                <option value="replace">Send a free replacement</option>
                <option value="escalate">Escalate to a team lead</option>
                <option value="decline">Decline the request</option>
              </select>
            </div>
          </li>
          <li class="mv-trio-check-item" data-trio-item="t4153">
            <span class="mv-trio-check-num" aria-hidden="true">6</span>
            <div class="mv-trio-check-text">
              <label class="mv-trio-check-prompt" for="tc-t4153">Received size M instead of L, photo attached</label>
              <span class="mv-trio-check-hint" id="tc-t4153-h">T-4153 · Jonas Lindqvist · $95.00</span>
            </div>
            <div class="mv-trio-check-control">
              <select class="mv-select" id="tc-t4153" aria-describedby="tc-t4153-h">
                <option value="" disabled selected>Choose an action…</option>
                <option value="refund">Refund to original payment</option>
                <option value="credit">Refund as store credit</option>
                <option value="replace">Send a free replacement</option>
                <option value="escalate">Escalate to a team lead</option>
                <option value="decline">Decline the request</option>
              </select>
            </div>
          </li>
          <li class="mv-trio-check-item" data-trio-item="t4159">
            <span class="mv-trio-check-num" aria-hidden="true">7</span>
            <div class="mv-trio-check-text">
              <label class="mv-trio-check-prompt" for="tc-t4159">Unworn backpack, delivered 75 days ago</label>
              <span class="mv-trio-check-hint" id="tc-t4159-h">T-4159 · Sofia Rossi · $160.00</span>
            </div>
            <div class="mv-trio-check-control">
              <select class="mv-select" id="tc-t4159" aria-describedby="tc-t4159-h">
                <option value="" disabled selected>Choose an action…</option>
                <option value="refund">Refund to original payment</option>
                <option value="credit">Refund as store credit</option>
                <option value="replace">Send a free replacement</option>
                <option value="escalate">Escalate to a team lead</option>
                <option value="decline">Decline the request</option>
              </select>
            </div>
          </li>
          <li class="mv-trio-check-item" data-trio-item="t4164">
            <span class="mv-trio-check-num" aria-hidden="true">8</span>
            <div class="mv-trio-check-text">
              <label class="mv-trio-check-prompt" for="tc-t4164">Unused camp stove set, delivered 40 days ago</label>
              <span class="mv-trio-check-hint" id="tc-t4164-h">T-4164 · Daniel Kim · $620.00</span>
            </div>
            <div class="mv-trio-check-control">
              <select class="mv-select" id="tc-t4164" aria-describedby="tc-t4164-h">
                <option value="" disabled selected>Choose an action…</option>
                <option value="refund">Refund to original payment</option>
                <option value="credit">Refund as store credit</option>
                <option value="replace">Send a free replacement</option>
                <option value="escalate">Escalate to a team lead</option>
                <option value="decline">Decline the request</option>
              </select>
            </div>
          </li>
          <li class="mv-trio-check-item" data-trio-item="t4170">
            <span class="mv-trio-check-num" aria-hidden="true">9</span>
            <div class="mv-trio-check-text">
              <label class="mv-trio-check-prompt" for="tc-t4170">Gloves that did not fit, delivered 3 days ago</label>
              <span class="mv-trio-check-hint" id="tc-t4170-h">T-4170 · Fatima Zahra · $35.00</span>
            </div>
            <div class="mv-trio-check-control">
              <select class="mv-select" id="tc-t4170" aria-describedby="tc-t4170-h">
                <option value="" disabled selected>Choose an action…</option>
                <option value="refund">Refund to original payment</option>
                <option value="credit">Refund as store credit</option>
                <option value="replace">Send a free replacement</option>
                <option value="escalate">Escalate to a team lead</option>
                <option value="decline">Decline the request</option>
              </select>
            </div>
          </li>
        </ol>
        <button slot="complete" type="button" class="mv-button" data-size="sm">Continue to module 3</button>
      </mv-trio-check>
    </div>
    <div class="tc-foot">
      <p class="tc-log" id="tc-log">Progress restored: 3 tickets confirmed last session.</p>
      <span class="tc-actions">
        <label class="mv-choice">
          <input type="checkbox" role="switch" class="mv-switch" id="tc-auto">
          <span class="mv-choice-text"><span class="mv-choice-title">Verify automatically</span></span>
        </label>
        <button type="button" class="mv-button" data-variant="outline" data-size="sm" id="tc-restart">Restart module</button>
      </span>
    </div>
  </section>

  <!-- Language exercise: items rendered from the items property, hashes checked in the browser, 5 s cooldown -->
  <section class="tc-card tc-lang" aria-labelledby="tc-lang-title">
    <div class="tc-head">
      <div>
        <h3 id="tc-lang-title">Spanish A2 · Around the house</h3>
        <p>Type the Spanish word or pick the article. Accents are optional. A wrong set means a 5-second pause.</p>
      </div>
      <span class="mv-badge" data-variant="outline" data-shape="pill">Unit 4 review</span>
    </div>
    <mv-trio-check id="tc-lang" cooldown="5s" salt="es-a2-unit4" label="Spanish vocabulary"></mv-trio-check>
  </section>

  <script type="module">
    await customElements.whenDefined("mv-trio-check");
    const TrioCheck = customElements.get("mv-trio-check");
    const desk = document.getElementById("tc-desk");
    const lang = document.getElementById("tc-lang");
    const log = document.getElementById("tc-log");
    const say = (text) => { log.textContent = text; };

    // Stand-in for your server. It only stores salted hashes (never the answers)
    // and applies the batch rule itself, so the browser learns nothing beyond
    // the ids it confirms.
    const SALT = "nw-refunds-2026";
    const SERVER = {
      t4127: "a3ba7943f2e72ef6b5ca475382d0eca784d445574ede4717e96a36b4a7588c4e",
      t4131: "e7cc294f4a77a7d1c10c4142739cda2431b2b6869a4ef6684dda97e971e86a01",
      t4138: "d922dfd2e3fd4310170c1fd92679c589ebb0499e6f737b1a203acc0240db3dd2",
      t4142: "d2fc233747364ef0232c0f5d1fdba7cdaea97e82d01ecc94bf3444b7331eb0e9",
      t4150: "a6776de3017d2432c3d1bf6b0a9b8afcdac2c5791f720a924be4e046cac1816a",
      t4153: "b0149df55dbf622aeedffad0d4e62f3721d3924321aef4fbc765d5150ae48448",
      t4159: "d4af7ffa16863311616fcddf768dbd75c2338916943075eb4743fe2e6cedf129",
      t4164: "3eb3c92353866407a49c892d0eff0ce98964ead87c5d92366a0acc6d3b03b00d",
      t4170: "fac735b4ff9b86b4883c067b073aebed49ec0e7740641933b722a296fae220e3",
    };
    const fakeServer = async (answers) => {
      await new Promise((r) => setTimeout(r, 700));
      const ids = Object.keys(answers);
      const size = Math.min(3, ids.length);
      const right = [];
      for (const id of ids) {
        if (answers[id] && (await TrioCheck.hash(answers[id], { id, salt: SALT })) === SERVER[id]) right.push(id);
      }
      return right.slice(0, Math.floor(right.length / size) * size);
    };
    desk.check = fakeServer;

    desk.addEventListener("mv-attempt", (e) => say(`Attempt ${e.detail.attempt}: sending ${Object.values(e.detail.answers).filter(Boolean).length} answers…`));
    desk.addEventListener("mv-miss", (e) => say(`Attempt ${e.detail.attempt}: no set of three was fully right.`));
    desk.addEventListener("mv-confirm", (e) => { if (e.detail.source !== "api") say(`Attempt ${e.detail.attempt}: locked ${e.detail.ids.map((id) => id.replace("t", "T-")).join(", ")}.`); });
    desk.addEventListener("mv-complete", (e) => say(`Module complete: ${e.detail.total} tickets in ${e.detail.attempts} attempt${e.detail.attempts === 1 ? "" : "s"}.`));
    document.getElementById("tc-auto").addEventListener("change", (e) => { desk.auto = e.target.checked; });
    document.getElementById("tc-restart").addEventListener("click", () => { desk.reset(); say("Module restarted."); });

    // Rendered items. answerHash = TrioCheck.hash(answer, { id, salt }), computed at authoring time.
    const articles = [{ value: "el", label: "el" }, { value: "la", label: "la" }];
    lang.items = [
      { id: "kitchen", prompt: "the kitchen", hint: "Where you cook", placeholder: "Spanish word", value: "cocina",
        answerHash: ["a4896e33902ad389f472740db007eea22e43d590fc1f4c5c430c9dd6970b7724", "fb069ef1f4fa3413edaabc7a72a7a30c8735520f0f14132edf5f043ceb09fbda"] },
      { id: "window", prompt: "the window", hint: "You open it for fresh air", placeholder: "Spanish word",
        answerHash: ["c31ccf4196fdeaf60478cf4e644eb11fea7de36ed9429f44a837335499f62cb1", "337ffcd8d39756b9286b0007c5ac8dc15c8e73ed6c2964a1083ed50724a76e6e"] },
      { id: "key", prompt: "the key", hint: "It opens the front door", placeholder: "Spanish word",
        answerHash: ["280dbdb70b411e2b86127ec29b7dd70b69ad984b63cfa4342e3c206166bea1e7", "d75dcae5ca5e689048bbec201542f2961c9c46d72d194585d69198881e618649"] },
      { id: "mesa", type: "choice", prompt: "___ mesa", hint: "table", options: articles, value: "la",
        answerHash: "ac606155a337df73bd53b2ddd39beefa03f47acc984a571f81b7f9d38fa56310" },
      { id: "sofa", type: "choice", prompt: "___ sofá", hint: "sofa", options: articles,
        answerHash: "f6c260d5de282870e993113b9723a561b7aeb4250716f78fc1ddd976dd7435f5" },
      { id: "mano", type: "choice", prompt: "___ mano", hint: "hand", options: articles,
        answerHash: "bd9744211255e86a44adea7ad774e131c567e1b15a9c4c33cd6523d7b247c4e7" },
    ];
  </script>
</div>

Kulturelle Referenz

Return of the Obra Dinn, Lucas Pope (2018, Spiel). Das Versicherungsbuch bestätigt deine Schlussfolgerungen nur stapelweise: Schicksale werden erst als richtig festgeschrieben, wenn drei auf einmal stimmen, sodass sie sich nicht einzeln durchprobieren lassen. In der UI werden Antworten immer nur in Sätzen von drei (oder jeder anderen Stapelgröße) bestätigt und gemeinsam versiegelt, während ein Fehlversuch nichts darüber verrät, welche Antwort falsch war.

API

Attributes

NameTypDefaultDescription
batchnumber3How many answers must be right at once to be locked in. Correct answers lock in whole sets only (floor(correct / batch) × batch, in document order); when fewer items than batch are left open, the last set is whatever remains. 1 turns it into plain per-answer verification.
cooldowntime ("5s", "800ms", "1m", ms number)Pause imposed after an attempt that locked nothing: the Verify button reads “Try again in 4s”, drains back and stays aria-disabled. No cooldown by default.
autobooleanVerify on its own 650 ms after an answer is committed (change event: select picked, radio chosen, text field left), whenever enough answers are filled and no cooldown runs. Identical answer sets are never resubmitted.
saltstringSalt mixed into data-answer-hash hashes (see hash()). Ignored when check is set.
normalizefold | trim | exactfoldHow answers are normalized before hashing: fold trims, collapses spaces, lowercases and strips accents (“La Cocína ” = “la cocina”); trim only trims and collapses spaces; exact hashes the raw value. check() always receives raw values.
labelstringKnowledge checkAccessible name of the group (aria-label on the host, unless it has aria-labelledby).
data-trio-itemstring (item id)Marks an item. Its value is the id used in answers, events and hashes; when empty, the element id or a generated id is written back.
data-answer-hashhex SHA-256, space or comma separatedOn an item: accepted answer hashes (several for synonyms such as “cocina” and “la cocina”). Used when no check callback is set.
data-trio-fieldmarker attributeInside an item: the control whose value property is the answer (a custom element, a combobox…). Otherwise the first text field, textarea or select, the checked radio, or the sorted checked checkbox values joined with commas.
data-trio-valuestringOn an item: the answer itself, set by your code for custom widgets (drag and drop, maps, pickers). Takes precedence over any field.
data-trio-statusmarker attributeInside an item: where the confirmed status (seal + “Confirmed”) is written. Otherwise a .mv-trio-check-status span is appended to the item.
data-trio-stateempty | answered | confirmedSet by the component on each item. Server-rendered items with data-trio-state="confirmed" are locked silently on start (restored progress). data-trio-batch holds the set number of a confirmed item.
data-stateidle | ready | pending | cooldown | completeSet on the host: not enough answers yet, verifiable, checking, cooling down, all confirmed. aria-busy is set while checking.

Properties

NameTypDescription
check(answers: Record<id, string>) => Promise<string[] | Set<string> | Record<id, boolean>>Your verifier. Receives every open (unconfirmed) item, empty string when unanswered, and resolves to the ids that are correct. For tamper-proof assessments, apply the same batch rule on the server and return only the ids you confirm: the component’s rule is idempotent. A rejection shows “Your answers couldn’t be checked” and spends no lock.
itemsArray<{ id, prompt, hint?, type?: "text" | "select" | "choice", options?: (string | { value, label })[], placeholder?, value?, answerHash?: string | string[] }>Rows rendered by the component into an <ol class="mv-trio-check-list"> before the bar: labelled text field (mv-input), select (mv-select, with a disabled placeholder) or a radio group of choice chips. type defaults to select when options are given, else text.
stringsPartial<Record<string, string>>Overrides for every visible text and announcement (keys: label, verify, cooldown ({s}), progress ({confirmed}, {total}), answered ({n}), rule ({batch}), ruleOne, needMore ({n}), miss, missCooldown ({s}), repeat, confirmed ({n}), confirmedOne, rest, ready, error, complete ({total}), completeText ({n}), completeTextOne, announceComplete ({total}, {n}), status, statusSr ({batch}), meter, choose). English defaults.
answersRecord<id, string>Current value of every item, confirmed or not (read-only).
confirmedstring[]Ids of the confirmed items in document order (read-only).
progress{ confirmed, total, answered, attempts, complete }Counts shown in the bar; answered counts open items with a value (read-only).
attempts / pending / coolingnumber / boolean / number (ms)Attempts spent, whether a check is in flight, time left in the cooldown (read-only).

Methods

NameDescription
verify({ reason? })Submits the open answers like the Verify button (reason defaults to "api"). Resolves to the ids locked by this attempt, [] when nothing was locked or the attempt was refused (not enough answers, cooldown, pending, identical to the last failed set, mv-attempt cancelled).
MvTrioCheck.hash(answer, { id, salt, normalize })Static: resolves to the hex SHA-256 of ${salt}:${id}:${normalized answer}, the format data-answer-hash expects. Run it at authoring time or on your server; requires a secure context (HTTPS or localhost).
confirm(ids, { animate? })Locks items without checking, e.g. progress restored from your server. Emits mv-confirm with source "api" (and mv-complete if everything is confirmed). Silent unless animate is true.
reset({ keepAnswers? })Unlocks every item, restores each control’s own readonly / disabled state, clears the answers (unless keepAnswers), the attempts, the cooldown and the feedback.

Events

NameDescription
mv-attemptCancelable, before an attempt is sent. detail: { attempt, answers (open items only), batch (set size for this attempt), reason: "button" | "keyboard" | "auto" | "api" }. preventDefault() refuses it without spending an attempt (e.g. a daily limit).
mv-confirmItems were locked. detail: { ids, items (elements), attempt, source (the attempt reason, or "api"), confirmed, total }.
mv-missAn attempt locked nothing. detail: { attempt, answered, cooldown (ms, 0 when none) }. Never says which answers were wrong.
mv-completeEvery item is confirmed. detail: { attempts, total, duration (ms since the first attempt) }.
mv-check-errorThe check callback rejected, or hashing failed (insecure context). detail: { attempt, error }.

Content structure

NameDescription
(content)Your items: any structure with [data-trio-item] elements (use .mv-trio-check-list / .mv-trio-check-item for the default row layout), or nothing when you use the items property. The bar is appended last.
completeChildren with slot="complete" (a Continue button, a certificate link) are moved into the bar and shown only once everything is confirmed.

CSS classes

NameDescription
mv-trio-check-list / mv-trio-check-itemDefault list and row layout (number, text, control, status columns; the control goes under the prompt below 34rem of container width). Parts: -num, -text, -prompt, -hint, -control.
mv-trio-check-choices / mv-trio-check-choiceChoice chips: a group of labels wrapping native radios (arrow keys, focus ring, checked state).
mv-trio-check-status / mv-trio-check-sealConfirmed status of an item: the stamped seal (-seal-check path) and -status-text. The row gets data-trio-fresh for the stamp animation, staggered with --_i.
mv-trio-check-barSticky summary bar: -done-seal, -summary (-head with -count and -answered, -meter of -set groups of -tick[data-tick="empty | answered | confirmed"], -message[data-tone="miss | repeat | success | error | complete"]), -actions (-verify button with data-cooling, -extra for the complete slot).

CSS variables

NameDefaultDescription
--mv-trio-check-sealvar(--mv-success)Seal, confirmed rows and confirmed ticks.
--mv-trio-check-gapvar(--mv-space-2)Space between rows of the default list.
--mv-trio-check-sticky-offsetvar(--mv-space-3)Distance of the sticky bar from the bottom of the viewport while a long list scrolls.

Accessibility

The host is a labelled group; every rendered field has a real <label> (choice chips are a labelled radiogroup of native radios, arrow keys included) and hints are linked with aria-describedby. Nothing ever signals whether an individual answer is right, to sighted users or to screen readers alike. When a set is locked, text fields become readonly (still focusable and readable), selects and radios become disabled, and each control is described by the item’s status (“Confirmed and locked, set 2”); the visible “Confirmed” text and the check mark in the seal mean it never relies on color. If focus sat on a control that just became disabled (auto mode), it moves to the next open field, or to the Verify button. A polite live region announces each outcome once: “3 answers confirmed. 6 of 12 confirmed.”, “Not enough correct yet. Nothing was locked. You can try again in 5 seconds.”, “You can verify again.” when a cooldown ends, and “All 12 answers confirmed. Finished in 4 attempts.”; the visible message line repeats it and is linked to the Verify button with aria-describedby. The Verify button stays focusable when it cannot be used (aria-disabled, with the reason in the message: “Answer 2 more to verify.”), shows aria-busy while checking, and Ctrl/Cmd+Enter anywhere in the component verifies (aria-keyshortcuts). The meter is a role="progressbar" with “6 of 12 confirmed” as its value text. Reduced motion (OS or data-motion="reduce"): no stamp, ink ring, shake or draining fill, states change instantly and the countdown text remains. Forced colors: ticks and seals use system colors, confirmed rows get a solid outline. Locked selects and radios are disabled, so they are not submitted with a native form: read answers or confirmed instead.

Diese Seite wurde mit KI übersetzt. Übersetzungsfehler melden