Skip to content

Build a Browser-Based Treatment That Reflows Without Losing Its Reading Order

Advertising

Build a Browser-Based Treatment That Reflows Without Losing Its Reading Order

A fixed canvas keeps a treatment's relationships by holding everything still. Position does the work: two frames sit shoulder to shoulder, a paragraph sits under the image it explains, and the comparison happens in the reader's eye without a word of help. A reflowing page can't promise that. At the narrow end, position is provisional. The two frames are no longer side by side, and something else has to carry what proximity was carrying.

So the decision isn't "does it fit at 390 pixels." It's which relationships in this treatment are load-bearing, and what carries each one when the width is gone. Once you've answered that, the implementation is short. Before you've answered it, no amount of grid syntax will help, because you'll be arranging content whose order you never settled.

Decide what the fixed composition is doing

Go through the desktop composition and sort the relationships into two piles.

The first pile genuinely needs simultaneity. Two frames of the same shot rendered in different light. A before-and-after where the whole point is that the figure hasn't moved. A table of three options whose comparison only works if all three are visible at once. These are relationships where seeing one, then seeing the other, is a different experience from seeing both.

The second pile just inherited a slide. The logo in the corner. The three-paragraph block that happens to sit left of the image because that's how the master slide was built. The pull quote parked in the margin. Nothing breaks if these move; they were only ever arranged, not designed.

Now price the fixed canvas honestly. Suppose the stage is 1280×720. If you scale it to fit a 390-pixel viewport, everything scales — including type. 390 ÷ 1280 × 16 works out to about 4.9 pixels for a 16-pixel caption. A 24-pixel subhead lands near 7.3. Nobody reads that, and transform: scale() doesn't reflow text anyway, so the line breaks stay exactly where the canvas put them. Your options at that size are panning sideways, illegible type, or a screenshot, which is not a reading experience at all.

What survives the sort is smaller than you'd expect, and that's the useful part. A fixed panel is a good container for one bounded visual comparison you've decided to protect. It's a bad container for a treatment's whole argument.

Write the reading sequence before you arrange the grid

Here is a fictional treatment to work on. It's for Last Boat, an invented 30-second spot for an invented ferry operator, and the images are placeholders standing in for real references.

Four sections, in this order:

  1. The read. One paragraph and one landscape reference: the terminal at 06:40, doors open, waiting room empty.
  2. Two passengers. Two portrait references, compared — Nadia, who doesn't get out of her car, and a teenager already asleep against the window.
  3. One crossing, two lights. The same frame twice, clear and in rain.
  4. The last ten seconds. A silent six-second loop: the gangway folding shut, deck lights going out bank by bank.

That list is the source order, and it should exist before any layout does. Not because linear order is morally superior, but because it's the order that survives when everything else is taken away — when the grid collapses, when the stylesheet fails, when someone reads the page in a screen reader, when the treatment gets exported to a single-column handout, when a colleague greps the file for the paragraph about Nadia.

MDN's page on grid layout and accessibility records the relevant mechanism: visual grid placement doesn't by itself change source reading order or normal sequential keyboard navigation. That's a useful fact and a dangerous one. It means a layout can look right while the linear order underneath it is wrong, and nothing in the browser will tell you. Grid placement, the order property, and dense auto-placement can each put visible content somewhere other than where it sits in the document. The page you inspect on screen is not the page a keyboard follows.

Here's the markup, with the reading order intact:

<article class="treatment">
  <h1>Last Boat — treatment, v4</h1>

  <section>
    <h2>The read</h2>
    <p>The crossing after midnight runs almost empty. The spot watches
       the people who take it anyway.</p>
    <figure>
      <img src="terminal-0640.jpg" width="1600" height="900"
           alt="The ferry terminal at 06:40, doors open, waiting room empty.">
      <figcaption>The terminal at 06:40, doors already open.</figcaption>
    </figure>
  </section>

  <section class="passengers">
    <h2>Two passengers</h2>
    <p>Nadia and the boy share one gesture: neither of them moves.</p>
    <div class="pair">
      <figure>
        <img src="nadia.jpg" width="900" height="1200"
             alt="A woman in her sixties, still in the driver's seat, engine off.">
        <figcaption>Nadia doesn't get out of the car.</figcaption>
      </figure>
      <figure>
        <img src="boy.jpg" width="900" height="1200"
             alt="A teenager asleep against the window, hood up.">
        <figcaption>The boy is already asleep.</figcaption>
      </figure>
    </div>
  </section>

  <section class="lights">
    <h2>One crossing, two lights</h2>
    <p>The same frame twice, same lens, same minute. Only the weather changes.</p>
    <div class="pair">
      <figure>
        <img src="crossing-clear.jpg" width="1200" height="675"
             alt="The crossing at 06:40 in clear weather.">
        <figcaption>06:40, clear.</figcaption>
      </figure>
      <figure>
        <img src="crossing-rain.jpg" width="1200" height="675"
             alt="The crossing at 06:40 in rain.">
        <figcaption>06:40, rain.</figcaption>
      </figure>
    </div>
  </section>

  <section>
    <h2>The last ten seconds</h2>
    <video controls muted loop playsinline
           poster="gangway-poster.jpg" width="1280" height="720">
      <source src="gangway.mp4" type="video/mp4">
    </video>
    <p>The gangway folds shut. The deck lights go out one bank at a time,
       fore to aft.</p>
  </section>
</article>

Three things in there are doing quiet work. The width and height attributes on every image reserve the right amount of space before the file arrives, so the page doesn't jump under a reader who's already started. The passengers and lights classes name the two sections that share a wide band, and a class travels with its element when the markup moves. And the paragraph above the two-light pair states the comparison — same frame, same minute, only the weather — while the captions do nothing but identify. Hold onto that division. It's the mechanism that makes the narrow version work.

Translate juxtaposition into a narrow-screen relationship

.treatment {
  max-width: 62rem;
  margin-inline: auto;
  padding: 1.25rem;
  display: grid;
  gap: 2.5rem;
}

.treatment p { max-width: 68ch; }

figure { margin: 0; }
figure img, figure video { display: block; width: 100%; height: auto; }
figcaption { margin-top: 0.5rem; font-size: 0.9375rem; }

.pair {
  display: grid;
  gap: 1rem;
  align-items: start;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
}

That last line is the whole responsive strategy for paired references, and the mechanism is worth naming because it's the difference between a layout that adapts and one that only looks like it does. The minimum track is 18rem — root-relative, so it grows when the reader raises their default font size. Two tracks plus the gap need about 37rem of container. When the container can't supply it, auto-fit drops to one column. Nothing in that rule knows about screen width; it knows about available space and text size together.

Compare that with a viewport media query, which is the thing most people reach for first. @media (min-width: 40rem) responds to the viewport and nothing else. A reader who sets a 32-pixel base font on a 1400-pixel monitor gets the two-column layout anyway, and each column now holds text at twice its intended size. The images survive because images scale; the captions don't. Root-relative minimums collapse on their own, which is why the rule above needs no media query at all.

At a 16-pixel root size, that pair stacks below roughly a 630-pixel viewport. Put two portrait references side by side above that line and stack them below it, and here's what happens to the argument: Nadia is on top, the boy underneath, and the sentence that connects them — neither of them moves — is right there above both. Nothing was lost except the ability to see them at once. For a comparison of two gestures, that's a survivable loss. For the two-light pair in section 3 it would be a real loss, which is precisely why the comparison was written into the prose instead of resting on adjacency. Captions identify. Prose compares. If the comparison sentence lives in a caption attached to only one of the pair, stacking breaks it, and no amount of grid work will recover it.

Two things to leave alone. Don't put a fixed height on a caption or a figure to make columns line up — the caption boxes will clip the moment the text grows, and a reader at 200% gets half a sentence. And don't paint yourself into a corner with viewport units: 100vh panels on mobile browsers have a well-known habit of hiding content or jumping as the browser chrome appears and disappears. You may also be tempted by container queries, which let a component respond to its own width rather than the viewport's. That's the right instinct, but check current support and behavior in the browsers you actually ship to before you make one load-bearing. If your paired references depend on it and it fails, the pair stacks in an order nobody chose.

