Browser experiments & working notes
Shadow DOM / working examples
Reference
Target.
Six Reference Target capabilities and seven rendering libraries, with opt-in fallback adapters.
for="host"#controlPreparing the controls…
Browser details
- Current path
- Checking browser support…
- Native API surface
- Checking…
- Active adapters
- Waiting for setup…
Automatic checks for the native property. Force fallback loads the selected adapters. Browser alone leaves them out.
Labels
An outside label, an inside control
A label reaches the checkbox selected by the component.
Try: Click either label, then its checkbox.
Wrap the component in its label
Behavior & limits
The first outside label uses for; the second contains its component. Each component also owns a visually hidden native fallback label, so its checkbox stays named when outward element-reference naming is unavailable. Where supported, the adapter merges the outside label with that local name.
It does not create native label.control or input.labels links. DOM readouts do not verify accessible names or screen-reader behavior.
Code sample
Explicit-label markup. Fallback setup hydrates data-reference-target; styling and readiness flags are omitted.
<label id="lj-checkbox-label" for="lj-checkbox-host">Send me release notes</label>
<rt-gallery-checkbox id="lj-checkbox-host" data-reference-target="control">
<template shadowrootmode="open" shadowrootreferencetarget="control">
<label class="visually-hidden" for="control">Release notes preference</label>
<input id="control" type="checkbox">
</template>
</rt-gallery-checkbox>
Popover targets
One panel, three actions
Outside buttons use popovertarget to reach the component’s panel.
Inside an open shadow root
The host leads here
The outside buttons target the component. Its Reference Target identifies this native popover.
This is a manual popover: use Hide panel or the close button to dismiss it.
Try: Toggle twice, then try Show and Hide.
Behavior & limits
Repeating Show or Hide keeps the same state. This manual popover closes through Hide or its internal close button.
The adapter requires the native Popover API. Event ordering and accessibility relationships can differ from native Reference Target.
Code sample
Manual popover with an internal close button.
<button id="pt-toggle" type="button" popovertarget="pt-host"
popovertargetaction="toggle">Toggle panel</button>
<rt-popover-target-demo id="pt-host" data-reference-target="pt-panel">
<template shadowrootmode="open" shadowrootreferencetarget="pt-panel">
<section id="pt-panel" popover="manual" role="dialog"
aria-labelledby="pt-panel-title">
<h2 id="pt-panel-title">The host leads here</h2>
<button id="pt-close" type="button" popovertarget="pt-panel"
autofocus
popovertargetaction="hide">Close panel</button>
</section>
</template>
</rt-popover-target-demo>
Dialog commands
Reach a closed shadow root
Send dialog commands into a closed shadow root.
Try: Open the modal. Use the non-modal preview for outside Close/Request close.
Behavior & limits
A modal makes outside controls inert; close it from inside before trying the non-modal preview. Arm cancellation before Request close to keep the dialog open. Close bypasses the cancel event.
The adapter uses native dialog methods. The component reports its state; its public shadowRoot remains null.
Code sample
Outside command, private dialog, internal close handler.
<button id="dc-show" type="button" commandfor="dc-host"
command="show-modal">Show modal</button>
<rt-closed-dialog-demo id="dc-host"></rt-closed-dialog-demo>
// Run component code after any fallback setup has finished.
const host = document.getElementById('dc-host');
const root = host.attachShadow({
mode: 'closed', referenceTarget: 'dc-dialog',
});
root.innerHTML = `
<dialog id="dc-dialog" aria-labelledby="dc-dialog-title">
<h2 id="dc-dialog-title">A moment of focus</h2>
<button id="dc-internal-close" type="button">Close dialog</button>
</dialog>
`;
const dialog = root.getElementById('dc-dialog');
root.getElementById('dc-internal-close').addEventListener('click', () => {
dialog.close('closed-inside');
});
Popover commands
Commands can be canceled
Send built-in popover commands to an internal panel.
A command reached the component
A panel with a say
The component can cancel a command event before the corresponding native popover operation runs.
This manual popover stays open until a hide or toggle action closes it.
Try: Select “Cancel the next command,” then Show twice.
Behavior & limits
The first Show is canceled; the second opens the panel. Hide and Toggle exercise the other built-in commands.
The fallback uses native popover methods after dispatching a synthetic cancelable command event. Event trust, paths and source handling can differ from native behavior.
Code sample
Three commands share one target. The demo also listens for cancellation.
<button id="pc-toggle" type="button" commandfor="pc-host"
command="toggle-popover">Toggle with a command</button>
<button id="pc-show" type="button" commandfor="pc-host"
command="show-popover">Show with a command</button>
<button id="pc-hide" type="button" commandfor="pc-host"
command="hide-popover">Hide with a command</button>
<rt-popover-command-demo id="pc-host" data-reference-target="pc-panel">
<template shadowrootmode="open" shadowrootreferencetarget="pc-panel">
<section id="pc-panel" popover="manual" role="dialog"
aria-labelledby="pc-panel-title">
<h2 id="pc-panel-title">A panel with a say</h2>
<button type="button" autofocus>Close from inside</button>
</section>
</template>
</rt-popover-command-demo>
Text names and descriptions
Choose the text a reference supplies
Let components supply an input’s name and description.
This extra component text is outside the selected name.
Tell us where the parcel should be left.
This extra component text is outside the selected description.
Try: Change the text, then inspect the input’s name and description in your accessibility tools.
Behavior & limits
The fallback creates hidden text references from data-label-text and data-description-text. Components keep those public values in sync with their visible text.
This plain-text approximation does not compute accessible names from arbitrary markup. The readout shows DOM references, not computed accessibility.
Code sample
Publish text on the hosts; select a provider during fallback setup.
<input id="tn-input" type="text" aria-labelledby="tn-label-host"
aria-describedby="tn-description-host">
<rt-gallery-label id="tn-label-host" data-reference-target="text"
data-label-text="Delivery preference">
<template shadowrootmode="open" shadowrootreferencetarget="text">
<strong id="text">Delivery preference</strong>
<p>This extra component text is outside the selected name.</p>
</template>
</rt-gallery-label>
<rt-gallery-description id="tn-description-host" data-reference-target="text"
data-description-text="Tell us where the parcel should be left.">
<template shadowrootmode="open" shadowrootreferencetarget="text">
<p id="text">Tell us where the parcel should be left.</p>
</template>
</rt-gallery-description>
// In the conditionally loaded setup module (package imports):
import { installReferenceTarget } from 'reference-target-fallback/core';
import { textNames } from 'reference-target-fallback/adapters/text-names';
const fallback = installReferenceTarget({
adapters: [textNames({
getText(host, kind) {
return host.getAttribute(kind === 'label'
? 'data-label-text' : 'data-description-text');
},
})],
});
fallback.hydrate(document);
Form actions
Submit and reset a form inside a component
Outside submit and reset buttons target a form inside the component.
Try: Submit with Email empty, Save draft to skip validation, then edit a field and Reset.
Inside the shadow root
This example requires a browser that parses declarative shadow DOM.
Outside the shadow root
Controls stay disabled until the form’s handlers are ready.
Actual native button.form getters
Waiting for setup…
Submission data
No submission yet. The submit handler cancels navigation and reads native FormData synchronously.
No submit event yet.
Native form events
- No events yet.
Reset restores the name, an empty email, a weekly digest, and the checked updates box.
Behavior & limits
Submit validates; Save draft skips validation and includes intent=draft; Reset restores defaults. The demo cancels navigation and reads FormData synchronously, so no data leaves the page.
The fallback routes actions without associating external data controls or changing native .form getters. Its temporary internal submitter carries the outside button’s name, value and form overrides; submitter identity and event paths differ from native Reference Target.
Code sample
Keep data controls inside the form; install submit handlers before enabling the buttons.
<rt-profile-form id="profile-form" data-reference-target="inner-form">
<template shadowrootmode="open" shadowrootreferencetarget="inner-form">
<form id="inner-form">
<label for="email">Email</label>
<input id="email" type="email" name="email" required>
</form>
</template>
</rt-profile-form>
<button id="submit-profile" type="submit" form="profile-form"
name="intent" value="submit" disabled>Submit</button>
<button id="save-draft" type="submit" form="profile-form"
name="intent" value="draft" formnovalidate disabled>Save draft</button>
<button id="reset-profile" type="reset" form="profile-form" disabled>Reset</button>
const form = document.getElementById('profile-form')
.shadowRoot.getElementById('inner-form');
form.addEventListener('submit', event => {
event.preventDefault();
// Read synchronously: a fallback submitter exists only during dispatch.
const data = new FormData(form, event.submitter);
submitProfile(data); // Application-owned validation and transport.
});
for (const id of ['submit-profile', 'save-draft', 'reset-profile']) {
document.getElementById(id).disabled = false;
}
Rendered by a library
Try the same label, checkbox replacement, and popover examples in 7 independent pages.
Functional JavaScript sizes
Functional page JavaScript
17.732 KB 6.193 KB gzipAlways loaded.
Fallback additional
34.010 KB 11.390 KB gzipCore + selected adapters.
Fallback route total
51.742 KB 17.583 KB gzipAlways-loaded and fallback files; shared files counted once.
Native-surface route total
18.721 KB 6.731 KB gzipIncludes 0.989 KB (0.538 KB gzip) of behavioral probes requested only after the property surface is present.
Minified / gzip. 1 KB = 1,000 bytes. Demo-only syntax highlighting is excluded from the functional totals and reported separately below.
Whole-page delivery context
HTML document
42.666 KB 1 requestGenerated markup, including these measurements.
Initial supporting assets
21.802 KB raw 21.179 KB local transfer · 4 requestsStyles, icon, and the highlighting scheduler; functional JS is separate above.
Initial page with fallback
116.210 KB raw 81.428 KB local transfer · 10 requestsDocument + functional fallback path + initial supporting assets, with unique files counted once.
Deferred highlighting
16.700 KB raw 7.831 KB local transfer · 8 requestsEngine, theme, and this page’s grammar closure, requested after a source disclosure opens or during idle time.
“Local transfer” matches the included development server: gzip JavaScript sidecars, but uncompressed HTML, CSS, and SVG. GitHub Pages, browser caching, HTTP headers, and production compression can change transferred bytes and request scheduling. Module preloads fetch functional modules early without evaluating the app and do not add duplicate transfers.
Inspect the 6 generated JavaScript files
Selected adapters: labels, popover-targets, dialog-commands, popover-commands, text-names, form-targets.
Gzip totals sum each compressed file. The fallback and native-probe routes are mutually exclusive. Separate CSS files, HTML, JSON, source maps, HTTP headers, and Microlighter assets are excluded. Component styles embedded in JavaScript are included. Transfer sizes depend on compression and caching. Microlighter MIT license.
| File | Delivery | Minified | Gzip |
|---|---|---|---|
main.js |
Page | 3.253 KB | 1.349 KB |
shared/chunks/app-LCB4BC4L.js |
Page | 14.337 KB | 4.706 KB |
shared/chunks/chunk-CLCOZ6O7.js |
Fallback / native probe shared | 0.849 KB | 0.422 KB |
shared/chunks/chunk-S2QLMWTP.js |
Page | 0.142 KB | 0.138 KB |
shared/chunks/detect-X6QVIAY4.js |
Native-surface probe | 0.140 KB | 0.116 KB |
shared/chunks/reference-target.setup-CRS7ANCA.js |
Fallback additional | 33.161 KB | 10.968 KB |
One adapter at a time
Independent page builds. Each fallback includes the core; the rows are not additive.
| Capability | Page JavaScript | Fallback additional | Total with fallback |
|---|---|---|---|
| Labels and activation | 4.398 KB (2.026 KB gzip) | 20.951 KB (7.528 KB gzip) | 25.349 KB (9.554 KB gzip) |
| Popover targets | 5.004 KB (2.242 KB gzip) | 18.991 KB (6.891 KB gzip) | 23.995 KB (9.133 KB gzip) |
| Dialog commands | 6.120 KB (2.596 KB gzip) | 19.270 KB (7.006 KB gzip) | 25.390 KB (9.602 KB gzip) |
| Popover commands | 5.185 KB (2.286 KB gzip) | 19.208 KB (6.967 KB gzip) | 24.393 KB (9.253 KB gzip) |
| Text names and descriptions | 5.097 KB (2.235 KB gzip) | 21.858 KB (7.813 KB gzip) | 26.955 KB (10.048 KB gzip) |
| Form submission and reset | 7.657 KB (3.334 KB gzip) | 19.389 KB (6.970 KB gzip) | 27.046 KB (10.304 KB gzip) |
Implementation notes & limitations
Fallback scope
Adapters cover selected behaviors; they do not replace native accessibility relationships, form ownership or event semantics.
Readouts describe DOM state. Check computed names, keyboard behavior and assistive technology separately.