Web platform (frontend) standards
Criteria verified as of August 2026. Re-verify on the web before committing to anything (§8).
1. Scope and triggers
Sets the criteria on what the browser does: which HTML, CSS and platform API features can be used and under which support policy, how the page loads, what budget the bundle has, which tool builds it and which client-side security controls are mandatory.
Axis: the platform before the framework. Before adding a dependency, the question is whether the browser already does it and since when it does it in every targeted browser.
Triggers: .html, .css, .browserslistrc, browserslist in package.json, vite.config.ts,
postcss.config.*, lightningcss config, Content-Security-Policy / Permissions-Policy /
Cross-Origin-* headers, <link rel=preload|preconnect>, srcset/<picture>, @font-face, @layer,
@container, innerHTML, localStorage, package-lock.json/pnpm-lock.yaml seen as attack
surface.
Arbitration rule with frontend-frameworks-standards: if the answer would change when the
framework changes, it belongs to frontend-frameworks-standards; if it would still hold with any
of them (or with none), it belongs here. fetchpriority on the LCP image is here; whether that image
is emitted by next/image or Astro's <Image> is a detail over there.
Not applicable:
frontend-frameworks-standards— decides which framework is chosen, with which rendering model (CSR/SSR/SSG/ISR/islands/RSC) and how the application is structured: routing, data loading, server vs. client state, hydration, error boundaries, component tests. Here no framework is recommended or vetoed; here we set what the browser must deliver whichever one is chosen.typescript-standards— the TS/JS language,tsconfig.json, typing, linting with Biome/ESLint and packaging for npm publication are theirs. Here the bundle as a budget of bytes served and the build as a tool, not the language rules nor the compiler config.accessibility-standards— WCAG 2.2, ARIA, keyboard navigation, assistive technology and the audit are theirs. Here only semantic HTML as a structural basis: choosing the right element is a platform decision, requiring it as conformance is theirs.web-performance-standards— Core Web Vitals, performance budgets and their measurement are theirs. Here the loading decisions (order, priority, format) that determine them.design-systems-standards(the component's public contract and the governance of the system as a versioned product: tokens, props API, breaking-change policy, documentation and adoption. Here the CSS and the browser APIs it is implemented with),cms-jamstack-standards(where the content lives, who edits it and how it is published: modelling, preview, and cache invalidation on publish),pwa-standards(service worker, manifest, offline),webgl-webgpu-standards,streaming-multimedia-standards(the video/audio pipeline is theirs —ingest, transcoding, HLS/DASH packaging, DRM—; here<video>, Media Source Extensions and EME as web platform APIs that consume what that pipeline delivers),i18n-standards(here the CSS logical properties and the decision to polyfillIntl/Temporal; there what gets formatted and under which rules: message catalogues, ICU MessageFormat, CLDR plural categories, collation, Unicode normalisation and time zones. Useful rule: if the problem is which side the margin falls on in RTL, it is here; if it is how Polish pluralises, it is theirs).caching-cdn-standards— the HTTP cache, the CDN,Cache-Control,Varyand purging are theirs. Here only which headers the application itself emits and why (hashed assets = immutable; HTML = not).api-design-standards(the contract the frontend consumes),appsec-standards(methodology, threat modelling and triage; here the concrete browser controls),secrets-management-standards(a secret does not live on the client: if it has to be stored, the design is wrong — the custody criteria are theirs),identity-access-management-standards(OIDC flows),observability-standards(RUM and telemetry platform; here only what to instrument on the client),cicd-standards(the pipeline that runs the §4 gates),webassembly-standards(already written: the Wasm module and its runtime),mobile-standards(native app),dart-standards(Flutter web).
2. Default decisions
Verify the latest version on the web before pinning it in a real project (§8).
| Piece | Choice | Status as of Aug 2026 | Why |
|---|---|---|---|
| Support policy | Baseline Widely available as the default target | baseline widely available = supported in Chrome, Edge, Firefox and Safari for 30 months |
It is the only definition of "modern browser" that is not an opinion. Measurable and auditable |
| Expressing the policy | Browserslist with extends browserslist-config-baseline or the baseline widely available query |
baseline widely available / baseline newly available / baseline <year> queries supported natively |
A single source that feeds build, autoprefixer and linters |
| Target reproducibility | Freeze to baseline widely available on YYYY-MM-DD in the build |
baseline-browser-mapping recommendation |
"Widely" is a moving target: with no fixed date, two builds of the same commit do not produce the same output |
| Feature gate | @eslint/css rule css/require-baseline or stylelint-plugin-use-baseline; html-eslint rule use-baseline; eslint-plugin-baseline-js for JS |
All with the same knob (widely / newly / year) |
Turns the policy into a CI failure, not a habit |
| Bundler / dev server | Vite | 8.2.0 (8.0 stable 2026-03-12; Rolldown is already the sole bundler, esbuild and Rollup disappear from the pipeline) | A single dev/prod bundler: the "works in dev, breaks in build" class of bug is over |
| Low-level bundler | Rolldown | 1.2.2 (1.0 stable 2026-05-07, ^1.0.0 API under semver) |
Only if you are building tooling; in apps it is consumed via Vite |
| One-off transpiler | esbuild | 0.28.1 | Still alive and correct for isolated tasks. Its 0.x breaks on minors: pin the exact version |
| Next.js bundler | Turbopack | Dev and build default since Next 16 | Not a choice: it comes with the framework (→ frontend-frameworks-standards) |
| CSS transformation | Lightning CSS (built into Vite) or PostCSS if you need specific plugins | Lightning CSS 1.33.0, MPL-2.0 licence (not MIT) | Takes the target from Browserslist and downgrades automatically (oklch(), light-dark(), prefixes) |
| Styles | Native CSS + CSS Modules by default; Tailwind if the team already masters it | Tailwind 4.3.3, MIT; v4 requires Safari 16.4+/Chrome 111+/Firefox 128+ | See the trade-off written out in §3 |
| Fonts | Self-hosting with subsetted woff2 + font-display: swap |
— | A <link> to a font CDN is a third party in the critical path and one more domain to connect to |
| Images | <picture> AVIF → WebP → JPEG, always with width/height |
AVIF Baseline since 2024 (~94-95% global); WebP ~97% | AVIF saves ~30% over WebP. JPEG XL is not an option: ~15% support |
Bundle budget (a starting point, adjusted with real audience data and declared in the repo): ≤ 150 KB of compressed JS on the initial route and ≤ 50 KB of CSS. Exceeding it is not forbidden: what is forbidden is exceeding it without CI saying so and without someone approving it.
3. Structure and conventions
Browser support policy
- The policy is written down, not assumed: a versioned
.browserslistrcplus a line in the README with the reason. "Modern browsers" is not a policy. - Widely available is the default. Newly available only with an explicit decision and awareness that it includes versions released weeks ago; in a mass-market product, almost never.
- Choose the target with your own analytics, not with global statistics: an intranet and a public shop do not have the same browser tail.
- A feature outside the target is not forbidden: it is used as progressive enhancement inside
@supports(CSS) or behindif ('x' in y)(JS), so that its absence degrades the experience and does not break it. If its absence breaks functionality, it is not progressive enhancement: it is a policy failure. - Tools that already expose Baseline and are worth using: CSS hover cards in Chrome DevTools (since
Chrome 140),
webstatus.devand its API (baseline_status:newly AND group:css), theweb-featurespackage, the<baseline-status>component. Verify the status per feature, not by rumour.
HTML
- The right element before the
divwith a role:<button>,<a href>,<nav>,<main>,<dialog>,<details>,<form>,<label for>, headings in order. A<div onclick>is not a button — it loses focus, keyboard, semantics, and therole/tabindexyou have to add by hand is already the signal. - A single
<h1>per document and a hierarchy without skips; real landmarks. - Forms: correct
name,autocompleteandtype(email,tel,url,search) — the mobile keyboard, autofill and the password manager depend on them. - Native validation (
required,pattern,min/max) as the first layer; never as the only one: the real validation is the server's. - A unique
<title>per page,<meta name=description>, Open Graph, canonical,langon<html>. It is SEO and accessibility at the same time; it costs nothing and is always forgotten. - JSON-LD structured data if the content justifies it (product, article, event).
Modern CSS: what replaces what
Status as of Aug 2026 according to Baseline. Re-verify per feature on
webstatus.dev/MDN (§8): this table expires on its own.
| Use | Instead of | Status |
|---|---|---|
@container + cq* units |
Media queries for reusable components | Widely available |
:has() |
State classes set by JS on the parent | Widely available |
@layer |
Specificity war, !important, #id to "win" |
Widely available |
subgrid |
Duplicated grids or manual alignment with calc() |
Widely available |
Custom properties + @property |
Preprocessor variables for theme tokens | Widely available |
color-mix(), light-dark(), oklch() |
Sass colour functions, double palettes | color-mix() widely; verify light-dark()/oklch() |
| Native nesting | Sass nesting as the only reason to have Sass | Widely available |
| View transitions | Page-transition animation libraries | Same-document widely; cross-document: Chromium and Safari yes; Firefox only partial since 146 → progressive enhancement mandatory |
| Anchor positioning, scroll-driven animations, masonry | Positioning with JS | Do not assume: still on the way, an Interop focus. Verify |
- A preprocessor only if it brings something native CSS does not have today. Sass "because we always have" is debt.
@supportsis the degradation tool, not an ornament: wrap anything outside the target in it.- Always reserve space:
width/heightoraspect-ratioon images, iframes and ads. Content shifting is a bug, not an aesthetic annoyance.
CSS methodologies and Tailwind: the trade-off, without taking sides
One convention is chosen per project and documented. All three are defensible:
| Option | Wins | Costs |
|---|---|---|
CSS Modules (+ @layer + custom properties) |
Local scope with no runtime, readable CSS, small output, portable across frameworks | Requires naming discipline and a token system of your own |
| Tailwind | Zero naming decisions, an imposed design scale, aggressive purging, fast onboarding in teams that already know it | Noisy markup, @apply reintroduces the very problem you came to avoid, build dependency, v4 raises the browser floor (Safari 16.4+), and leaving it is a complete refactor |
| CSS-in-JS with runtime | Styles derived from props | Runtime cost and an SSR/RSC penalty. If chosen, make it zero-runtime (compiled at build time). It is the option that has aged worst |
Criteria: if the team is already productive with Tailwind, staying with Tailwind is the right answer — changing is cost without benefit. For greenfield with no prior preference, native CSS + Modules because it does not tie the project to a tool with its own major cycle. What is vetoed is mixing three methodologies in the same repo "depending on who touched the file".
Loading model
- Critical HTML first: the server emits useful markup, not an empty
<div id=root>waiting on JS. (How that HTML is generated belongs tofrontend-frameworks-standards.) - Scripts:
<script type="module">(already implicitlydefer).asynconly for scripts independent of the DOM and of other scripts. Blocking scripts in<head>= veto. preconnectto the critical third-party origins (2-3 at most: each one opens a connection and competes).preloadonly for the critical-path resource the parser discovers late (the font, the CSS of a chunk). Preloading too much is worse than not preloading: it steals bandwidth from the LCP.fetchpriority="high"on the LCP image;fetchpriority="low"on what is below the fold. It is a hint, not an order, and its effect depends on the browser: use it sparingly and measure.loading="lazy"on everything not in the first viewport; never on the LCP image.- Fonts:
woff2, subset to the real character range,font-display: swap(oroptionalif the typographic shift is more annoying than the delay),size-adjust/ascent-overrideto reduce the jump between the fallback and the final one. Two families at most and only the weights actually used. - Code splitting per route as the default; dynamic
import()for the heavy and rare (editor, charts, maps). One huge shared chunk is a silent anti-pattern. - Third parties (analytics, chat, tag manager) out of the critical path and with their own budget: each one is a third party that can go down, slow you down or exfiltrate. A tag manager without governance is an injection door left open to the marketing department.
Platform JavaScript versus dependencies
Rule: every dependency justifies why the platform is not enough. Status as of Aug 2026:
| API | Status | Replaces |
|---|---|---|
fetch + AbortController + AbortSignal.timeout() |
Widely available | axios and homegrown HTTP clients |
IntersectionObserver, ResizeObserver, MutationObserver |
Widely available | Debounced scroll/resize listeners |
structuredClone |
Widely available | lodash.cloneDeep, JSON.parse(JSON.stringify(...)) |
Intl.* (NumberFormat, DateTimeFormat, RelativeTimeFormat, Collator, Segmenter) |
Widely available | Formatting and localisation libraries |
URL / URLSearchParams |
Widely available | Parsing the query by hand |
URLPattern |
Newly available since 2025-09 (widely ~2028-03) | path-to-regexp in homegrown routing and in service workers |
| Navigation API | Newly available since 2026-01 (Firefox 147, Safari 26.2; widely ~2028-07) | History API for SPAs. Real adoption still low: feature-detect + fallback |
Element.setHTML() / Sanitizer API |
Not Baseline: Firefox 148 (2026-02), Chrome 146; Safari no | DOMPurify — when it arrives. Not today |
Temporal |
Not Baseline: Chrome 144, Edge 144, Firefox 139; blocked by Safari since Jan 2026. TC39 Stage 4 (ES2026) | Date, date-fns, dayjs. Polyfill: +35-50 KB gz — a budget decision, not an automatic one |
View Transitions cross-document |
Chromium and Safari yes; Firefox partial since 146 | Only as progressive enhancement |
- Polyfill only when the functionality is essential and the cost has been measured.
Temporalin a calendar app pays off; on a landing page with one formatted date,Intl.DateTimeFormatis already enough. - No global polyfills "just in case" and no full
core-js: it is paid on every load and forever.
Dependencies, bundle and monorepo
- Before adding a dependency: does the platform do it? how much does it weigh with its transitives?
is it maintained? what licence does it have (read from the
LICENSE, not assumed — Lightning CSS is MPL-2.0)? - Bundle budget measured in CI with the compressed size and failing if it grows beyond the agreed delta.
A bundle analysis (
rollup-plugin-visualizeror equivalent) on every PR that touches dependencies. - Importing an entire library for one function is forbidden:
import { x } from 'lib'with real tree-shaking or nothing. Verify that the package is ESM and declaressideEffects. - Monorepo only when there are several deployable artifacts sharing code. A single frontend does not
need a monorepo: it needs folders. If there is one: package manager workspaces, the
workspace:protocol, task caching (Turborepo/Nx), and one bundle budget per app, not a global one. - The package manager, the lockfile and the
package.jsonrules are set bytypescript-standards.
4. Quality and CI gates
In increasing order of cost; each one breaks the build:
- CSS/HTML formatting and linting: Stylelint (or
@eslint/css) andhtml-eslint. - Baseline gate:
css/require-baseline/stylelint-plugin-use-baseline/eslint-plugin-baseline-jswithavailable: "widely". Warning only while migrating an existing project; in greenfield, error. - Valid HTML: validation of the generated markup. A
<div>inside a<p>is a cascading render bug, not pedantry. - Bundle budget: compressed size per entrypoint against the declared limit.
- CSP: check that the emitted policy contains no
unsafe-inlineorunsafe-evalinscript-src. - SCA and lockfile: dependency audit and verification that the lockfile did not change without a PR.
- Visual/E2E against a preview deployment (the tool is set by
frontend-frameworks-standards).
Tests that belong to this skill: that progressive enhancement degrades well (test with the feature disabled, not only with it enabled), that the CSP does not block the app in report-only before enforcement, and that the page works without JS in whatever must work without JS.
5. Platform security
Content Security Policy
- CSP with a
nonceper response (a unique, cryptographically random nonce, never reused) or with ahashfor static scripts, plusstrict-dynamic. A CSP with'unsafe-inline'inscript-srcdoes not protect against XSS: it is exactly the vector it was meant to close, and its presence turns the header into audit decoration. object-src 'none',base-uri 'none',frame-ancestors 'none'(or the list of origins allowed to embed). Deploy first inContent-Security-Policy-Report-Only, enumerate violations, then enforce. Never the other way round.- Trusted Types:
require-trusted-types-for 'script'; trusted-types <policy>. Verified as of Feb 2026 as Baseline (Firefox was the last engine to arrive) — there is no longer a support excuse. It is the only structural defence against DOM XSS: it turns every sink into aTypeErroruntil it goes through an explicit policy. Report-only first, just like the CSP.
XSS and DOM
innerHTML,outerHTML,insertAdjacentHTML,document.write,eval,new FunctionandsetTimeoutwith a string: FORBIDDEN with data you do not control. UsetextContent, or build nodes, or (when support allows)setHTML().Element.setHTML()is preferable to DOMPurify by construction —it filters against the browser's real parser, so the class of mXSS born from double parsing disappears— but it is not Baseline as of Aug 2026 (Safari missing) and bypasses were already published for it in May 2026. Criteria today: feature-detect ('setHTML' in Element.prototype) with DOMPurify as fallback, and server-side sanitisation regardless.setHTMLUnsafe()requires written justification.- URLs: validate the scheme before assigning to
href/src/formaction.javascript:anddata:from user input are XSS. - No rendering HTML received from an API "because it is our API": that API serves whatever was put into it.
Headers and isolation
Strict-Transport-Securitywith a longmax-ageandincludeSubDomains; HTTPS with no exceptions.X-Content-Type-Options: nosniff.- Clickjacking:
frame-ancestorsin the CSP (andX-Frame-Options: DENYas a backstop for old agents). Referrer-Policy: strict-origin-when-cross-originas a minimum.Permissions-Policy: explicitly deny what is not used (camera=(),microphone=(),geolocation=(),payment=(),interest-cohort=()). It also applies to the iframes you embed.Cross-Origin-Opener-Policy: same-originandCross-Origin-Resource-Policy;COEPonly if you need cross-origin isolation (SharedArrayBuffer) — it breaks embeds, it is not enabled for decoration.- CORS belongs to the server and is not authorisation:
Access-Control-Allow-Origin: *with credentials is contradictory and the browser rejects it; reflecting the receivedOriginwithout an allowlist is equivalent to having no CORS. Never use CORS to "protect" an endpoint: protect the endpoint. - Subresource Integrity (
integrity+crossorigin) mandatory on every script or stylesheet served from an origin you do not control. If the resource is mutable by the third party, SRI will break — that is the signal that it should not be in your critical path.
Session and storage
- Session in an
HttpOnly+Secure+SameSite=Laxcookie (orStrict;Noneonly with a real reason and then with explicit CSRF protection).__Host-prefixes where applicable. - A token in
localStorageis a risk decision: any XSS reads it, and it survives closing the tab. If taken, write down why, with what short lifetime and with what revocation. It is not "what everybody does": it is accepting that an XSS equals session theft.sessionStoragereduces the window, not the problem.IndexedDBlikewise. - There are no secrets on the client: no privileged API keys, no feature flags hiding
paid functionality, no authorisation logic. Everything that reaches the browser is public —
see
secrets-management-standards. - If there is personal data on the client, there is retention and deletion; browser storage is not a drawer with no expiry date.
npm supply chain
- Versioned lockfile; CI always installs with a frozen install (
--frozen-lockfile). - Install scripts disabled by default with an explicit allowlist: arbitrary code runs with your privileges before anyone has evaluated the package.
- Valid provenance is no guarantee of security. Verified precedent: the Shai-Hulud campaign
(2025-09 → 2026-05, TeamPCP) published malicious packages with valid SLSA Build Level 3
attestations, using stolen GitHub Actions OIDC tokens; the TanStack case of May 2026 (CVE-2026-45321,
CVSS 9.6) happened with trusted publishing, signed provenance and 2FA on every account.
npm audit signaturesanswers "who built this", not "this is benign". - Operational consequences: a minimum of direct dependencies, pinning by exact version or a strict lockfile, grouped and reviewed updates (Renovate), isolated CI with least privilege, and on an advisory, treat as compromised every machine that installed the affected version.
- Audit new dependencies: maintainer accounts, activity, transitives, typosquatting.
6. Client performance and operability
- Measure in the field, not only in the lab: the metric that decides is the one from real users. The
RUM platform is set by
observability-standards; the thresholds, byweb-performance-standards. Here: what to instrument — navigation, critical resources, uncaught JS errors, CSP violations (report-to), chunk loading failures. - Chunk loading errors after a deployment are a real operational failure, not noise: the old assets must keep being served during the transition window.
- Assets with a hash in the name → cacheable as immutable. HTML → never. The concrete policy belongs to
caching-cdn-standards. - Source maps: uploaded to the error system, not served publicly.
- Degradation: the app must survive a third party not responding. A blocking
<script>from a third party that is down is a blank page. - A client-side feature flag is a suggestion, not access control.
7. Sustainability and prohibitions
- Review the support policy every 6-12 months with analytics data and move the target: leaving it frozen for three years is the silent path to a codebase full of useless polyfills.
- When a feature reaches Widely available, retire its polyfill and its fallback. Nobody does it and that is why bundles only grow. Every polyfill carries its retirement condition written next to it.
- Vite/Rolldown/Tailwind majors: read the changelog and use the official codemod if there is one. Update
browserslist-config-baseline/caniuse-liteon a cadence, not when something breaks. - Dependency unmaintained for >18 months: it is reviewed or replaced.
FORBIDDEN:
- ❌ CSP with
'unsafe-inline'or'unsafe-eval'inscript-src— it invalidates the protection; it is equivalent to having no CSP and on top of that it feigns compliance. - ❌
innerHTML/outerHTML/insertAdjacentHTML/document.writewith user data or data from any API.eval,new Function,setTimeout("string"): forbidden without exception. - ❌ Session or access tokens in
localStoragewithout a written and signed risk decision. - ❌ Secrets, privileged keys or authorisation logic in the client bundle.
- ❌ A third-party script without
integrity+crossorigin, or served from a mutable origin in the critical path. - ❌ Permissive CORS (
*with credentials, reflecting theOriginwithout an allowlist) or used as access control. - ❌
<div onclick>as a button;role+tabindexby hand to recreate semantics that already exist. - ❌ Images, iframes or ads without reserved dimensions or
aspect-ratio. - ❌
loading="lazy"on the LCP image;preloadof everything "just in case". - ❌ Fonts served from a third-party CDN in the critical path; more than two typographic families.
- ❌ "We support modern browsers" as a policy; a browser target with no file and no analytics.
- ❌ Using a feature outside the target without
@supports/ feature detection, or with a fallback that breaks. - ❌ Adding a dependency that duplicates something the platform already does (
fetch,structuredClone,Intl,URLPattern, observers). - ❌ CI without a frozen lockfile; install scripts enabled without an allowlist; trusting the provenance attestation as proof that a package is safe.
- ❌ Mixing three CSS methodologies in the same repo;
!importantto win specificity when@layerexists. - ❌ Copying a feature's support status from memory (or from a blog). It is checked (§8).
8. Mandatory web verification
Before pinning anything, check online (WebSearch/WebFetch; webstatus.dev, MDN, web-features,
official changelogs; GitHub Atom feeds for versions — api.github.com returns 403 unauthenticated):
- Baseline status of every feature you use, one by one, on
webstatus.dev/MDN. The §3 table expires by design. Check in particular whether these have already changed:Temporal(does Safari ship it yet?), Sanitizer API /setHTML()(Safari?), cross-document view transitions in Firefox (partial in 146),light-dark(),oklch(), anchor positioning, scroll-driven animations, masonry. - Interop 2026 focus and its dashboard: what is about to become interoperable (cross-document view
transitions, CSS scroll snap,
shape(), extendedattr(),:open,popover="hint", WebTransport, ESM module loading). This year's list is usually Baseline the next one. - Versions: Vite (is it still 8.2.x? is there a 9?), Rolldown, esbuild (its 0.x breaks on minors),
Lightning CSS, Tailwind, and the version of
browserslist-config-baseline/caniuse-lite. - Licences read from the raw
LICENSE, not assumed. Verified as of Aug 2026: Vite MIT, Rolldown MIT, esbuild MIT, Tailwind MIT, Lightning CSS MPL-2.0. - Advisories and supply chain:
github.com/advisories, osv.dev and the Shai-Hulud waves (is there a generation after "Mini Shai-Hulud"?) before updating dependencies en masse. - Security headers: Trusted Types support status (Baseline since Feb 2026 — reconfirm),
new or deprecated CSP directives, and the status of
Permissions-Policy(its feature list changes). - Your audience's data: your own analytics beats any global statistic when choosing the browser target.
Declared gaps as of Aug 2026 (do not fill them from memory; check before using):
- Exact Baseline status of
light-dark()andoklch(): not verified. - Status of anchor positioning, scroll-driven animations and CSS masonry: not verified beyond being declared Interop targets.
speculationrules(declarative prerender/prefetch) support outside Chromium: not verified.- Exact figures for the §2 bundle budget: they are a starting point, not a measured datum; they are set with the project's real audience.
- Current version of
browserslist-config-baselineand of the Baseline lint plugins (eslint-plugin-baseline-jsstill has no major, its API may change): not verified.
Declared discrepancy: on cross-document view transitions, several secondary sources still
claim that "Firefox and Safari do not support it"; the compatibility data indicate that Safari does
(since 18.4 iOS / 18.5 desktop) and that Firefox supports it partially since 146. Confirm on MDN
before deciding. Likewise, content-security-policy.com and the django-csp documentation
kept listing Trusted Types as unsupported in Firefox and Safari after it reached Baseline
in Feb 2026: MDN/Baseline wins.
If the web contradicts this document, the web wins — flag the discrepancy.