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. Onlymedia()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>— andaria-labelflows 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,sensorsandpermissionshave 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 explicitdomfeature to appear at all — see extending.
What each piece becomes
| Day piece | HTML |
|---|---|
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 |
list | scrolling <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.