The reorder that hides itself

Now break it, deliberately.

Suppose the desktop mock showed section 3 in a wide band beside section 2. That band comes from a two-column template on the treatment, applied once there's room — the breakpoint is in em, so it follows the reader's default font size rather than the page's root size:

@media (min-width: 62em) {
  .treatment {
    grid-template-columns: 1fr 1fr;
    align-items: start;
  }
  .treatment > section { grid-column: 1 / -1; }
  .treatment .passengers { grid-column: 1; }
  .treatment .lights { grid-column: 2; }
}

The person building it moved the markup for section 3 below section 4 to make that easier, then pinned it back into the row the mock showed:

.treatment .lights { grid-row: 2; grid-column: 2; }

The pin names the section by class, and that part is right. A positional selector like section:nth-of-type(3) indexes document position rather than identity: move the markup and it silently points at a different section. The class travels with the element, so the pin follows section 3 wherever it has been moved to. It is also written without a query, as such overrides usually are, which will matter in a moment.

On a wide screen the mock matches, so nobody reads the source. Everything looks finished. What's actually happened is that explicit placement is now compensating for a document whose linear order no longer makes sense on its own, and explicit placement doesn't switch off when the grid changes shape.

Collapse to one column and the pin is still there, asking for a track the narrow layout doesn't have. The browser invents one. The two-light comparison comes back as a band beside the passengers — the desktop arrangement restored at exactly the width where it can't be read — and the display order and the document order now disagree in a second way on top of the first.

Three symptoms, same cause. Print it or export it to a one-column handout and the sections arrive in the wrong sequence, because the export follows the document. Tab through it and focus moves in document order, so a keyboard reader meets The last ten seconds before the two-light comparison — a sequence no version of the layout shows. Read it in a screen reader and the headings announce the document's order, not the screen's. The visual layout was never wrong; the source was, and the layout was covering for it.

The repair is to fix the source, not the CSS. Put section 3 back where the argument needs it and delete the pin; the two-column template then places the band from the document, and there's nothing left to un-pin when the grid collapses. If you'd rather not hand each section a column, wrap sections 2 and 3 in a container that declares two columns for those two only. order and dense auto-placement deserve the same suspicion, for the same reason: both move things on screen without moving them in the document.

Keep media and a static edition honest

The six-second loop needs more care than the stills, because it can fail in ways the stills can't.

It should not autoplay. If the client insists, keep it silent and short and honor a reduced-motion preference — an unattended looping video at the top of a treatment is an attention tax on every reader who scrolls past it. Set a poster frame so the section has a face before anything loads. More importantly, the loop is silent and has no spoken content, which means it needs a description rather than captions: the sentence in the markup above — the gangway folds shut, deck lights out one bank at a time — is doing that work, and it should stay visible under the player rather than being tucked into a metadata file that only some readers will encounter. If the video fails to load, that sentence is all that's left, so it can't be the player's own caption track.

Nadia and the boy are placeholders, incidentally. Compose the narrow version as if every reference in a treatment will eventually be swapped for something the client approved, so the captions carry meaning that doesn't depend on this exact photograph.

Then, separately, the downloadable edition. Two files with nearly the same content will drift apart unless you make the relationship explicit, so label both with the same version string and say in the document what the static version does and doesn't contain. A PDF generated from this source is a one-column snapshot: same sections, same order, same captions, and a described three-still sequence where the six-second loop used to be. It is the same argument, not the same experience, and saying so is more useful than implying equivalence. A screenshot of the reflowed page is neither — it has no alt text, no selectable captions, and no version relationship, and it will be out of date by Thursday.

Paginating that PDF properly — running heads, orphan control, where the figure lands relative to its caption across a page break — is its own composition job, and a longer one than it sounds. Treat the browser treatment and the print edition as two pieces of work with one shared source, not one piece of work in two formats.

Inspect the real page under changed reading conditions

Here is where a prototype earns or loses its claim. The markup and CSS above are untested scaffolding; these checks are what tells you whether they hold.

Narrow. Load the page and drag the window down to 320 CSS pixels. Watch for horizontal scroll on the page — that's the failure that breaks everything downstream. Its usual causes are an image without a width constraint, a long unbreakable string, or a white-space: nowrap nobody remembers writing. overflow-wrap: break-word on the article handles most of the accidental cases. Then confirm the pairs actually stacked and that the connecting sentence is still above them, not orphaned below.

Wide. Check that the paired references sit as intended and that nothing has been placed by a rule you can't find. If a section moves when you resize, work out which property moved it.

Text enlargement. Enlarge text to twice its normal size, then four times, and check that nothing is clipped, cut off, or reachable only by scrolling in two directions at once. A useful working target: a 320-CSS-pixel viewport and 400% zoom, where everything should still be reachable by scrolling in one direction. The captions and the six-second description are the first things to go — fixed heights and tight line boxes fail here before anything else does.

Keyboard. Tab from the top and write down the order you actually traverse: headings, then the figure links if any, then the video controls. If the order doesn't match the reading sequence, you have a source-order problem, not a CSS problem. Check the focus ring is visible against the treatment's own backgrounds — an invisible focus indicator is indistinguishable from a broken page for a keyboard reader. And keep tabindex values at zero or absent; raising one above zero does change the order, and it does so in a way that's hard to maintain.

Media. Play the loop with sound off. Read the description and ask whether someone who never sees the video would understand what those six seconds are for. Then break it deliberately — block the file, or rename the source — and confirm the section still reads.

And read, don't skim. Automated checkers will catch a missing alt attribute or a skipped heading level. They cannot tell you whether the two-light comparison still reads as a comparison after it stacked, or whether the sentence about Nadia still makes sense on its own. That judgment is the work, and it doesn't automate.

The version that survives

A finished reflowing treatment looks unremarkable in a thumbnail, which is exactly why the thumbnail proves nothing. What you're checking is whether the argument still arrives in the same order with the width taken away, whether the paired references carry their connection in words rather than proximity, and whether the exceptions are stated out loud rather than left for a reader to discover.

One last diagnostic, and it's the cheapest one available. Export the page to a single-column PDF and read it. If the treatment makes sense in that flat sequence, your source order is sound — and the export is only a rough check, but it's a rough check that fails loudly. If the source order has been quietly rearranged to suit a desktop grid, the export will say so before the client does.

Preserve the argument, not the coordinates. A fixed panel that protects one bounded visual comparison is a fine thing to keep, and it should be named as the exception it is. Everything else should be arranged from a sequence that already made sense on its own, so that when the layout collapses, there's nothing left to reconstruct.

Frequently asked questions

What is the first decision when making a treatment reflow?

Decide which relationships are load-bearing and what will carry each one when width is gone. A fixed canvas can protect one bounded visual comparison, but it cannot carry a treatment’s whole argument if scaling makes type illegible and line breaks stay fixed.

Why write a reading sequence before arranging the grid?

The source order survives collapsed layouts, stylesheet failure, screen readers, single-column export, and file searches. Visual grid placement does not by itself change source reading order or normal sequential keyboard navigation, so a layout can look right while the document order is wrong.

What can go wrong when a section is moved in markup and pinned back with CSS?

Explicit placement can compensate for a source order that no longer makes sense. When the grid collapses, the pin can still ask for a track the narrow layout does not have, restoring a desktop band where it cannot be read and making display order and document order disagree. The repair is to fix the source, not the CSS.

How should a six-second loop be handled?

It should not autoplay; if it must, keep it silent and short and honor reduced-motion preference. Set a poster frame, and keep a visible description under the player because the loop is silent and has no spoken content. That sentence is also the fallback if the video fails.

What is the difference between the browser treatment and a static PDF edition?

A PDF generated from the source is a one-column snapshot with the same sections, order, captions, and a described three-still sequence where the loop was. It is the same argument, not the same experience, so label both with the same version string and state what the static version does and does not contain.

More in Advertising Browse all articles