Overview
<trust-warning> is the one custom element behind every
redesigned certificate and link warning in this project. It takes a
connection's details, runs them through classify(), and
renders whatever that situation calls for: nothing, a quiet indicator, or
an interrupting panel. It never decides on its own what a situation means;
that logic lives entirely in component/classify.js.
It renders into light DOM, inside a wrapper carrying the class
tw, so the page's own token values (light or dark) apply
without any shadow-DOM boundary to cross.
Anatomy
A caution or stop panel, top to bottom, with the real markup below it.
Severity
Four levels. Colour is never the only signal: each also has its own icon shape and text label.
No interrupt, no indicator. The connection needs no comment.
No interrupt. A quiet, persistent indicator pill (circle with a check).
Interrupts. Proceeding is a reasonable, remembered choice (triangle).
Interrupts. No proceed action on the first screen (octagon).
States
Every situation classify() can return, each rendered from a real scenario in prototypes/scenarios.js.
API
| Property / attribute / event | Type | Description |
|---|---|---|
| context | property, object | The classify() input, plus optional display vars org, orgTeam, renewalChanges. Setting it re-renders. |
| open | attribute, boolean | Whether the panel or indicator is currently shown. Toggling it re-renders. |
| result | property, object (read-only) | The classify() output for the current context: situation, severity, actions, reportTo. |
| tw-decision | event, CustomEvent | Fires on every action taken. detail: {action, situation, severity, view}, bubbles. |
Copy rules
From the top of component/copy.js:
- The title says what is happening, not how the reader should feel.
- Say what the browser knows and what it cannot know, in that order.
- Every action label is a verb phrase that describes its outcome.
- "Secure" is never used. The browser only knows "verified" or "not verified".
- No exclamation marks, no "Warning:", no "Your connection is not private".
Accessibility
- Focus order. When the panel opens, or its view state changes (warning to reviewing, to reporting, and so on), focus moves to the
<h2>title, which carriestabindex="-1". - Escape. Closes the report, review, or fingerprint-comparison sub-view back to the base warning. Escape never proceeds and never trusts a device: there is no path from Escape to a resolved, trusted state.
- alertdialog role. Caution and stop panels are a
<section role="alertdialog" aria-labelledby aria-describedby>, so assistive technology announces them as an interrupting dialog tied to the title and body. - Colour independence. Every severity also has a distinct icon shape (circle, triangle, octagon) and a text label, so nothing is communicated by colour alone.
- Targets. Every interactive control has a minimum hit area of
var(--target)(44px), including on narrow screens where buttons stack full-width. - Reduced motion. The panel's entry animation is skipped entirely under
prefers-reduced-motion: reduce.
Using it
Import the element, set its context, and listen for decisions.
import "./component/trust-warning.js";
const warning = document.createElement("trust-warning");
warning.context = {
kind: "tls",
host: "192.168.1.40",
certProblem: "self-signed",
fingerprint: "4F:9C:1E:...",
publicKey: "spki:7d1f0a",
issuer: "192.168.1.40",
accepted: null,
};
warning.open = true;
document.body.appendChild(warning);
warning.addEventListener("tw-decision", (e) => {
console.log(e.detail.action, e.detail.situation);
});