Every standard HTML element (per the
MDN HTML elements reference),
with its status in the Tina4Pascal renderer. Companion to
CSS-PROPERTY-INDEX.md. Goal: every rendered element ✅.
Re-audited 2026-09-06 against the source (every ✅ spot-checked in code; added
the <search> landmark found missing). Corrections are flagged inline.
Status: ✅ Rendered correctly · 🟡 Partial · ⬜ Intentionally not rendered (metadata/scripting) · ❌ Missing (should render, doesn't).
| Element | Status | Note |
|---|---|---|
| html, body | ✅ | layout roots |
| head, meta, base | ⬜ | not rendered |
| title | ⬜ | ignored (inner text can leak as a stray node — minor) |
| link[rel=stylesheet] | ✅ | inline <style> and external <link href> both applied. Relative hrefs load from beside the HTML; remote URLs are fetched once through the shell (FetchToFile) into a local csscache/ and reused. Cascade order preserved (links before inline <style>) |
| style | ✅ | parsed into the stylesheet |
| Element | Status | Note |
|---|---|---|
| header, footer, main, section, article, aside, nav | ✅ | block |
| search | ✅ | block landmark (wraps a search form) — added to the block-tag set |
| h1–h6 | ✅ | UA sizes + weight + margins |
| hgroup | ✅ | block; heading children stack |
| address | ✅ | block + italic UA |
| Element | Status | Note |
|---|---|---|
| div, p | ✅ | block + margins |
| ul, ol | ✅ | block + indent + markers |
| menu | ✅ | renders as a list (block + indent + disc markers), like ul |
| li | ✅ | list-item + marker |
| dl, dt, dd | ✅ | bold term, indented def |
| blockquote | ✅ | indent + left border |
| pre | ✅ | monospace, whitespace preserved |
| hr | ✅ | UA thin grey rule line (1px block) + 8px margins |
| figure, figcaption | ✅ | block; figure has UA 1em/40px margins |
| Element | Status | Note |
|---|---|---|
| a, span | ✅ | inline. A click on <a href> with no onclick/control fires the core link hook (Tina4InvokeLink) with the href + target; the shell's registered opener (Tina4LinkOpen — open/ShellExecute/xdg-open) hands it to the OS by scheme: tel:→dialer, mailto:→mail, sms:→messages, http(s):→browser, geo:/maps:→maps. Verified all four schemes dispatch with exact URLs. App wires it once via Tina4InstallLinkOpener (no-op until then) |
| b, strong, i, em, u, ins | ✅ | bold/italic/underline |
| s, del | ✅ | line-through |
| strike | ✅ | line-through (legacy alias of s/del) |
| small, mark, code, kbd, samp, var | ✅ | mark = yellow bg; code/kbd/samp mono |
| sub, sup | ✅ | baseline shift + smaller |
| abbr, cite, dfn, q, br | ✅ | q = auto quotes; nested <q> switch to the inner ‘ ’ pair (reftest css-q-nested) |
| wbr | ✅ | zero-width break opportunity (line wraps there when needed) |
| bdi, bdo | ✅ | <bdo dir> overrides direction (characters reverse + mirror); <bdi> isolates content with auto-detected direction. Built on the bidi reorder; verified matching Chrome. Reftest bdo-override |
| time, data | ✅ | inline text (no visual difference required; value/datetime are metadata) |
| ruby, rt, rp | ✅ | stacked annotation: the <rt> renders centred above its base in a smaller font (furigana); the ruby flows inline and reserves space above the line; <rp> fallback parens hidden. Whole base treated as one annotation pair (no per-character split). Reftest ruby-basic |
| Element | Status | Note |
|---|---|---|
| img | ✅ | remote fetch + decode + cache + aspect (all platforms) |
| svg | ✅ | pure-Pascal vector painter (Tina4SVG) — shapes, paths, text, transforms, fill gradients (<linearGradient>/<radialGradient> via url(#id)), clip-path (<clipPath> first shape, incl. on a <g> subtree) and <use> (href reference + x/y, from <defs>); no mask/filters yet |
| qrcode | ✅ | Tina4 custom — pure-Pascal QR encoder |
| camera | ✅ | Tina4 custom — "Take Photo" → shell capture |
| recorder | ✅ macOS · 🟡 iOS/Android degrade | Tina4 custom — mic capture, the stateful audio analogue of <camera>. Classified ckFile; a tap toggles a recording attribute that drives the chrome (🎙 Record → ⏹ Stop → 🎙 filename) and the start/stop path. TinaTouch returns TINA_RECORD_START/TINA_RECORD_STOP; the shell contract adds StartAudioCapture/StopAudioCapture (safe defaults → a shell with no mic just never arms). On stop the host calls TinaSetRecording(path), which stamps the control value, routes the clip into an <audio id="rec"> player if present, and fires the element's onrecord action. macOS ✅ real AVAudioRecorder → AAC .m4a in the temp dir (bundled app needs NSMicrophoneUsageDescription). iOS/Android degrade like <camera> there (no shell override yet — iOS AVAudioRecorder+AVAudioSession, Android MediaRecorder+RECORD_AUDIO are the follow-up). State machine covered by tests/test_interact.pas |
| picture, source, srcset | ✅ | responsive selection (media/type/density/width/sizes) |
| video | ✅ iOS · ✅ Android · 🟡 macOS | core lays out a poster box + exposes rect/src/flags/poster via tina4_embed_*. Attributes honored per shell: controls, autoplay, loop, muted (bitmask), and poster (painted as the box background by the core). iOS ✅ AVPlayerViewController. Android ✅ VideoView+MediaController in a host FrameLayout. macOS 🟡 Cocoa AVPlayerView (AVKit; loop TODO) — compiles, verify pending a GUI run |
| audio | ✅ | <audio controls> is an engine-drawn control (ckAudio) — a light bar with a round ▶/⏸ button and a progress track, painted by the core (PaintAudioControl) so it looks identical on every platform (no black video surface). A bare <audio> is display:none. It is not a native overlay: a tap returns TINA_AUDIO_TOGGLE and the shell reads TinaAudioSrc/TinaAudioWantPlay, drives an audio player, and pushes the elapsed fraction + sounding state back with TinaSetAudioProgress each frame (playing/progress attributes drive the glyph + bar). Shell contract: AudioPlay/AudioPause/AudioProgress (safe defaults → a shell with no audio out draws an idle bar). iOS/macOS AVAudioPlayer, Android MediaPlayer (relative src resolved against the asset base), desktop the htmlviewer harness — engine-drawn control verified on macOS render + live on Android (tap ▶ plays, bar advances, ⏸ pauses, resets at end) |
| canvas | ✅ | pure-Pascal Canvas 2D (Tina4Canvas2D) — an app registers a painter for <canvas id>; draws with the familiar methods (rects, paths, arc/bezier/quadratic, fill/stroke, text, transforms, globalAlpha, drawImage). drawImage honours the context matrix — scale/flip on every backend, rotation/skew through the CTM (Cocoa/iOS). Core-rendered → every shell + snapshot-testable. No JS, no Skia |
| barcode-scanner | 🟡 core+decode · ❌ camera | Tina4 custom — a live camera scanner. The core lays out a placeholder box and surfaces it through the embed pipeline as kind 2 (tina4_embed_*), capturing the formats attr (e.g. "qr,ean13") and boolean torch; a decode reported by a shell via TinaScanResult fires the element's onscan action with the exact decoded value (by-name dispatch — never parsed as call syntax). Desktop decoding done: Tina4Barcode binds libzbar, loaded dynamically by soname (no build/link dep) — decodes a grayscale frame → value + symbology; round-trip-tested (tests/test_barcode.pas: Tina4QR → zbar). Still per-shell: the camera preview + frame capture (AVFoundation built-in metadata on iOS/macOS, CameraX+MLKit on Android, MediaCapture/V4L2 on desktop feeding Tina4Barcode). Element/dispatch covered by tests/test_scanner.pas |
| camera-view | ✅ core box · ❌ preview | Tina4 custom — a live camera preview (the un-decoded sibling of <barcode-scanner>, for a monitor/feed rather than QR). The core lays out a black inline-block placeholder box (HTML default intrinsic 300×150, shell overlays a native preview over it — same model as <video>); reftest camera-view-test/-ref matches at 0.00%. Shell contract added (Tina4RenderBackend, safe defaults): StartCameraPreview(elementId, facing) / StopCameraPreview / GrabCameraFrame → JPEG (for streaming/motion/detection). Still per-shell: the native preview + frame grab (reuse the scanner's AVCaptureSession/Camera2 session minus the metadata output; add AVCaptureVideoDataOutput/ImageReader for the JPEG). Companion contract StartAudioMeter/AudioLevel (RMS 0..1) + BeginBackgroundCapture land alongside for the monitor use case |
| iframe | ⬜ | intentionally not supported — Tina4 composes pages with <include src> (native HTML splice into the same render tree), not a foreign web view |
| object, map | ⬜ | legacy embed / image maps — not planned |
| track, area, embed | ⬜ | void children / not applicable |
Custom-element registry (src/Tina4Elements.pas) — invent a tag once and it
works everywhere, no core SameText edits. Two tiers, both proven by
tests/test_elements.pas and rendered live on Cocoa (see the demo <nicebutton>
/ <ratingstars> in examples/htmlviewer):
- Tier 1 —
RegisterElement(name, template, css): a template element expands at Build time (next to InjectPseudo) into plain HTML — brace-attr placeholders filled from attributes, a children placeholder from the element's inner HTML. The host tag is consumed (itsidcarries to the first expanded node); the downstream engine sees only standard elements. Idempotent/stable across rebuilds. Registered default CSS is folded into the sheet (UA-like). - Tier 2 —
RegisterNativeElement(name, paint, ontap, css): the element keeps its own tag and draws itself through the canvas contract (Paint hook in PaintBox, clipped like<canvas>) and responds to a tap (OnTap hook in Tina4Interact + the macOS host), sized by its registered CSS. Portable to every shell by construction.
| Element | Status | Note |
|---|---|---|
| include[src] | ✅ | fetches HTML + splices in place, caches, nested, per-tag auth headers |
| secure | ✅ | redacted under capture-protection (see TinaSetCaptureProtected) |
| lottie | ✅ | Tina4 custom — pure-Pascal Lottie/Bodymovin player (Tina4Lottie) rendering inline JSON via Canvas 2D; shape layers, bezier paths, fills/strokes, keyframed transforms + parenting, cubic-bezier easing; animates off the ticker. No Skia/JS. Verified on iPhone |
| Element | Status | Note |
|---|---|---|
| table | ✅ | auto/explicit column sizing, border attr |
| thead, tbody, tfoot | ✅ | tfoot rows moved to the bottom regardless of source order; no sticky/repeat |
| tr, td | ✅ | |
| th | ✅ | UA bold + center |
| colspan (attr) | ✅ | both layout passes |
| rowspan (attr) | ✅ | cell reserves its columns downward; following rows shift correctly |
| caption | ✅ | full-table-width block above (default) or below rows; caption-side honored |
| col, colgroup | ✅ | per-column width via width attr or style; span honored |
| Element | Status | Note |
|---|---|---|
| form | ✅ | block; submit collects name=value |
| input[text/email/password/search/number] | ✅ | all accept text + focus/caret; password masks (•). number paints a right-edge ▴/▾ spinner; clicking it steps value by step (default 1), clamped to min/max, firing change. (numeric/email soft-keyboard still pending a keyboard-type shell contract) |
| input[checkbox] | ✅ | draw + toggle, :checked stylable |
| input[radio] | ✅ | draw + group exclusivity by name |
| input[submit/button] | ✅ | drawn button + press feedback |
| input[file] | ✅ | "Choose File" → shell picker; shows filename |
| input[date] | ✅ | native calendar picker — formatted display (format attr), month nav, today, ISO value (see examples/datepicker) |
| input[color/range/tel/url/…] | ✅/🟡 | color swatch + range slider drawn; tel/url as text |
| textarea | ✅ | multi-line editing: click positions the caret (line by Y, column by X); type/backspace/delete at the caret; Enter splits the line; ←/→ and ↑/↓ navigate (↑/↓ keep the column across lines) |
| select, option | ✅ | drawn dropdown overlay with ✓ |
| optgroup | ✅ | label row + indented options in the dropdown; nested options selectable and resolved when closed |
| button | ✅ | drawn; submit |
| label | ✅ | for=/wrapping/sibling all wired to focus+toggle |
| fieldset, legend | ✅ | bordered group; the legend straddles the top border and notches it (masks the segment behind its background) |
| datalist | ✅ | inert (display:none UA); options never painted |
| output | ✅ | <output formula="a * 2 + b"> recomputes from named field values via a built-in arithmetic evaluator (+ − × ÷, parens); recalculated on oninput="output.recalc()" and seeded at load. No JS engine |
| progress, meter | ✅ | drawn bars (progress accent, meter green) from value/max |
| Element | Status | Note |
|---|---|---|
| details, summary | ✅ | draws ▸/▾ + honors open; tapping the summary toggles open/closed (mobile TinaTouch + desktop MouseUp) |
| dialog | ✅ | dialog.show(id)/dialog.close(id) open/close it in flow; dialog.showModal(id) centres it over a dimmed backdrop (top layer). Driven by the built-in event actions from a plain onclick — no JS engine |
| script | ✅ | content skipped (not rendered) |
| noscript | 🟡 | children render (non-spec, harmless) |
| template | ✅ | inert (display:none UA); content never painted |
| slot | 🟡 | passthrough (no shadow DOM) |
| Element/Attr | Status | Note |
|---|---|---|
| onclick (any element) | ✅ | semantic obj:method(args) + :active feedback |
| id / class / style | ✅ | selectors + inline styles (inline wins) |
| hidden attribute | ✅ | ⇒ display:none |
<!-- comments --> |
✅ | stripped by the parser (SkipComment) — content and any tag-like text inside are dropped, never a stray node |
<!DOCTYPE> |
✅ | skipped (SkipDoctype) |
Everything not listed here is ✅ or ⬜ in the tables above. Each row is corrected in the same commit that changes its behaviour — this list is the truth, not a wishlist.
Core-renderable, still open: none — the element inventory is complete. A
minimal event hook (Tina4Builtins: dialog.show/showModal/close,
output.recalc + a tiny arithmetic evaluator, dispatched from plain onclick/
oninput via Tina4Events) unblocked the last two (<output>, <dialog>
modal) without a JS engine. Anything richer (arbitrary scripting) stays out of
scope by design — native apps consume APIs built elsewhere.
Done recently: <output> formula binding + <dialog> show/close/modal (event
hook); <textarea> mid-text editing; input[number] steppers; <fieldset>
notch; external <link href> CSS fetch (FetchToFile → csscache/).
Media — all done: video (iOS AVPlayerViewController + Android VideoView;
macOS Cocoa AVPlayerView 🟡 loop TODO), audio (engine-drawn ckAudio
control + AudioPlay/AudioProgress contract, not a native overlay),
canvas (pure-Pascal Tina4Canvas2D),
lottie (pure-Pascal Tina4Lottie). iframe/object/map intentionally not
rendered (use <include src>).
Deferred (need a larger subsystem, intentionally later):
Done since the previous audit: whole tables set (rowspan, caption+side,
col/colgroup, tfoot, th); template/datalist inert; <optgroup>; <dialog>
open/closed; details/summary tap-toggle (was already wired — doc was stale);
menu list, strike, hgroup, figure margins, address italic, hr rule line, wbr,
time/data.