Modern Web Guidance
JetBrainsOfficialCreated by Google ChromeDistributed by JetBrains
- Skill
- Tool integration
- Software development
- Apache-2.0
- Low risk
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured way to access up-to-date web development patterns, ensuring that implementations follow modern standards rather than relying on potentially outdated training data. It covers a wide range of frontend topics including UI components, performance optimization, browser APIs, and form handling. By integrating this tool, developers can search for specific use cases and retrieve detailed, framework-agnostic guides that include browser support and fallback recommendations.
What it does
- Searches for standardized web development patterns and best practices.
- Retrieves detailed implementation guides for specific frontend use cases.
- Provides guidance on browser compatibility and necessary fallbacks.
- Helps avoid ad-hoc solutions and unnecessary dependency bloat.
- Supports modern web features like container queries, view transitions, and performance APIs.
When to use it
- At the beginning of any frontend feature implementation.
- Before creating new components to check for existing standardized patterns.
- When evaluating the browser support requirements for modern web features.
- When needing to implement performance optimizations like LCP or content-visibility.
Advantages
- Ensures use of current, recommended web development practices.
- Reduces technical debt by discouraging ad-hoc implementations.
- Provides clear guidance on handling browser compatibility and fallbacks.
- Promotes the use of native browser capabilities over heavy external dependencies.
Usage examples
- “I need to implement a modal using modern web standards. What is the best approach?”
- The agent searches for modal-related guides and retrieves the recommended implementation pattern, including any necessary browser fallbacks.
- “How can I optimize the loading priority of my LCP images?”
- The agent searches for image optimization guides and retrieves the specific instructions for using fetch priority to improve performance.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/jetbrains/modern-web-guidance/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip modern-web-guidance.zip -d .agents/skills
Documentation by Google Chrome
Modern Web Guidance
A skill to search for specific web development use cases and retrieve their corresponding best practice guides.
When to use
Must use this skill:
At the start of implementing any web feature.
Before creating a new component, to check if a standardized pattern already exists.
To avoid implementing ad-hoc solutions or loading large dependencies unnecessarily.
Usage Instructions
Step 1. Search Use Cases
Search with an action-oriented query summarizing what you want to achieve using the search command. Run modern-web-guidance directly with npx.
npx -y modern-web-guidance@latest search "<query>" --skill-version 2026_05_16-c5e7870Example Output:
[
{
"id": "optimize-image-priority",
"description": "Optimize the loading priority of Largest Contentful Paint (LCP) candidate images.",
"category": "performance",
"featuresUsed": [ "Fetch priority" ],
"tokenCount": 985,
"similarity": 0.7289
},
{
"id": "defer-rendering-heavy-content",
"description": "Reduce rendering times in content-heavy web pages by deferring rendering for offscreen content.",
"category": "performance",
"featuresUsed": [ "content-visibility", "hidden=\"until-found\"" ],
"tokenCount": 1250,
"similarity": 0.6961
}
]Note: If search results are vague, return no matches, or show low similarity scores, run the
listcommand to browse all guides:npx -y modern-web-guidance@latest list
Step 2. Retrieve Best Practices
Once you have a relevant id from the search results, call this script using the retrieve command to get the full guide. You can pass multiple IDs separated by commas.
npx -y modern-web-guidance@latest retrieve "<id>"Example Output:
The markdown content of the guide describing implementation steps...
Using npx
IMPORTANT: on Windows, using
npxmay fail. Usenpx.cmd ...instead.Network access is required for fetching npm packages needed by the task.
If the
npx -y modern-web-guidance…command hangs, you may be offline. Try running again in offline mode:npx --offline ….The
--skill-versionflag is used to determine if this SKILL.md is out of date. If it is, a warning message is logged to stderr.
Guidelines
Always search first to find the most relevant guides.
These guides are usually framework-agnostic; adapt them correctly to your setup.
Do not hallucinate guides or ignore them; they represent the preferred local standard for the user's project.
Interpreting Browser Support & Fallbacks
Default Behavior: All guides assume Baseline Widely available features are safe to use without fallbacks. For features that are not Baseline widely available, you MUST follow the fallback recommendations in the guide, unless the user has specified a custom browser support policy.
Custom Policies: If the user has already defined explicit browser support requirements, use the browser compatibility data in the guide to determine if a fallback can be safely ignored.
For Baseline YYYY targets, a feature satisfies this target if its "Baseline since" date is <= YYYY.
Policy Examples:
"Do not implement feature fallbacks." (for exploratory prototypes of the cutting-edge web)
"Safari 17.4+" (for internal tools targeting macOS or Tauri-based desktop apps)
"Never recommend or implement polyfills; if a Baseline Newly Available feature is required for core functionality, provide a lightweight custom fallback or redesign the approach." (to minimize bundle size and avoid technical debt)
"Assume a modern execution environment where Baseline Newly Available features can be used natively, provided they are strictly feature-detected and degrade gracefully." (for progressive enhancement strategies)
Reactive Policy Discovery: Watch for environmental cues to suggest documenting a policy in CLAUDE.md or AGENTS.md. Suggest this if the developer:
Mentions building for a restricted runtime (e.g., Electron or Tauri).
Explicitly excludes specific targets (e.g., "we don't support Desktop Chrome").
Expresses hesitation about polyfill complexity, bundle size, or performance cost.
Questions if a feature is safe to use without fallbacks.
No defined policy format. This is an example:
**Browser Support:** Allow Newly Available features, but only adopt custom fallback code that adds <= 20 lines and does not require external dependencies.
Files
- ATTRIBUTION.mdAdded by AutoGineer939 B
- LICENSE11.1 KiB
- SKILL.md5.7 KiB
- guides/accessibility/accessibility.md24.0 KiB
- guides/accessibility/accessible-error-announcement.md6.9 KiB
- guides/built-in-ai/language-detection.md3.5 KiB
- guides/built-in-ai/language-model.md7.1 KiB
- guides/built-in-ai/summarizer.md5.9 KiB
- guides/built-in-ai/translator.md5.3 KiB
- guides/css-layout/css-layout.md19.5 KiB
- guides/css/css.md27.0 KiB
- guides/css/highlight-text-ranges.md5.4 KiB
- guides/forms/animated-select-picker.md7.8 KiB
- guides/forms/autofill-address-form.md4.1 KiB
- guides/forms/autofill-highlight-inputs.md2.3 KiB
- guides/forms/autofill-payment-form.md8.5 KiB
- guides/forms/autofill-sign-in-form.md9.9 KiB
- guides/forms/autofill-sign-up-form.md11.0 KiB
- guides/forms/brand-consistent-forms.md8.3 KiB
- guides/forms/branded-select-styling.md5.5 KiB
- guides/forms/custom-select-picker-layouts.md5.0 KiB
- guides/forms/form-fields-automatically-fit-contents.md5.8 KiB
- guides/forms/forms.md13.8 KiB
- guides/forms/required-field-feedback.md6.8 KiB
- guides/forms/rich-media-picker.md6.6 KiB
- guides/forms/select-menu-interaction.md5.6 KiB
- guides/forms/validate-input-after-interaction.md8.6 KiB
- guides/html/html.md17.5 KiB
- guides/passkeys/passkey-authentication.md9.7 KiB
- guides/passkeys/passkey-conditional-create.md6.5 KiB
- guides/passkeys/passkey-management.md9.3 KiB
- guides/passkeys/passkey-reauthentication.md4.9 KiB
- guides/passkeys/passkey-registration.md9.3 KiB
- guides/passkeys/passkeys.md4.2 KiB
- guides/performance/batch-analytics-events.md6.4 KiB
- guides/performance/break-up-long-tasks.md2.1 KiB
- guides/performance/calculate-total-foreground-time.md2.9 KiB
- guides/performance/conditional-async-dependencies.md3.6 KiB
- guides/performance/defer-rendering-heavy-content.md6.3 KiB
- guides/performance/defer-work-until-scroll-ends.md3.3 KiB
- guides/performance/deprioritize-background-fetches.md1.8 KiB
- guides/performance/detect-initial-visibility-state.md4.5 KiB
- guides/performance/efficient-background-processing.md5.0 KiB
- guides/performance/faster-spa-view-transitions.md3.4 KiB
- guides/performance/full-session-analytics.md5.7 KiB
- guides/performance/identify-heavy-scripts.md4.2 KiB
- guides/performance/identify-inp-causes.md5.4 KiB
- guides/performance/improve-next-page-load-performance.md7.1 KiB
- guides/performance/interactions-in-complex-layouts.md3.4 KiB
- guides/performance/optimize-image-priority.md4.0 KiB
- guides/performance/optimize-preload-priority.md3.3 KiB
- guides/performance/optimize-script-priority.md2.6 KiB
- guides/performance/performance.md17.9 KiB
- guides/performance/resolution-optimized-pseudo-elements.md2.3 KiB
- guides/performance/schedule-tasks-by-priority.md2.2 KiB
- guides/performance/sequence-distributed-events.md3.5 KiB
- guides/privacy/privacy.md11.0 KiB
- guides/security/security.md25.2 KiB
- guides/user-experience/adapt-scrollbar-to-contrast-preferences.md3.5 KiB
- guides/user-experience/anchor-positioning-tab-underline.md2.9 KiB
- guides/user-experience/animate-element-entry-exit.md5.0 KiB
- guides/user-experience/animate-to-from-top-layer.md5.5 KiB
- guides/user-experience/animate-to-intrinsic-sizes.md5.3 KiB
- guides/user-experience/apply-webgl-shaders.md9.9 KiB
- guides/user-experience/calculate-event-differentials.md4.4 KiB
- guides/user-experience/calculate-with-intrinsic-sizes.md9.6 KiB
- guides/user-experience/capture-location-agnostic-data.md3.6 KiB
- guides/user-experience/carousel-slide-effects.md8.1 KiB
- guides/user-experience/carousel-snap-highlights.md5.7 KiB
- guides/user-experience/child-state-based-styling.md4.8 KiB
- guides/user-experience/complex-shapes.md3.5 KiB
- guides/user-experience/component-specific-light-dark-theme.md9.3 KiB
- guides/user-experience/consistent-cross-document-transitions.md11.8 KiB
- guides/user-experience/content-based-styling.md5.2 KiB
- guides/user-experience/coordinate-global-events.md3.5 KiB
- guides/user-experience/cross-document-transitions.md4.4 KiB
- guides/user-experience/customize-scrollbar-color-and-thickness.md2.4 KiB
- guides/user-experience/dark-mode.md15.2 KiB
- guides/user-experience/declarative-button-actions.md5.4 KiB
- guides/user-experience/declarative-dialog-popover-control.md9.2 KiB
- guides/user-experience/deliver-optimized-decorative-images.md2.8 KiB
- guides/user-experience/design-token-reactivity.md5.7 KiB
- guides/user-experience/directional-navigation-transitions.md4.9 KiB
- guides/user-experience/dynamic-sibling-animations.md2.6 KiB
- guides/user-experience/dynamic-sibling-styling.md3.6 KiB
- guides/user-experience/export-html-media-from-canvas.md11.2 KiB
- guides/user-experience/expose-canvas-content-to-browser-features.md14.1 KiB
- guides/user-experience/flicker-free-client-side-ab-testing.md8.5 KiB
- guides/user-experience/fluid-scaling.md3.0 KiB
- guides/user-experience/format-human-readable-durations.md4.2 KiB
- guides/user-experience/group-element-transitions.md5.0 KiB
- guides/user-experience/improve-text-layout-and-legibility.md5.1 KiB
- guides/user-experience/individual-transform-properties.md3.6 KiB
- guides/user-experience/interactive-content-in-3d-scenes.md13.3 KiB
- guides/user-experience/interactive-content-reveal.md5.1 KiB
- guides/user-experience/interest-triggered-action-previews.md4.1 KiB
- guides/user-experience/interest-triggered-tooltips.md6.8 KiB
- guides/user-experience/light-dismiss-a-dialog.md3.6 KiB
- guides/user-experience/manage-recurring-intervals.md3.3 KiB
- guides/user-experience/model-partial-time-concepts.md4.2 KiB
- guides/user-experience/move-dom-element-without-losing-state.md2.5 KiB
- guides/user-experience/navigation-drawer.md15.7 KiB
- guides/user-experience/overflow-clipping-control.md8.0 KiB
- guides/user-experience/parallax-scroll-effects.md8.8 KiB
- guides/user-experience/persistent-app-tours.md3.9 KiB
- guides/user-experience/persistent-toast-notifications.md1.8 KiB
- guides/user-experience/persistent-top-layer-ui.md2.2 KiB
- guides/user-experience/physics-based-easing.md5.8 KiB
- guides/user-experience/platform-controls-dismiss-dialog.md2.6 KiB
- guides/user-experience/position-aware-tooltips.md5.9 KiB
- guides/user-experience/precise-text-alignment.md4.7 KiB
- guides/user-experience/prevent-text-wrapping.md2.4 KiB
- guides/user-experience/pull-to-reveal.md4.7 KiB
- guides/user-experience/reduce-style-repetition.md3.9 KiB
- guides/user-experience/resilient-context-menus-and-nested-dropdowns.md4.9 KiB
- guides/user-experience/same-document-transitions.md8.3 KiB
- guides/user-experience/scroll-entry-exit-effects.md7.8 KiB
- guides/user-experience/scroll-position-aware-elements.md3.6 KiB
- guides/user-experience/scroll-progress-indicator.md6.7 KiB
- guides/user-experience/scroll-snap-realtime-feedback.md6.7 KiB
- guides/user-experience/scroll-snap-state-sync.md3.9 KiB
- guides/user-experience/scroll-target-on-load.md3.3 KiB
- guides/user-experience/scrollability-affordance-hints.md3.6 KiB
- guides/user-experience/scrollytelling.md8.3 KiB
- guides/user-experience/search-hidden-content.md6.7 KiB
- guides/user-experience/shaped-cutouts.md7.0 KiB
- guides/user-experience/shrinking-header-on-scroll.md6.3 KiB
- guides/user-experience/size-aware-styling.md2.9 KiB
- guides/user-experience/soft-edge-content-fade.md2.6 KiB
- guides/user-experience/stabilize-reactive-state.md3.5 KiB
- guides/user-experience/stack-drill-down.md32.9 KiB
- guides/user-experience/style-parent-with-has.md6.0 KiB
- guides/user-experience/support-global-calendar-systems.md5.7 KiB
- guides/user-experience/swipe-to-remove.md26.2 KiB
- guides/user-experience/visually-stable-font-fallbacks.md2.9 KiB
- guides/user-experience/visually-stable-mixed-fonts.md2.8 KiB
- guides/user-experience/visually-texture-content.md3.7 KiB
- guides/webmcp/agentic-forms.md5.0 KiB
- guides/webmcp/agentic-javascript-tools.md4.1 KiB
- guides/webmcp/webmcp.md3.7 KiB
guides/user-experience/expose-canvas-content-to-browser-features.md
Expose canvas content to browser features
Regular <canvas> content is not exposed to browser features such as screen readers, indexing, translation tools, accessibility assistive tools, find-in-page, print, etc. With HTML in canvas, you can render real DOM directly in a canvas element. Adding the layoutsubtree attribute to a <canvas> HTML element allows rendering descendant HTML elements within the canvas's rendering context. You can use it to style and lay out text in a canvas, expose canvas content to browser features (like accessibility, translation, or find-in-page), and apply 2D and 3D effects to HTML.
How to implement
Check if HTML-in-Canvas is supported in the browser:
if ('requestPaint' in HTMLCanvasElement.prototype) {
// Use HTML in Canvas API
} else {
// Use fallback strategy
}Add the
layoutsubtreeattribute to the<canvas>HTML element.Place your HTML content inside the
<canvas>element with thelayoutsubtreeattribute.
<canvas id="canvas" layoutsubtree>
<div id="html-content"></div>
</canvas>Scale your canvas grid to match the device scale factor to prevent blurriness:
const observer = new ResizeObserver(([entry]) => {
const dpc = entry.devicePixelContentBoxSize;
canvas.width = dpc
? dpc[0].inlineSize
: Math.round(entry.contentRect.width * window.devicePixelRatio);
canvas.height = dpc
? dpc[0].blockSize
: Math.round(entry.contentRect.height * window.devicePixelRatio);
});
const supportsDevicePixelContentBox =
typeof ResizeObserverEntry !== "undefined" &&
"devicePixelContentBoxSize" in ResizeObserverEntry.prototype;
const options = supportsDevicePixelContentBox
? { box: "device-pixel-content-box" }
: {};
observer.observe(canvas, options);Render the HTML content to the canvas inside a
canvas.onpaintevent handler:
In 2D context, use the
drawElementImagemethod:
canvas.onpaint = () => {
ctx.reset();
// Draw the form element at x:0, y:0
let transform = ctx.drawElementImage(form_element, 0, 0);
};In WebGL context, use the
texElementImage2Dmethod:
canvas.onpaint = () => {
if (gl.texElementImage2D) {
gl.texElementImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
gl.RGBA,
gl.UNSIGNED_BYTE,
uiElement,
);
}
};In WebGPU context, use the
copyElementImageToTexturemethod:
canvas.onpaint = () => {
root.device.queue.copyElementImageToTexture(valueElement, 512, 128, {
texture: targetTexture,
});
};When using a requestAnimationFrame loop to render the scene, call canvas.requestPaint() within the loop to ensure that the HTML content is rendered to the canvas. Make sure you only re-render the canvas if there has been an update to the descendant HTML elements:
function render() {
// Request to update the canvas
canvas.requestPaint();
requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas.onpaint = (event) => {
if (event.changedElements && event.changedElements.length > 0) {
// Update the texture with drawElementImage, texElementImage2D, or copyElementImageToTexture, and update the CSS transform as shown in step 6
}
};Update the CSS transform.
For the 2D context case, apply the transform returned by the rendering call to the
style.transformproperty:
canvas.onpaint = () => {
ctx.reset();
// Draw the form element at x:0, y:0
let transform = ctx.drawElementImage(form_element, 0, 0);
// Sync the DOM location with the drawn location
form_element.style.transform = transform.toString();
};For the 3D case with WebGL or WebGPU, the browser needs to map from the 3D coordinate space into the CSS coordinate space using a viewport transform. To facilitate this, do the following:
Convert WebGL MVP Matrix to DOM Matrix.
Normalize the HTML element. HTML elements are sized in pixels (for example, 200px wide). WebGL, however, usually treats objects as "unit squares", for example, ranging from 0 to 1. If you don't normalize, your 200px button will look 200 times larger.
Map to the canvas viewport. This step is the "re-scaling" phase: it stretches that unit-space math back out to match the actual pixel dimensions of your
<canvas>element on the screen. It also flips the Y-axis, because in WebGL, up is positive, but in CSS, down is positive.Calculate the final transform. Multiply the matrices in order: Viewport _ MVP _ Normalization. Combining them into one final transform produces a "map" that tells the browser exactly where that HTML element layer should sit to align with the 3D drawing.
Apply the transform to the HTML element. This moves the HTML element layer to sit directly on top of its rendered pixels. This ensures that when a user clicks a button or selects text, they are actually hitting the real HTML element.
if (canvas.getElementTransform) { // 1. Convert WebGL MVP Matrix to DOM Matrix const mvpDOM = new DOMMatrix(Array.from(htmlElementMVP)); // 2. Normalize the HTML element (Canvas Grid pixels -> WebGL Model Space) const dprX = canvas.width / canvas.clientWidth; const dprY = canvas.height / canvas.clientHeight; const gridWidth = targetHTMLElement.offsetWidth * dprX; const gridHeight = targetHTMLElement.offsetHeight * dprY; const toGLModel = new DOMMatrix() // Scale pixels to 1 unit, flip Y (as in CSS it points down, and in WebGL it points up) .scale(1 / gridWidth, -1 / gridHeight, 1 / gridHeight) // Center the origin: (0,0) becomes (-width/2, -height/2) before scaling .translate(-gridWidth / 2, -gridHeight / 2); // 3. Map to the canvas viewport const clipToCanvasViewport = new DOMMatrix() // Move center (0,0) to center of canvas .translate(canvas.width / 2, canvas.height / 2) // Scale normalized clip (-1..1) to viewport size .scale(canvas.width / 2, -canvas.height / 2, canvas.height / 2); // 4. Multiply: (Clip -> Pixels) * (MVP) * (pixels -> unit square) const screenSpaceTransform = clipToCanvasViewport .multiply(mvpDOM) .multiply(toGLModel); // 5. Apply to the transform const computedTransform = canvas.getElementTransform( targetHTMLElement, screenSpaceTransform, ); targetHTMLElement.style.transform = computedTransform.toString(); }
[Troubleshooting] If the developer is experiencing a mismatch in the DOM logical layout in 3D even after applying the CSS transform from step 5, check if the developer is experiencing the issue in Chromium 148 or earlier. If that's the case, check if
transform.is2Dis correctly set to false for a 3D DOMMatrix. If not, re-initialize the DOMMatrix which correctsis2Dto be false before applying the transform to the target HTML element. This issue is fixed in Chromium 149+, and if the developer is experiencing it in newer Chromium versions, the is2D value is not the cause:
if (transform.is2D) {
// Workaround for Chromium bug https://crbug.com/512171941
// affecting Chrome versions under 149 where `transform.is2D`
// is incorrectly true for a 3D DOMMatrix. The assignment
// below re-initializes the DOMMatrix which corrects is2D to be false.
transform = DOMMatrix.fromFloat64Array(transform.toFloat64Array());
}
targetHTMLElement.style.transform = computedTransform.toString();Example code
2D Canvas
<canvas id="canvas" layoutsubtree style="width: 400px; height: 200px;">
<div id="ui-element">
<p>
This text is rendered inside the canvas but is present in the DOM tree.
</p>
<input type="email" name="email" placeholder="enter your email" />
<button type="button">Submit</button>
</div>
</canvas>
<script>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const uiElement = document.getElementById("ui-element");
canvas.onpaint = () => {
ctx.reset();
// Draw the HTML element at x:0, y:0
const transform = ctx.drawElementImage(uiElement, 0, 0);
// Sync the DOM location with the drawn location
uiElement.style.transform = transform.toString();
};
// Handle resizing to match device pixels
const observer = new ResizeObserver(([entry]) => {
const dpc = entry.devicePixelContentBoxSize;
canvas.width = dpc
? dpc[0].inlineSize
: Math.round(entry.contentRect.width * window.devicePixelRatio);
canvas.height = dpc
? dpc[0].blockSize
: Math.round(entry.contentRect.height * window.devicePixelRatio);
canvas.requestPaint();
});
const supportsDevicePixelContentBox =
typeof ResizeObserverEntry !== "undefined" &&
"devicePixelContentBoxSize" in ResizeObserverEntry.prototype;
const options = supportsDevicePixelContentBox
? { box: "device-pixel-content-box" }
: {};
observer.observe(canvas, options);
</script>WebGL Canvas
<canvas id="canvas" layoutsubtree style="width: 400px; height: 400px;">
<div id="ui-element">
<p>WebGL UI Element</p>
<button>Action</button>
</div>
</canvas>
<script>
const canvas = document.getElementById("canvas");
const gl = canvas.getContext("webgl");
const uiElement = document.getElementById("ui-element");
// Setup WebGL texture...
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
canvas.onpaint = () => {
// 1. Update texture with HTML content
if (gl.texElementImage2D) {
gl.texElementImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
gl.RGBA,
gl.UNSIGNED_BYTE,
uiElement,
);
}
// ... Render your 3D scene here, calculating htmlElementMVP matrix ...
// 2. Sync DOM position with 3D scene
if (canvas.getElementTransform) {
const mvpDOM = new DOMMatrix(Array.from(htmlElementMVP));
// Recalculate the DPR compensation mapping
const dprX = canvas.width / canvas.clientWidth;
const dprY = canvas.height / canvas.clientHeight;
const gridWidth = uiElement.offsetWidth * dprX;
const gridHeight = uiElement.offsetHeight * dprY;
const cssToUnitSpace = new DOMMatrix()
.scale(1 / gridWidth, -1 / gridHeight, 1 / gridHeight)
.translate(-gridWidth / 2, -gridHeight / 2);
const clipToCanvasViewport = new DOMMatrix()
.translate(canvas.width / 2, canvas.height / 2)
.scale(canvas.width / 2, -canvas.height / 2, canvas.height / 2);
const screenSpaceTransform = clipToCanvasViewport
.multiply(mvpDOM)
.multiply(cssToUnitSpace);
const computedTransform = canvas.getElementTransform(
uiElement,
screenSpaceTransform,
);
uiElement.style.transform = computedTransform.toString();
}
};
</script>WebGPU Canvas
<canvas id="canvas" layoutsubtree style="width: 400px; height: 400px;">
<div id="ui-element">
<p>WebGPU UI Element</p>
</div>
</canvas>
<script>
const canvas = document.getElementById("canvas");
const context = canvas.getContext("webgpu");
const uiElement = document.getElementById("ui-element");
// Setup WebGPU...
// const device = ...
// const targetTexture = ...
canvas.onpaint = () => {
// 1. Copy HTML content to texture
if (device.queue.copyElementImageToTexture) {
device.queue.copyElementImageToTexture(uiElement, width, height, {
texture: targetTexture,
});
}
// 2. Sync DOM position (same matrix math as WebGL)
if (canvas.getElementTransform) {
const mvpDOM = new DOMMatrix(Array.from(htmlElementMVP));
// Recalculate the DPR compensation mapping
const dprX = canvas.width / canvas.clientWidth;
const dprY = canvas.height / canvas.clientHeight;
const gridWidth = uiElement.offsetWidth * dprX;
const gridHeight = uiElement.offsetHeight * dprY;
const cssToUnitSpace = new DOMMatrix()
.scale(1 / gridWidth, -1 / gridHeight, 1 / gridHeight) // Retain Z scale
.translate(-gridWidth / 2, -gridHeight / 2);
const clipToCanvasViewport = new DOMMatrix()
.translate(canvas.width / 2, canvas.height / 2)
.scale(canvas.width / 2, -canvas.height / 2, canvas.height / 2); // Retain Z scale
const screenSpaceTransform = clipToCanvasViewport
.multiply(mvpDOM)
.multiply(cssToUnitSpace);
const computedTransform = canvas.getElementTransform(
uiElement,
screenSpaceTransform,
);
uiElement.style.transform = computedTransform.toString();
}
};
</script>Best Practices
MANDATORY: Check browser support for the HTML-in-Canvas API before using it.
MANDATORY: Always add the
layoutsubtreeattribute to the<canvas>element.MANDATORY: Use an
onpaintevent handler to render the HTML content to the canvas.MANDATORY: Use the
drawElementImage,texElementImage2D, orcopyElementImageToTexturemethods to render the HTML content to the canvas.MANDATORY: Update the CSS transform of the HTML element to match the transform of the rendered content by setting the
style.transformproperty of the HTML element.MANDATORY: Use
ResizeObserverto observe the screen size and update the canvas size to match device pixels.DO NOT embed cross-origin content in a canvas, as it is not supported.
DO NOT initialize
ResizeObserverwithin theonpaintevent handler, as it may lead to memory leaks.
Fallback strategies
HTML in canvas is not natively supported by any major browser yet.
The HTML-in-Canvas API is not currently supported in all modern browsers, thus a fallback strategy is typically required.
However, given the improved performance benefits of this API, HTML-in-Canvas should be used if the browser supports it.
The fallback strategy depends on the use case. For example, for an interactive HTML content in canvas, if HTML-in-Canvas is not supported, place the HTML content on top of the canvas using CSS.
HTML-in-Canvas polyfills
Use the following polyfill script to mimic the HTML-in-Canvas API in browsers that do not support it.
Install or embed the library:
# Install
npm install three-html-render# Embed
<script src="https://cdn.jsdelivr.net/npm/three-html-render/dist/polyfill.js"></script>Run the
installHtmlInCanvasPolyfill()method to translate HTML-in-Canvas.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs Node.js
Security
What our scanner noticed· Low risk
- Instructions that use the network
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Google Chrome
- Publisher
- JetBrains (Official)
- Distributed by
- JetBrains
- Source
- JetBrains/skills · modern-web-guidance · commit 3f53782
- Licence
- Apache License 2.0 (redistribution allowed)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Google Chrome or JetBrains.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download modern-web-guidance.zip