Web (DOM)

web-dom compiles your app to WebAssembly and drives real DOM elements — a <button> is the browser’s button, <dialog> its modal, <input type="range"> its slider. There is no canvas renderer and no reimplemented widget set. It needs no wasm-bindgen, bundler, or npm step; the output is a plain ES module plus a .wasm file.

Experimental. This is the newest backend and the only one whose gaps are large enough to plan around. The list below is exhaustive as of today; the live Showcase build the app deploys from its own repository is this backend running, so you can judge it yourself.

Getting started

The only prerequisite beyond the CLI is the wasm target.

rustup target add wasm32-unknown-unknown
cargo install day-cli
day new app my-app --toolkit web-dom
cd my-app
day launch -p web-dom     # builds, serves over loopback, opens your browser

day build -p web-dom writes a self-contained dist/index.html, the shim, the stylesheet, your .wasm, and any bundled images and fonts. There is no day pack for web, because dist/ is already the deployable artifact: copy it to any static host.

day build -p web-dom --profile release
# dist/ → your bucket, your CDN, GitHub Pages, anywhere

Serve it over HTTPS (or localhost). Browsers won’t instantiate WebAssembly from file:, and sensors require a secure context.

Caveats and special considerations

This backend is a subset. What’s missing:

  • Most external pieces have no web arm and render a ⟨kind⟩ placeholder: web view, map, Lottie, search field, combo box, date and time pickers, activity indicator, remote image. Only media() is implemented, as a <video>.
  • media() needs a URL, not a file path, and autoplay only starts when muted — both are browser policy, not Day’s.
  • No file dialogs, no context menus, and no application menu bar. cover() presents as a fixed-position overlay (no transition, programmatic dismissal only).
  • The list is emulated, not recycled: one element per row, created eagerly. Long lists cost.
  • Split navigation is decided once at launch from the viewport width (≥ 700px), and does not re-evaluate on resize.
  • Accessibility is thinner here than on native. Semantics come from the real tags Day uses — <button>, <input>, <select>, <progress>, <textarea>, <dialog> — and aria-label flows through from .a11y(...). But where a piece realizes as a <div> (label, tabs, navigation, the nav menu, segmented and radio pickers, the spinner), there is no compensating ARIA role. If you are shipping to an accessibility requirement, use a native target or budget for tweaks.
  • Parts are partial. http, location, prefs, sensors and permissions have browser arms; battery, clipboard, device info, haptics and network do not. The magnetometer is never available (no cross-browser API).
  • Piece registration differs. linkme’s distributed slices don’t compile for wasm, so web-dom keeps a runtime registry and a piece registers itself from its constructor. A piece needs an explicit dom feature to appear at all — see extending.

What each piece becomes

Day pieceHTML
column / row / stack / section<div>
label<div> (not a <label>)
button<button type="button">
toggle<input type="checkbox"> styled as a switch
slider<input type="range">
text_field<input type="text">
text_area<textarea>
picker().menu()<select> with <option>s
picker().segmented() / .inline()a <div> of <button>s
progress(f)<progress max="1">
spinner()a CSS-animated <div>
divider<div>
scroll<div> with overflow:auto
listscrolling <div> of absolutely positioned cells
nav / nav_menu / tabs<div>s (with <button>s for the tab strip and back control)
image<img>
canvas<canvas>, display list replayed on a 2-D context
dialogs / alerts / prompts<dialog> via showModal()
external media()<video>

Text measurement uses a hidden offscreen element with the same computed font and a max-width, measured with getBoundingClientRect() — real browser wrapping metrics, cached per element and width.

Typography is rem-based: Font::Body is 1rem, the other styles follow the Apple text-style ratios, and custom point sizes map to pt/16 rem — so if you raise your browser’s default font size, every Day font scales with it (canvas drawing text excepted, since it lives in the app’s coordinate space).

Pages report their own size back through a ResizeObserver, which is how CSS-managed panes stay in sync with Day’s layout.