RizTech Academy logo
RizTech Academy
Best PracticesLesson 3 of 535 min

Code that survives the content changing

Your page works. You built it with "Sharma Kirana", a two-line description and four products. Then the shop is called "Shree Ganesh Provision Stores and General Merchants", the description runs to six lines, one product has no photograph, and somebody adds a twenty-third item.

This lesson is about the page still working. It is the difference between a demo and something you can hand over.

The content will not be what you used

You designed with content you invented, which means you designed with content that fits. Real content does not.

The eight cases worth testing deliberately, every time:

  1. A much longer name or title. Four words where you assumed two.
  2. A much shorter one. One word, where your layout assumed a two-line heading.
  3. Nothing at all. An empty description, a missing price, a product with no photo.
  4. Far more items. Twenty-three cards where you built three.
  5. One item. A grid of one looks wrong if you never checked.
  6. No items. The empty state, which is almost always forgotten.
  7. An unbroken string. A URL, an email address, a long product code.
  8. A different language or script. Devanagari is taller than Latin and changes line heights; German compound words are long.

Make yourself a test fixture with all eight and load it. Ten minutes, and it finds everything.

The specific failures, and their fixes

Long text overflowing

.card__title {
  overflow-wrap: break-word;      /* break a long word rather than overflow */
  hyphens: auto;                  /* with lang set, break at sensible points */
}

overflow-wrap: break-word is the one from the module 2 reset. It only breaks when a word genuinely cannot fit, which is what you want — unlike word-break: break-all, which breaks mid-word constantly and is almost never right.

For a single line that must not wrap:

.card__title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

All three are required — text-overflow does nothing without overflow: hidden and nowrap, which is why it "does not work" for most people the first time.

For a clamp at several lines:

.card__desc {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
}

Ugly prefixed syntax, works everywhere, and there is no better answer yet.

But think before truncating. An ellipsis hides information, and on a product name that may be the part the customer needed. Wrapping to two lines is usually better than cutting. Truncate when the layout genuinely cannot give more room, not as a default.

Flex and grid items that will not shrink

From modules 3 and 4, and this is where they bite in practice:

.row { display: flex; gap: 1rem; }
.row__text { flex: 1; min-width: 0; }         /* without min-width:0 it overflows */

.grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }   /* not just 1fr */

A long unbroken string in a flex item or a 1fr grid track pushes the whole layout wider, because the default minimum size is the content's minimum. min-width: 0 and minmax(0, 1fr) are the fixes, and they belong in your defaults rather than being remembered each time.

Images that are not there, or are the wrong shape

.card__image {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  background: var(--colour-surface);    /* visible if the image fails */
}

aspect-ratio plus object-fit: cover means every image occupies the same space whatever its real proportions, so titles line up — module 3's point. The background is what shows if the file 404s, instead of a broken-image icon on a transparent gap.

And a placeholder for a genuinely missing image, which is better than an empty box:

<img class="card__image" src="images/atta.jpg" alt="A 10kg bag of atta"
     width="600" height="400" onerror="this.src='images/placeholder.svg'">

That is the one line of JavaScript this course suggests, and it is optional.

The empty state

The most commonly forgotten case, and :has() from module 6 handles it with no JavaScript:

<ul class="cards" id="results"></ul>
<p class="empty" hidden>No products match. Try a different area.</p>
.cards:empty { display: none; }
.cards:empty + .empty { display: block; }

Or with :has() on the container:

.results:not(:has(.card)) .empty { display: block; }

An empty state should say what happened and what to do next. "No results" is a dead end; "No products match. Try a different area." is not.

Note :empty is strict in one direction and not the other: an element containing a single space is not empty, which catches people, while an element containing only an HTML comment is. So whitespace from your template's indentation will silently stop :empty matching.

One item, and too many items

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 1.5rem;
}

auto-fit from module 3 handles both ends: one card stretches to fill rather than sitting in a third of the row, and twenty-three wrap without a media query. auto-fill would leave the single card at column width — which is sometimes what you want, and is a decision to make rather than a default to inherit.

For a genuinely different treatment at low counts, :has() does quantity queries:

.cards:not(:has(> :nth-child(2))) .card { max-width: 24rem; }   /* exactly one */

Zoom, and the 400% requirement

WCAG asks that content reflows at 320px equivalent, which is what 400% zoom on a 1280px window produces. That is not an edge case — it is how many people with low vision browse.

What breaks at 400%:

Fixed pixel widths. A width: 400px sidebar at 400% zoom is 1600 effective pixels of a 1280 window. Use max-width and rem.

Fixed heights on text containers. Text at 4× needs four times the vertical space; a height: 200px box clips it. min-height, always.

Sticky headers. A header that is 15% of the viewport at 100% zoom can be 60% at 400%, leaving almost no content visible. Consider un-sticking it at small heights:

@media (min-height: 30rem) {
  .site-header { position: sticky; top: 0; }
}

Anything positioned absolutely at pixel offsets. It ends up somewhere unrelated.

Test it: zoom to 400% and try to use the page. Then zoom text only, if your browser offers it separately, because that is a different and harsher test — your px sizes will not move at all.

Do not depend on JavaScript for content

A static site should not, and this course has not needed to. But the principle is worth stating because it is the same resilience argument:

If content only appears after JavaScript runs, it is missing for anybody whose script failed — a flaky connection, a blocked CDN, an extension, an error in a different script on the page. Scroll-triggered reveals from module 6 are the common version: content that is absent until you scroll.

Write the content in the HTML and enhance it. That is what progressive enhancement means, and for a static site it costs nothing because there is nothing to enhance.

The graceful degradation of CSS itself

CSS fails in a useful way if you let it, and @supports is the explicit tool:

.cards {
  display: flex;
  flex-wrap: wrap;                    /* works everywhere */
}

@supports (grid-template-columns: subgrid) {
  .cards { display: grid; grid-template-rows: subgrid; }
}

The base styles are the version that works; the enhancement is conditional. This is mobile-first thinking applied to features rather than widths, and it is the same argument.

Remember from module 6 that an unsupported selector invalidates its whole rule — so a :has() rule simply does not apply, which is a clean fallback if your base styles are sensible. An unsupported property is dropped individually, leaving the rest of the rule.

The test fixture

Build this once and keep it:

<section class="cards">
  <article class="card"><h3 class="card__title">Atta</h3></article>
  <article class="card"><h3 class="card__title">Shree Ganesh Provision Stores and General Merchants Premium Chakki Fresh Wholewheat Atta</h3></article>
  <article class="card"><h3 class="card__title">contact@shreeganeshprovisionstores-kothrud.co.in</h3></article>
  <article class="card"><h3 class="card__title">किराना सामान</h3></article>
  <article class="card"><h3 class="card__title"></h3></article>
  <article class="card"><h3 class="card__title">Tur Dal</h3><img class="card__image" src="does-not-exist.jpg" alt="Tur dal"></article>
</section>

Six cards: normal, far too long, an unbroken string, another script, empty, and a broken image. Load it at 320px, at 1440px and at 400% zoom. If that renders, your component is finished. If it does not, you have found the bugs before your client did.

Check your work

Why the content you designed with is misleading. You invented content that fits.

The eight cases. Longer, shorter, absent, many, one, none, unbroken string, another script.

What overflow-wrap: break-word does that word-break: break-all does not. Breaks only when a word cannot fit, rather than constantly.

The three declarations text-overflow: ellipsis needs. overflow: hidden and white-space: nowrap as well.

Why to think before truncating. An ellipsis hides the part the reader may have needed.

The two shrink fixes. min-width: 0 on a flex item, minmax(0, 1fr) on a grid track.

What a background on an image slot buys. Something sensible when the file 404s.

What an empty state must say. What happened and what to do next.

Why :empty catches people. A single space means not empty — but a comment alone does count as empty.

How auto-fit handles both ends. One card stretches; twenty-three wrap, with no query.

What 400% zoom is equivalent to. Reflow at about 320px — and it breaks fixed pixel widths, fixed heights and sticky headers.

Why a fixed height on text is worse at 400%. The text needs four times the space and gets clipped.

Why content must be in the HTML. A failed script means missing content.

How an unsupported selector fails versus an unsupported property. The whole rule is invalidated; a single declaration is dropped.

Practice

  1. Build the six-card test fixture and load it. Write down every failure.
  2. Put a 60-character product name in a card built for 12 characters.
  3. Put an email address with no spaces in a flex row. Fix it with min-width: 0.
  4. Do the same in a 1fr grid track, then use minmax(0, 1fr).
  5. Apply text-overflow: ellipsis with only that one declaration, then add the other two.
  6. Clamp a description to three lines, then decide whether wrapping would have been better.
  7. Break an image's src and look at the card. Add a background colour and compare.
  8. Add the onerror placeholder and break the src again.
  9. Render a grid with zero items, then one, then twenty-three. Fix each.
  10. Write an empty state that says what to do next. Show it with :empty and again with :has().
  11. Put a single space inside an element and confirm :empty no longer matches. Then put only an HTML comment inside and confirm it does.
  12. Zoom your page to 400% and try to complete a task.
  13. Find a fixed pixel width that breaks at 400% and convert it to max-width in rem.
  14. Put six lines of text in a height: 100px box, then change to min-height.
  15. Un-stick your header below min-height: 30rem and check it at 400% zoom.
  16. Put a Devanagari heading next to a Latin one and compare their line heights.
  17. Wrap a subgrid enhancement in @supports and confirm the flex fallback works.

Official documentation

Next: performance, on the phone your visitor actually has.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship