Full-Bleed Images in Hugo and WordPress: Three Approaches Compared
Getting an image to break out of its containing column and run edge-to-edge across the viewport is one of those problems with three solutions, two of which are wrong in ways that only surface later.
The Problem
Hugo and WordPress both wrap post content in a constrained column — typically 680px to 800px wide — for readability. That column is intentional. The problem is that a hero image confined to that width reads as a thumbnail on any modern display. A full-width image signals scale, context, and editorial intent that a column-constrained image cannot.
The naive fix is to set max-width: none on the image. This breaks the image out of its inline flow but leaves the parent container in place, so the image overflows the right edge rather than centering across the viewport.
The Transform Approach
The classical full-bleed hack uses three declarations in combination:
<div style="width:100vw;position:relative;left:50%;transform:translateX(-50%);margin:2rem 0;">
<img src="image.jpg" style="width:100%;display:block">
</div>
left:50% shifts the div rightward by half the parent column width. transform:translateX(-50%) then pulls it back by half its own width — which is now 100vw — re-centering it against the viewport. The result is correct, but the mechanism is a two-pass correction: the browser calculates layout, applies the left shift, then applies the transform as a second operation.
The deeper problem is that transform creates a new stacking context. Any z-index behavior on child elements — or on overlapping ancestors like sticky navigation bars or modal overlays — becomes unpredictable. For a standalone image this is rarely an issue. In a template with layered UI components it will eventually surface as a bug.
The Calc Approach
The same visual result can be achieved in a single declaration:
<div style="width:100vw;margin:2rem 0 2rem calc(50% - 50vw)">
<img src="image.jpg" style="width:100%;display:block">
</div>
calc(50% - 50vw) evaluates at layout time. 50% resolves to half the parent column width; 50vw resolves to half the viewport width. The difference is exactly the distance from the column's left edge to the viewport's left edge, expressed as a negative margin that snaps the div flush to the viewport without repositioning or transforming.
No stacking context is created. No second pass is required. The margin shorthand carries all four sides — top, right, bottom, left — so vertical spacing and the horizontal correction are expressed in one attribute.
The Negative Margin Alternative
Some implementations use explicit negative margins with a corresponding positive padding:
<div style="width:100vw;margin-left:-9999px;padding-left:9999px;">
<img src="image.jpg" style="width:100%;display:block">
</div>
This approach predates vw units and calc() and appears in legacy WordPress themes from the early 2010s. It produces horizontal scrollbars in most browsers unless overflow:hidden is set on a parent element, which introduces its own containment side effects. There is no reason to use it on any site built after 2015.
Compatibility Notes
Both the transform and calc approaches require that the CMS renders the raw HTML without stripping inline styles. In Hugo, this requires unsafe: true under [markup.goldmark.renderer] in config.toml. In WordPress Classic, inline styles on div elements survive sanitization for users with unfiltered_html capability, which covers administrator and editor roles by default.
The calc version also requires that the post body not be wrapped in an element with overflow:hidden, which would clip the full-width div before it reaches the viewport edge. Most well-maintained themes do not do this, but pagebuilder-generated layouts occasionally do.
The Recommended Pattern
<div style="width:100vw;margin:2rem 0 2rem calc(50% - 50vw)"><img src="image.jpg" alt="Description" style="width:100%;display:block"></div>
One element. One attribute per element. No positioning. No transform side effects. display:block on the image eliminates the default inline baseline gap that would otherwise introduce a few pixels of space beneath it. The vertical margin is carried inside the shorthand rather than as a separate declaration.
The transform approach is more widely documented and will appear in more Stack Overflow answers. That is a reflection of when those answers were written, not of which technique is correct.