DOM Manipulation and CSS Properties
SkillWeb & browsingBrowser rendering performance for Trezor Suite — forced synchronous layout from geometry reads, observer APIs instead of measuring an element, and transitioning only compositor properties. Use when measuring, scrolling or animating a DOM element, or when writing a CSS transition. Web and desktop only.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the DOM Manipulation and CSS Properties skill
What this skill tells your AI
The instructions your AI receives, as published by trezor/trezor-suite in skills/performance-dom/SKILL.md and read by ahel’s review.
Reads that force the browser to lay out again mid-frame, and animations that make it lay out on every frame. Web and desktop only.
Optimize DOM reads and writes, and never interleave them
All of the below properties or methods, when requested/called in JavaScript, will trigger the browser to synchronously calculate the style and layout*. This is also called reflow or layout thrashing, and is common performance bottleneck.
Generally, all APIs that synchronously provide layout metrics will trigger forced reflow / layout. Read on for additional cases and details
Getting box metrics
elem.offsetLeft,elem.offsetTop,elem.offsetWidth,elem.offsetHeight,elem.offsetParentelem.clientLeft,elem.clientTop,elem.clientWidth,elem.clientHeightelem.getClientRects(),elem.getBoundingClientRect()
Scroll stuff
-
elem.scrollBy(),elem.scrollTo() -
elem.scrollIntoView(),elem.scrollIntoViewIfNeeded() -
elem.scrollWidth,elem.scrollHeight -
elem.scrollLeft,elem.scrollTopalso, setting them -
elem.focus()(source) -
elem.computedRole,elem.computedName -
elem.innerText(source) -
window.scrollX,window.scrollY -
window.innerHeight,window.innerWidth -
window.visualViewport.height / width / offsetTop / offsetLeft (source)
-
document.scrollingElementonly forces style -
document.elementFromPoint -
inputElem.focus() -
inputElem.select(),textareaElem.select() -
mouseEvt.layerX,mouseEvt.layerY,mouseEvt.offsetX,mouseEvt.offsetY(source)
window.getComputedStyle() will typically force style recalc.
window.getComputedStyle() will often force layout, as well.
Details of the conditions where gCS() forces layout
window.getComputedStyle() will force layout in one of 3 conditions:
- The element is in a shadow tree
- There are media queries (viewport-related ones). Specifically, one of the following: (source
min-width,min-height,max-width,max-height,width,heightaspect-ratio,min-aspect-ratio,max-aspect-ratiodevice-pixel-ratio,resolution,orientation,min-device-pixel-ratio,max-device-pixel-ratio
- The property requested is one of the following: (source)
height,widthtop,right,bottom,leftmargin[-top,-right,-bottom,-left, or shorthand] only if the margin is fixed.padding[-top,-right,-bottom,-left, or shorthand] only if the padding is fixed.transform,transform-origin,perspective-origintranslate,rotate,scalegrid,grid-template,grid-template-columns,grid-template-rowsperspective-origin- These items were previously in the list but appear to not be any longer (as of Feb 2018):
motion-path,motion-offset,motion-rotation,x,y,rx,ry
range.getClientRects(),range.getBoundingClientRect()
SVG
Quite a lot of properties/methods force. This list in incomplete:
-
SVGLocatable:
computeCTM(),getBBox() -
SVGTextContent:
getCharNumAtPosition(),getComputedTextLength(),getEndPositionOfChar(),getExtentOfChar(),getNumberOfChars(),getRotationOfChar(),getStartPositionOfChar(),getSubStringLength(),selectSubString() -
SVGUse:
instanceRoot -
Lots & lots of stuff, including copying an image to clipboard (source)
// bad - collapse.tsx:37 - line two re-reads the clientWidth that line one's write just invalidated
inner.style.width = `${inner.clientWidth}px`;
container.style.width = `${inner.clientWidth}px`;
// good - one read, then the writes
const innerWidth = inner.clientWidth;
inner.style.width = `${innerWidth}px`;
container.style.width = `${innerWidth}px`;
- Where geometry can come from an observer, take it there: an
IntersectionObserverorResizeObservercallback runs after layout, so reading in it is cheap (example:useAnchor.ts:25). requestAnimationFramerequests the browser to call a user-supplied callback function before the next repaint.
Transition compositor properties, and never leave the property unnamed
width,height,top,marginandmin-widthre-run layout on every frame of the animationtransform,opacityare composited and do not.transition: allis the same defect with nothing named — and so is the bare-duration shorthand, because an omittedtransition-propertyresets toall.
// bad - ProgressBar.tsx:22 - lays out the bar every frame for half a second
width: ${({ $max, $value }) => `calc((100% / ${$max}) * ${$value})`};
transition: width 0.5s;
// good - laid out once at full width, then scaled
width: 100%;
transform-origin: left;
transform: scaleX(${({ $max, $value }) => $value / $max});
transition: transform 0.5s;
transform-originis load-bearing: the default origin is the centre, so without it every bar in the app grows from its middle. Scale is also the weakest case fortransform— a scale change re-rasters unlesswill-change: transformpins the texture, and then it stretches a bitmap — so it is safe for a solid childless fill like this one and wrong for a pill-shaped or labelled bar, where it distorts the radius and the text. Translation and opacity are the genuinely composite-only changes.
Related skills
- Asymptotic complexity — indexing, sorting and reducing over collections that grow.
- React hooks — memoization, dependency arrays, render loops.
- Long and non-essential tasks — yielding long tasks, deferring background work.
Signals
- GitHub stars
- 1k
- Forks
- 375
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
performance-dom- Source
- github.com/trezor/trezor-suite