Skip to content

Component

stretch

Demo

Live demo, isolated in an iframe with only tokens.css, base.css and stretch.css. No scripts.

Source

Copy both files, or run npx nojsui add stretch.

Source for stretch
<!-- .sk-stretch — make an element fill its container minus its own margins,
     which is what inline-size: 100% looks like it does and does not.

     The utility is the one class on the third button. The demo puts the same
     button, with the same inline margins, in three dashed frames so the
     difference is visible: auto shrink-wraps, 100% runs past the frame's
     edge by its margins, and .sk-stretch fills the frame exactly.

     The labels are code, so they are dir="ltr": in a right-to-left page the
     bidi algorithm would otherwise move ".sk-stretch"'s leading period to
     the end. -->
<div class="sk-stretch__demo">
  <p class="sk-stretch__label"><code dir="ltr">inline-size: auto</code></p>
  <div class="sk-stretch__frame">
    <button type="button">Shrink-wraps its label</button>
  </div>

  <p class="sk-stretch__label"><code dir="ltr">inline-size: 100%</code></p>
  <div class="sk-stretch__frame" data-size="percent">
    <button type="button">Overflows by its margins</button>
  </div>

  <p class="sk-stretch__label"><code dir="ltr">.sk-stretch</code></p>
  <div class="sk-stretch__frame">
    <button type="button" class="sk-stretch">Fills the frame, margins included</button>
  </div>
</div>
/* .sk-stretch — fill the space you were given, margins included.

   `inline-size: 100%` is what everyone writes to make a button or an input fill
   its container, and it is subtly wrong: 100% is the size of the containing
   block, and margins are added OUTSIDE it. Give the element any inline margin
   and it overflows by exactly that margin. `box-sizing: border-box` (base.css)
   fixes padding and border, never margin.

   `inline-size: stretch` is the size that `auto` already gives an ordinary
   block — the containing block minus the margins — available to elements that
   would otherwise shrink-wrap: buttons, inputs, selects, inline-blocks, flex
   items, absolutely positioned boxes. It is what 100% was meant to be.

   An ordinary block in normal flow does not need this. `auto` already fills.

   Specs:
   - stretch ............... https://drafts.csswg.org/css-sizing-4/#valdef-width-stretch
   - MDN ................... https://developer.mozilla.org/docs/Web/CSS/width#stretch

   No theme knobs: this is layout, and ADR 0011 keeps knobs for appearance. */

/* The fallback. Right whenever the element has no inline margins, which is the
   common case; with margins it overflows by them. The README's degradation
   table says so. */
.sk-stretch {
  inline-size: 100%;
}

/* Chromium 138+, WebKit. Gecko reads the 100% above. */
@supports (inline-size: stretch) {
  .sk-stretch {
    inline-size: stretch;
  }
}

/* ---------------------------------------------------------------------------
   Presentation, not behaviour

   The demo puts the same button, with the same inline margins, in three
   frames: `auto`, `100%` and `.sk-stretch`. The frame's dashed edge is what
   shows the 100% one overflowing. Delete all of this when copying the utility.
--------------------------------------------------------------------------- */

.sk-stretch__demo {
  display: grid;
  gap: var(--sk-space-md);
  padding-inline: var(--sk-space-xl);
}

.sk-stretch__label {
  margin: 0;
  color: var(--sk-color-text-subtle);
  font-family: var(--sk-font-mono);
  font-size: var(--sk-text-xs);
}

.sk-stretch__frame {
  border: var(--sk-border-width) dashed var(--sk-color-border-strong);
  border-radius: var(--sk-radius-sm);
  padding-block: var(--sk-space-xs);
}

.sk-stretch__frame > button {
  display: block;
  margin-inline: var(--sk-space-lg);
  padding-block: var(--sk-space-xs);
  padding-inline: var(--sk-space-md);
  border: var(--sk-border-width) solid var(--sk-color-accent);
  border-radius: var(--sk-radius-md);
  background: var(--sk-color-accent-subtle);
  color: var(--sk-color-text);
  font: inherit;
}

.sk-stretch__frame[data-size="percent"] > button {
  inline-size: 100%;
}

Browser support

Baseline widely available

Works across current and earlier versions of every major engine.

Per-feature support, generated from web-features 3.39.0
FeatureBaselineChromeEdgeFirefoxSafariChrome AndroidFirefox AndroidSafari iOS
Enhancements — the component works without these; they add polish. Their status does not affect the badge above.
FeatureBaselineChromeEdgeFirefoxSafariChrome AndroidFirefox AndroidSafari iOS
stretchlimited138138—27138—27

Usage

Add the class to an element that would otherwise shrink-wrap — a button, an input, a select, an inline-block — to make it fill its container:

<button type="submit" class="sk-stretch">Create account</button>

It is a utility, not a component with parts. One class, one declaration, and it combines with anything: <button class="sk-button sk-stretch">.

How it works

Why not inline-size: 100%

100% is the size of the containing block. Margins are added outside that, so an element with any inline margin overflows its container by exactly that margin. box-sizing: border-box (which base.css sets) absorbs padding and border. It never absorbs margin.

inline-size: stretch is the containing block minus the margins: the size an ordinary block already gets from auto. The keyword makes that size available to the elements that auto shrink-wraps instead:

Elementauto gives100% givesstretch gives
A <div> in normal flowFills, minus marginsFills, plus margins (overflows)Fills, minus margins
A <button>, <input>, <select>Its content’s widthFills, plus marginsFills, minus margins
An absolutely positioned box with one inline insetIts content’s widthThe containing block, plus marginsThe containing block, minus margins

So the first row does not need this utility: a plain block already fills. The other rows do.

What it does not do

stretch fills the containing block, not the space left over in a row. In a flex row it makes the item as wide as the whole container, so the item wraps or squeezes its siblings. For “take the remaining space”, use flex: 1 on the item, not this.

Keyboard contract

None. This is a sizing utility. Whatever element you put it on keeps its own keyboard behaviour.

KeyBehaviour
TabUnchanged — focus order is the element’s own
Any other keyUnchanged

Verified manually in VoiceOver and NVDA: not applicable — the utility changes size only, and adds no element, role or state.

Accessibility notes

A wider control is an easier target, which is most of why forms on narrow screens stretch their buttons. Nothing about the element’s role, name or focus behaviour changes.

The 100% overflow this utility avoids has an accessibility side: content that runs past its container can cause sideways scrolling at high zoom (WCAG 1.4.10, Reflow). stretch cannot overflow by its margins.

Degradation

Feature usedBaseline statusBehavior without it
stretchlimitedinline-size: 100% instead. Identical when the element has no inline margins; with margins, it overflows the container by them. Firefox today. Avoid inline margins on a stretched element if Firefox matters to you, or put the space on the parent as padding.