The classic way to free elements from their containers is position: absolute, which removes them from the layout flow. Newer techniques improve upon that disaster, but they require wrapper elements, ugly hacks, magic numbers, .foo > *, or other sins. I blundered into something better than all those.

Ta-da

  .bust-out { margin: 0 calc(50% - 50vw) }

Demo (and SCSS mixin)

Benefits

  • Works with the box model — margin, padding, border, box-sizing, and friends all do what you want.

  • Supports Internet Explorer 9+ and Android 4.4+. Doesn’t work in older Androids, but their viewports are narrow enough that it doesn’t need to. And the fallback is fine: the element as wide as its parent.

  • Usable on nearly any element without touching the HTML — great for restyling content.

How does it work?

Negative margins pull elements in the opposite direction they would normally push them. When opposite negative margins both pull, the element stretches to satisfy both.

If a percentage is used for margin, it takes that percentage from the parent’s width. So margin: 0 calc(50vw - 50%) means:

  1. Subtract half of the container’s width from half the viewport’s width.

  2. Move the element to the left and right by that amount, thereby stretching the element.

  3. The total amount of stretching is 100vw - 100%. That is, the screen width minus the parent’s width — exactly what we want!

Is this a hack?

Nah. Negative margins are CSS1, and this is exactly what calc() is for.

What if I don’t want it to be the full width of the viewport?

If you want margins that don’t butt up against browsers’ edges, modify the calc() equation to leave room for them:

  .bust-out-with-margins {
  margin-left: calc(50vw - 50% + [LEFT MARGIN HERE]);
  margin-right: calc(50vw - 50% + [RIGHT MARGIN HERE]);
}

If you want a maximum width, it’s a little trickier. You can try max-width:

  .bust-out-with-max-width {
  margin: calc(50vw - 50%);
  max-width: 800px;
}

But that clings to the left, because… reasons. I don’t know why it doesn’t work.

To fix, shimmy it to the right with transforms:

  .bust-out-with-max-width {
  margin: calc(50vw - 50%);
  max-width: 800px;
  transform: translateX(calc(50% - 50vw));
}

You could also use relative positioning.

Caution: that translateX(calc(50% - 50vw)) expression is almost the earlier margin: calc(50vw - 50%), but the units are switched.

Oh no, a horizontal scrollbar!

If you use width: 100vw to make an element as wide as the viewport, you may notice it triggers a horizontal scrollbar. It’s like the browser doesn’t think the vertical scrollbar exists and uses the viewport without it, which makes the element wide enough for the dreaded horizontal scroll.

Turns out that’s exactly what happens:

However, when the value of overflow on the root element is ‘auto’, any scroll bars are assumed not to exist.

It’s defined that way to avoid a circular dependency, such as defining something’s height with vw, such that if there are no scrollbars it’s taller than the viewport, which triggers a scrollbar, but if there are scrollbars it’s shorter than the viewport, so you don't need scrollbars.

Easy enough, right?

  html { overflow-y: scroll }

You may be have that anyway to avoid page jumping.

…But Firefox is the only browser compliant enough to do that. Internet Explorer, Edge, Chrome, and Safari when “Show scroll bars” is on all render the blasted scrollbar anyway. So instead…

  html { overflow-x: hidden }

I’ve seen “solutions” with -webkit- selector hacks that assume the scrollbar is 17 pixels wide, which is the least reliable thing I can imagine. For instance: touchscreens, Chrome on Windows, Firefox on OSX, and users configuring their scrollbar width.