In the DOM, why does reading `el.style.width` often return an empty string while `el.offsetWidth` returns a number, and which of the two can make the browser do layout work?
answer
- two different notions of width
- inline attribute versus rendered box
- style reads back what you set
- offsetWidth counts padding and border
- rendered numbers can cost layout
basics
~20 sel.style.width reads only the element's inline style attribute, so it is empty unless something set it inline. el.offsetWidth reports the element's actual rendered border-box width, so the browser may have to compute pending layout before it can answer.
solid answer
~40 sThey answer two different questions. `el.style` is a `CSSStyleDeclaration` over the element's **inline** `style` attribute only — if the width came from a stylesheet rule, `el.style.width` is `""`, and it never tells you what the element actually looks like. `el.offsetWidth` is a layout-derived number: the rendered border-box width, including padding and border, excluding margin, rounded to an integer. Because it has to reflect reality, the browser must have up-to-date layout to answer it, and will recompute pending style and layout synchronously if anything changed since the last update. So the inline read is a cheap attribute lookup; the geometry read can be one of the most expensive lines in a frame. `getComputedStyle(el).width` sits between them: it gives the resolved pixel width from any source, and it too needs current layout.
code
javascript · 11 linesconst style = document.createElement('style');
style.textContent = '.box { width: 100px; padding: 10px; border: 2px solid; }';
document.head.append(style);
const box = document.createElement('div');
box.className = 'box';
document.body.append(box);
console.log(box.style.width); // "" - nothing was set inline
console.log(getComputedStyle(box).width); // "100px" - used content width
console.log(box.offsetWidth); // 124 - content + padding + bordergo deeper
Be ready to say that el.style exposes only the inline style attribute, and that offsetWidth is the rendered border-box width returned as an integer.
Explain all three ladders — the inline declaration, getComputedStyle's resolved value, and the layout-derived offset and rect numbers — and say which of them need up-to-date layout.
Point out that the layout-derived reads are the ones that can stall a frame, and describe where in a component's update cycle you would take a measurement so it costs a single flush.
Own the guidance: agree a house rule for where in the component lifecycle measurement is allowed to happen, so measurement cost stays predictable as the number of components grows.
## Three different "widths" An element can tell you its width in at least three ways, and they are not interchangeable. 1. **The inline declaration** — `el.style.width`. This is the `style` **attribute** on that one element, exposed as a `CSSStyleDeclaration`. It is a plain string you can read and write. 2. **The resolved value** — `getComputedStyle(el).width`. This is what the cascade produced for this element after stylesheets, inheritance and the default styles, expressed in absolute units. 3. **The rendered geometry** — `el.offsetWidth`, `el.clientWidth`, `el.getBoundingClientRect().width`. These are numbers taken from the box the browser actually laid out on the page. ## Why `style.width` is usually empty `el.style` is not a view of the cascade. It is a view of the inline attribute. If the width comes from a stylesheet rule, a `<link>`ed file, or nothing at all, there is no inline declaration to report and the property is the empty string. It becomes non-empty only when markup wrote `style="width: …"` or JavaScript assigned `el.style.width = …`. This catches people out in both directions: reading it to "find out how wide the element is" returns nothing useful, and writing to it silently adds an inline declaration that outranks most stylesheet rules. ## What `offsetWidth` measures `offsetWidth` is the **border-box** width of the element's rendered box: content plus padding plus border, excluding margin. It is an integer, rounded from the real fractional layout value. For an element that is not rendered at all — `display: none`, or detached from the document — it is `0`, because there is no box. ```js // .box { width: 100px; padding: 10px; border: 2px solid } box.style.width; // "" nothing was set inline getComputedStyle(box).width; // "100px" the used content width box.offsetWidth; // 124 100 + 2*10 + 2*2 ``` `getBoundingClientRect()` is the more precise sibling: it returns a `DOMRect` with fractional values, positioned relative to the viewport, and it reflects CSS transforms applied to the element. `offsetWidth` is the untransformed layout width, rounded. ## Which one costs the browser something Reading the inline attribute is a string lookup — effectively free. The layout-derived numbers are not, because the value they describe may not exist yet. The browser batches DOM and style changes and normally recomputes layout once, right before it paints a frame. A geometry read is defined to return a current value, so if anything is still pending the browser must recalculate style and run layout **immediately**, in the middle of your script, before the read returns. That is a forced synchronous layout. `getComputedStyle(el).width` is in the same category: to give you a pixel value for `width` it needs layout to have run. Asking it for a property that layout does not affect, such as `color`, only needs style to be current. ## Practical rules - To find out what you set inline, read `el.style.x`. To find out what the element actually is, read geometry or computed style. - Never assume `el.style.width` round-trips a stylesheet value; it does not. - Remember `offsetWidth` includes padding and border while `clientWidth` includes padding but not border, and `scrollWidth` describes the content including what overflows. - Treat geometry reads as calls that can do work, not as fields. One read is fine; a read taken repeatedly in a loop that also writes styles is the expensive pattern.
- What does offsetWidth report for an element with display: none?Zero. `display: none` means the element generates no box at all, so there is no rendered geometry to report, and every offset and client measurement comes back as `0`. The same is true for an element that is not attached to the document. This is why code that measures a hidden element before revealing it gets zeros and has to measure after it is rendered.
- How does getBoundingClientRect().width differ from offsetWidth?`getBoundingClientRect()` returns fractional values, is measured relative to the viewport, and reflects CSS transforms — a scaled element reports its scaled size. `offsetWidth` is the untransformed layout width rounded to an integer, relative to nothing in particular. Use the rect when you need sub-pixel precision or on-screen position; either one can force the browser to compute pending layout.
saying these in an interview costs you the question
- Thinks el.style reflects stylesheet rules, not just inline ones
- Says offsetWidth excludes padding and border
- Treats offsetWidth as free because it looks like a field
- Uses clientWidth and offsetWidth interchangeably
- Expects style.width to return a number rather than a string