ExklusivBeta
Certainty <mv-certainty>
Text, dessen Passagen jeweils einen Konfidenzgrad tragen (KI-Antwort, Zusammenfassung, Bericht), ruhig dargestellt, damit Leser ihr Vertrauen kalibrieren können, ohne eine Wand aus Warnungen: drei Stufen (Verified ohne Markierung, Likely mit gepunkteter Unterstreichung, Uncertain mit einer handgezeichneten Welle, deren Amplitude mit dem Zweifel wächst) und eine Schrift, die heller wird, je geringer die Konfidenz ist. Hover oder Fokus auf einer Passage öffnet eine Lupe (Prozentsatz, Stufe, Anzeige, Grund, Quellen, "Flag") und dimmt den Rest leicht ab; ein Regler "Only show what's at least X% reliable" macht Passagen unter dem Schwellenwert unscharf oder klappt sie ein; eine Leiste fasst die Gesamtzuverlässigkeit zusammen. Funktioniert mit bestehendem Markup oder mit Segmenten, die angehängt werden, während ein Stream eintrifft.
| Kategorie | Animierter Text |
|---|---|
| Typ | Web Component (<mv-certainty>) |
| Status | Beta |
| Kit | KI, die du überprüfen kannst |
| Keywords | exclusive, text, ai, llm, confidence, uncertainty, trust, calibration, citations, sources, hallucination, streaming, annotation, threshold |
When to use
- An AI answer, summary or report must show per-passage confidence so readers can calibrate trust
- Low-confidence passages should be dimmed or collapsed below a reliability threshold
- Streamed generated text needs a reason, sources and a flag action on each passage
Avoid when
- A full chat interface with composer and streaming replies is needed → use AI Chat instead
- No real confidence scores are available; made-up levels would mislead readers
Installation
node scripts/add.mjs certainty --out ./src/marvelousKI-Agent mit dem MCP-Server von Marvelous UI: install_components({ slugs: ["certainty"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Kopierte Dateien (inklusive Abhängigkeiten): tokens/tokens.css, core/base.css, core/dismiss.js, core/dom.js, core/element.js, core/position.js, components/certainty/certainty.js, components/certainty/certainty.css.
Verwendung
Schnellstart mit dem kleinsten funktionierenden Markup:
<mv-certainty summary>
<p><span data-confidence="0.93">Heat pumps work efficiently in most climates</span>, and <span data-confidence="0.34" data-reason="Based on 2023 contractor quotes.">a ducted system costs about $14,000 installed</span>.</p>
</mv-certainty>Referenz-Markup zum Starten und Anpassen mit Attributen, data-* und CSS-Variablen:
<div id="ct-demo" style="width:min(100%,46rem);margin-inline:auto">
<style>
#ct-demo { display:grid; gap:2.25rem }
#ct-demo .ct-card { display:grid; gap:1rem; padding:1.25rem 1.375rem 1.375rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-xl); background:var(--mv-surface); box-shadow:var(--mv-shadow-xs) }
#ct-demo .ct-ask { justify-self:end; max-width:85%; margin:0; padding:.55rem .9rem; border-radius:1rem 1rem .25rem 1rem; background:var(--mv-bg-muted); font-size:.9375rem; line-height:1.45 }
#ct-demo .ct-who { display:flex; align-items:center; gap:.5rem; margin:0; color:var(--mv-fg-muted); font-size:.75rem; font-weight:600; letter-spacing:.06em; text-transform:uppercase }
#ct-demo .ct-who::before { content:""; width:.5rem; height:.5rem; border-radius:50%; background:var(--mv-accent) }
#ct-demo mv-certainty { font-size:1rem }
#ct-demo .ct-foot { display:flex; align-items:center; justify-content:space-between; gap:.75rem 1rem; flex-wrap:wrap; padding-top:.25rem; color:var(--mv-fg-muted); font-size:.8125rem }
#ct-demo .ct-foot .mv-choice { font-size:.8125rem }
#ct-demo .ct-head { display:flex; align-items:baseline; justify-content:space-between; gap:1rem; flex-wrap:wrap; margin:0 0 -.25rem }
#ct-demo .ct-head h3 { margin:0; font-size:1rem; letter-spacing:-.01em }
#ct-demo .ct-head span { color:var(--mv-fg-muted); font-size:.8125rem }
#ct-demo .ct-flagged { font-variant-numeric:tabular-nums }
</style>
<!-- 1 · Annotated markup: summary strip + threshold -->
<section class="ct-card" aria-label="Annotated answer">
<p class="ct-ask">Is a heat pump worth it for my house built in 1975?</p>
<p class="ct-who">Assistant</p>
<mv-certainty id="ct-answer" summary filter mode="dim" label="Answer reliability">
<p>For a house built in 1975, <span data-confidence="0.93" data-reason="Standard retrofit for forced-air homes of that era." data-sources="U.S. Department of Energy · Heat pump systems|https://www.energy.gov/energysaver/heat-pump-systems">an air-source heat pump can usually replace both the furnace and the central air conditioner</span>. <span data-confidence="0.66" data-reason="Inferred from the construction year; only an energy audit will confirm it.">Homes from that period often have thin wall insulation and leaky ductwork</span>.</p>
<p><span data-confidence="0.95" data-reason="Well established, confirmed by two official sources." data-sources="U.S. Department of Energy · Heat pump systems|https://www.energy.gov/energysaver/heat-pump-systems;ENERGY STAR · Heat pumps|https://www.energystar.gov/">Modern heat pumps work efficiently in most climates, and cold-climate models keep heating well below freezing</span>. <span data-confidence="0.34" data-reason="Based on 2023 contractor quotes; current regional prices were not found.">A ducted whole-house system would cost about $14,000 to $20,000 installed</span>.</p>
<p><span data-confidence="0.9" data-reason="Certification criteria are published and stable." data-sources="ENERGY STAR · Heat pumps|https://www.energystar.gov/">ENERGY STAR certified models use noticeably less electricity than standard ones</span>, and <span data-confidence="0.97" data-reason="Basic physics of heat transfer, explained by the DOE." data-sources="U.S. Department of Energy · Heat pump systems|https://www.energy.gov/energysaver/heat-pump-systems">they move heat rather than generate it, which is why they can deliver more energy than they consume</span>. <span data-confidence="0.58" data-reason="Depends on your utility and local program.">Many utilities offer rebates for qualifying installations</span>, <span data-confidence="0.18" data-reason="Federal credit rules changed recently; no current source confirms this.">and these would still stack with federal tax credits</span>.</p>
<p><span data-confidence="0.72" data-reason="Common in many states, not everywhere." data-sources="DSIRE · Database of State Incentives|https://www.dsireusa.org/">Your state or city may add its own incentive</span>: <span data-confidence="0.41" data-reason="Generalized from a handful of programs only.">it often takes the form of a $500 to $1,500 rebate</span>. <span data-confidence="0.88" data-sources="U.S. Department of Energy · Home energy audits|https://www.energy.gov/energysaver/home-energy-audits">The best first step is a professional home energy audit</span>, which will confirm current prices and programs.</p>
</mv-certainty>
<div class="ct-foot">
<label class="mv-choice"><input type="checkbox" role="switch" class="mv-switch" id="ct-collapse"><span class="mv-choice-text"><span class="mv-choice-title">Collapse hidden passages</span></span></label>
<span class="ct-flagged" id="ct-flagged" aria-live="polite">Hover or tab to a passage to see where it comes from.</span>
</div>
</section>
<!-- 2 · Streaming: segments appended as they arrive -->
<section class="ct-card" aria-label="Streamed answer">
<div class="ct-head"><h3>Live stream</h3><span>segments added one by one with append()</span></div>
<p class="ct-ask">When did the Musée d'Orsay open?</p>
<mv-certainty id="ct-stream" summary label="Reliability of the answer in progress"></mv-certainty>
<div class="ct-foot">
<span id="ct-stream-state">Generating…</span>
<button class="mv-button" data-variant="outline" data-size="sm" type="button" id="ct-regen">Regenerate</button>
</div>
</section>
<script type="module">
await customElements.whenDefined("mv-certainty");
const answer = document.getElementById("ct-answer");
const flagged = document.getElementById("ct-flagged");
document.getElementById("ct-collapse").addEventListener("change", (e) => { answer.mode = e.target.checked ? "collapse" : "dim"; });
answer.addEventListener("mv-flag", () => {
const n = answer.stats.flagged;
flagged.textContent = n ? `${n} passage${n > 1 ? "s" : ""} flagged for review` : "No passages flagged";
});
// Streaming: two alternative answers, each segment arrives on its own.
const orsay = "Musée d'Orsay · Official website|https://www.musee-orsay.fr/en";
const takes = [
[
{ text: "The Musée d'Orsay opened its doors " },
{ text: "in December 1986", confidence: 0.94, reason: "Date given by the museum's official website.", sources: orsay },
{ text: ", inside the former Gare d'Orsay, " },
{ text: "a railway station built for the 1900 World's Fair", confidence: 0.9, reason: "Several sources agree.", sources: orsay },
{ text: ". " },
{ text: "Its interior was designed by Gae Aulenti", confidence: 0.74, reason: "She designed the interior alongside ACT Architecture, who led the conversion." },
{ text: ", and " },
{ text: "the museum reportedly welcomes nearly 4 million visitors a year", confidence: 0.38, reason: "Attendance varies from year to year; the figure is undated." },
{ text: "." },
],
[
{ text: "Housed in a station " },
{ text: "designed by Victor Laloux", confidence: 0.87, reason: "Architect named by every source consulted.", sources: orsay },
{ text: ", the Musée d'Orsay " },
{ text: "was inaugurated in late 1986", confidence: 0.92, sources: orsay },
{ text: ".\n\n" },
{ text: "Its collection mostly spans 1848 to 1914", confidence: 0.69, reason: "Usual boundaries, sometimes presented differently." },
{ text: ", and " },
{ text: "attendance reportedly neared 3.9 million visitors in 2023", confidence: 0.46, reason: "Press figure, not checked against the annual report." },
{ text: "." },
],
];
const stream = document.getElementById("ct-stream");
const state = document.getElementById("ct-stream-state");
let run = 0;
let take = 0;
async function play() {
const id = ++run;
stream.segments = [];
stream.setAttribute("aria-busy", "true");
state.textContent = "Generating…";
for (const seg of takes[take]) {
await new Promise((r) => setTimeout(r, seg.confidence === undefined ? 160 : 520));
if (id !== run || !stream.isConnected) return;
stream.append(seg);
}
stream.setAttribute("aria-busy", "false");
state.textContent = `Done · ${stream.stats.count} annotated passages`;
}
document.getElementById("ct-regen").addEventListener("click", () => { take = (take + 1) % takes.length; play(); });
play();
</script>
</div>API
Attributes
| Name | Typ | Default | Description |
|---|---|---|---|
summary | boolean | Shows the "Overall reliability" strip: mean confidence weighted by passage length, a segmented bar of tier proportions and a legend of the three underline styles. | |
filter | boolean | Shows the "Only show what's at least X% reliable" threshold slider and the count of passages below the threshold. | |
threshold | number (0..1) | 0 | Confidence threshold: passages below it recede (0 = show everything). Also accepts 0..100. |
mode | dim | collapse | dim | dim: passages below the threshold stay in place, blurred and faded (stable layout; hover or focus to read them). collapse: they leave the flow and are replaced by a "… uncertain passage hidden" pill that reveals them on click. |
tiers | string | 0.5,0.8 | Tier boundaries: below the first = Uncertain, from the second up = Verified, in between = Likely. |
label | string | Text reliability | Accessible name of the strip. |
aria-busy | "true" | While streaming: shows a subtle typing caret at the end of the generated text. | |
data-confidence | number (0..1 or %) | On a child element (span…): makes it an annotated passage. Passages added or changed later are picked up. | |
data-reason | string | On a passage: short justification shown in the lens ("single source, dated 2023"). | |
data-sources | string | On a passage: "Title|https://…;Title 2|https://…". Only http(s) URLs become links, opened in a new tab. |
Properties
| Name | Typ | Description |
|---|---|---|
segments | { text, confidence?, reason?, sources? }[] | Text rendered with text nodes only (never HTML). Without confidence: neutral text; a blank line (\n\n) starts a paragraph. sources: an array of { title, url } or a string in the data-sources format. Assigning replaces all generated content. |
stats | object | Read-only: { count, confidence (weighted mean), threshold, hidden, flagged, tiers: { verified | likely | uncertain: { label, count, share } }, passages: [...] }. |
Methods
| Name | Description |
|---|---|
append(segment | segment[]) | Appends one or more segments at the end (streamed LLM output) with a fade-in; returns the created passages. Given DOM nodes, behaves like Element#append(). |
setThreshold(value) | Sets the threshold (0..1 or 0..100); same as the threshold attribute. |
inspect(index) / close() | Opens the lens on passage number index / closes it. |
Events
| Name | Description |
|---|---|
mv-threshold | The threshold changed. detail: { threshold, hidden, count }. |
mv-inspect | The lens opens on a passage. detail: { index, text, confidence, tier, label, reason, sources, below, flagged, element }. |
mv-flag | "Flag" (toggle). detail: the passage data, including flagged. The passage gets data-flagged. |
Content structure
| Name | Description |
|---|---|
(content) | Any text (paragraphs, lists…) containing data-confidence elements. With segments, text is rendered into a [data-certainty-body] container (provided or created). |
CSS classes
| Name | Description |
|---|---|
mv-certainty-passage | Annotated passage (data-tier = verified | likely | uncertain, data-below under the threshold, data-active under the lens). |
mv-certainty-bar / -segments / -legend / -range | Summary strip, segmented bar, legend and threshold slider. |
mv-certainty-lens | Lens (popover): tier, percentage, gauge, reason, sources, .mv-certainty-flag button. |
mv-certainty-stub | Pill standing in for a collapsed passage (collapse mode). |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-certainty-hue | var(--mv-accent) | Single hue for the whole scale (underlines, bar, gauge), stepped light to dark. |
--mv-certainty-ink | var(--mv-fg) | Ink of verified text. |
--mv-certainty-ink-likely / -ink-uncertain | 84% / 68% | Ink density of likely / uncertain passages. |
--mv-certainty-leading | 1.8 | Line height (leaves room for the waves). |
--mv-certainty-bar-bg | Summary strip background. |
Accessibility
Confidence is never conveyed by color alone: ink density + underline style (none / dotted / wavy) + a text label (lens, legend, description). Every passage is focusable (Tab) and described via aria-describedby ("42% confidence, uncertain: reason. 2 sources."); on keyboard focus the lens opens without stealing focus, Enter or Space moves into it (links, "Flag"), Tab out of it resumes reading right after the passage, Shift+Tab returns to the passage, Escape closes it and restores focus. The lens is a non-modal role="dialog" (manual popover), also closed by an outside click; on touch, a tap opens or closes it. The threshold slider is a labelled native range input (aria-valuetext "60% reliable"); the number of passages below the threshold is announced politely after a pause (role=status). Documented choice for hidden passages: in dim mode they stay in the accessibility tree with "Below the threshold" in their description; in collapse mode they leave it (display: none) and the pill, a real button, announces them ("Hidden uncertain passage, 32% confidence. Show"). prefers-reduced-motion and [data-motion="reduce"]: instant transitions, no fade-in or blinking caret.