Module: CSS
CSS·025·9 MIN READ

025: Display and Normal Flow

TOPICS COVERED: Display and Normal Flow

Learning outcomes

By the end, you can explain normal flow; distinguish block, inline, and inline-block outer behavior; use display: none deliberately; recognize formatting contexts; and solve simple page structure without Flexbox, Grid, floats, or positioning.

Prerequisites and retrieval

Open 024's fluid portfolio. Retrieve the box layers and why content should usually determine block size. Predict whether two <p> elements occupy one line or separate lines by default, and whether an <a> interrupts a sentence. Those defaults come from display type and normal flow.

Terminology

Mental model: document order is the baseline layout

Normal flow is not “no layout.” Block boxes stack in the block direction; inline content forms line boxes and wraps when inline space runs out. The browser's stylesheet gives familiar elements useful defaults: headings, paragraphs, sections, and lists are block-level; links and emphasis are inline.

display controls box generation and layout participation, not HTML meaning. display: block on a <span> does not make it a paragraph. Keep semantic markup, then select presentation.

Modern display conceptually has outer and inner behavior. display: flex means a block-level box whose children use flex layout; inline-flex means an inline-level box with flex children. Today focus on block, inline, and inline-block while preserving normal document order.

Beginner example: observe defaults

html
<main class="site-shell">
  <p class="eyebrow">Frontend portfolio</p>
  <h1>Asha Rao</h1>
  <p>I build <a href="#projects">clear project pages</a> with semantic HTML.</p>
  <section id="projects">
    <h2>Selected work</h2>
    <article class="project-card">
      <h3>Library finder</h3>
      <p>Search opening hours and directions.</p>
      <a class="button-link" href="#">Read case study</a>
    </article>
  </section>
</main>
css
html { box-sizing: border-box; }
*, *::before, *::after { box-sizing: inherit; }
body { margin: 0; font-family: system-ui, sans-serif; line-height: 1.6; }
.site-shell { max-inline-size: 70rem; margin-inline: auto; padding: 1rem; }
.project-card { max-inline-size: 36rem; padding: 1rem; border: 1px solid rgb(203 213 225); }
.button-link {
  display: inline-block;
  padding: 0.6rem 0.9rem;
  border: 2px solid currentColor;
  border-radius: 0.4rem;
  font-weight: 700;
}

With CSS disabled, source order is already usable. The heading and paragraphs stack. The link stays in its sentence. .button-link remains alongside neighboring inline content but can carry predictable vertical padding and dimensions. Do not turn every text link into a block merely to enlarge it; choose behavior based on context.

Experiment safely: set h1 { display: inline; } and watch following text share its line. Set the sentence link to display: block and watch it interrupt prose. Restore both. This makes display consequences concrete.

Intermediate example: normal-flow page sections

A centered page does not require a layout system:

css
.site-header,
.site-main,
.site-footer {
  padding-inline: max(1rem, calc((100% - 70rem) / 2));
}

.site-header { padding-block: 3rem; background: rgb(15 23 42); color: white; }
.site-main { padding-block: 2rem; }
.site-footer { padding-block: 2rem; border-block-start: 1px solid rgb(203 213 225); }

.project-card + .project-card { margin-block-start: 1rem; }

.tag {
  display: inline-block;
  margin-inline-end: 0.35rem;
  margin-block-end: 0.35rem;
  padding: 0.2em 0.55em;
  border-radius: 999px;
  background: rgb(219 234 254);
}

Sections remain in DOM order. Cards stack naturally and grow with content. Tags wrap as inline-level boxes. max() chooses at least 1rem edge padding, increasing it when viewport space around a 70rem content area permits. This stable function is optional; a nested .site-shell is equally valid and often simpler.

Hiding content

The browser's user-agent stylesheet supplies the appropriate presentation for the HTML hidden attribute, so an author rule is normally unnecessary. Use hidden for genuinely inactive content, and remove it when content becomes available. The ordinary hidden state removes an element and descendants from layout and normally from the accessibility tree. Do not add a blanket [hidden] { display: none; } rule: it overrides hidden="until-found", whose content is initially hidden but can be revealed by find-in-page or fragment navigation. Do not use either state for text that should remain continuously available to screen readers. visibility: hidden preserves layout space but also hides content; opacity: 0 merely makes paint transparent and can leave focusable, clickable content, so it is not a safe general hiding technique.

If a component truly must override hidden presentation, target that component and only the ordinary state, for example .tabs [hidden]:not([hidden="until-found"]). Prefer the user-agent behavior unless the component has a tested reason to differ. This restriction preserves the browser's reveal behavior while keeping the override local and reviewable.

Optional advanced example: a new block formatting context

display: flow-root creates a block formatting context:

css
.component {
  display: flow-root;
  padding: 1rem;
  background: white;
}

It can contain legacy floated content and prevent certain margin interactions without adding meaningless wrapper markup. Floats are appropriate for text wrapping around media, not modern page layout. You do not need flow-root for ordinary cards, but recognize it when maintaining existing CSS.

Mistakes, debugging, and DevTools

  • Changing display to repair semantic markup: use the correct element first.
  • Applying vertical width/height expectations to inline text: inline boxes fragment across lines.
  • Setting navigation links to inline-block with no wrap testing: long labels can create overflow.
  • Using display: none to visually hide an accessible label: it becomes unavailable to assistive technology.
  • Using opacity: 0 as hidden: invisible interactive controls may remain reachable.
  • Reaching for Flexbox to stack ordinary prose: normal flow already handles it robustly.
  • Using floats for cards or site columns: save Flexbox and Grid for their intended layout roles.

DevTools' Computed pane shows final display. Add outlines to siblings and resize to watch line wrapping. The accessibility tree panel can confirm whether hidden content is exposed. Toggle the browser's user-agent rules to see which defaults establish block and inline behavior.

Accessibility and performance

Source order determines reading, keyboard, and normal-flow order. Keep it logical; visual CSS should not compensate for confusing HTML. Controls that become inline-block still need adequate target size, visible focus, and space to wrap. Never hide focused content.

Normal flow is resilient and efficient. Fewer layout overrides mean less CSS and fewer reflow surprises. Performance concerns should focus on media and fonts rather than whether a semantic section is block-level.

Deep dive: outer and inner display behavior

display conceptually describes how a box participates in its parent layout and, for many values, how its children are laid out.

css
.block-box { display: block; }
.inline-box { display: inline; }
.inline-block-box { display: inline-block; }
.flex-box { display: flex; }
.grid-box { display: grid; }

A block box usually starts on a new line and stretches in the inline direction when width is auto. An inline box participates inside line boxes and does not accept every sizing behavior the same way. inline-block participates inline externally but behaves like a block container internally.

Deep dive: display: none, visibility, and opacity

These are not interchangeable:

css
.a { display: none; }
.b { visibility: hidden; }
.c { opacity: 0; }
  • display: none removes the box from layout and generally from the accessibility tree.
  • visibility: hidden keeps layout space but hides the box and its descendants visually/interaction-wise unless overridden in certain cases.
  • opacity: 0 makes the box transparent but it still participates in layout and can remain interactive/focusable.

Therefore opacity: 0 is not a safe general-purpose “hide this control” technique.

Deep dive: flow-root and formatting contexts

css
.article {
  display: flow-root;
}

flow-root creates a new block formatting context without changing the element into Flexbox or Grid. This can contain floats and isolate certain margin interactions.

Float example

html
<article class="story">
  <img class="story__thumb" src="author.jpg" alt="">
  <p>Long article text wraps beside the image...</p>
</article>
css
.story {
  display: flow-root;
}

.story__thumb {
  float: inline-start;
  inline-size: 8rem;
  margin-inline-end: 1rem;
  margin-block-end: 0.5rem;
}

Floats were historically abused for page layout. Today they are most appropriate when content should genuinely wrap around media, like editorial text around an image.

Worked example: inline versus inline-block

html
<p>
  Read the
  <a class="tag" href="/css">CSS course</a>
  today.
</p>
css
.tag {
  display: inline-block;
  padding: 0.25rem 0.5rem;
  border: 1px solid currentColor;
  border-radius: 999px;
}

inline-block permits padding and a self-contained box while still flowing with text. If this tag needs to wrap naturally across lines like ordinary text, regular inline behavior may be more appropriate.

Worked example: hiding a disclosure panel correctly

HTML:

html
<button aria-expanded="false" aria-controls="details">
  Details
</button>

<div id="details" hidden>
  <p>Additional information.</p>
</div>

Use the HTML hidden attribute for semantic hidden state and let JavaScript toggle that state when behavior is added. Avoid creating a CSS-only visual state where assistive technologies disagree with what sighted users can see.

CSS may style the visible state:

css
[hidden] {
  display: none !important;
}

Browsers already provide this behavior, so a custom rule is often unnecessary.

Deep dive: normal flow remains valuable inside modern components

Even when a page uses Grid:

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

each .card can keep normal block flow for its headings and paragraphs. Do not turn every wrapper into Flexbox or Grid. Layout modes should solve relationships that actually need coordination.

Display debugging questions

When an element behaves strangely:

  1. What formatting context does its parent establish?
  2. Is this element block-level, inline-level, flex item, or grid item?
  3. Was it removed from layout with display: none?
  4. Is it transparent but still interactive?
  5. Did float, position, or display: contents change box generation?
  6. Are you changing layout mode only to fix spacing that normal flow already handles?

Start with normal flow and add a stronger layout system only when the relationship demands it.

Tiered exercises

Checkpoint: solve with the baseline first

Take a project article and remove all layout declarations while retaining type, color, padding, and borders. Read it from top to bottom. If the desired design is simply a heading, description, and action stacked in order, normal flow is already the correct solution. Add layout only when a relationship cannot be expressed clearly by that baseline.

Now place three tags after a paragraph and make the viewport narrow. Inline content wraps naturally at text boundaries; inline-block tags wrap as intact boxes when each fits. A single tag wider than its container still needs breakable content or a size constraint. Display mode cannot repair an unbreakable URL.

Compare hiding methods using one noninteractive paragraph. display: none removes its space and accessibility exposure. visibility: hidden retains its space. opacity: 0 retains both space and potential interaction. Restore the paragraph after inspection. For production, choose based on the intended semantic state, not the visual effect alone, and test focusable descendants before shipping.

Inline formatting has another practical consequence: text can split into several line fragments, so its background and decoration may appear on every line. Padding at line starts and ends can look different from a single rectangular control. When the design requires one intact badge or button-like rectangle, inline-block is appropriate, but give long labels room to wrap elsewhere rather than forcing page overflow. For ordinary prose links, inline behavior remains the most natural and readable choice.

Foundation: Label each portfolio element block or inline by default. Change one temporarily and explain the observed result.

Core: Build stacked project cards in normal flow and inline-block tags that wrap. Style a case-study link as an obvious padded control with visible focus.

Stretch: Create inactive supplementary content using hidden; inspect both layout and accessibility tree before and after removing the attribute.

html
<article class="project-card">
  <h3>Library finder</h3>
  <p><span class="tag">HTML</span><span class="tag">Accessibility</span></p>
  <a class="button-link" href="#">Read case study</a>
  <p hidden>Draft testing notes.</p>
</article>
css
.project-card { padding: 1rem; border: 1px solid rgb(203 213 225); }
.project-card + .project-card { margin-block-start: 1rem; }
.tag { display: inline-block; margin: 0 0.35rem 0.35rem 0; padding: 0.2em 0.55em; background: rgb(219 234 254); }
.button-link { display: inline-block; padding: 0.6rem 0.9rem; border: 2px solid currentColor; }
.button-link:focus-visible { outline: 3px solid rgb(234 88 12); outline-offset: 3px; }

Recap and exit questions

Normal flow is the robust baseline: blocks stack and inline content wraps. display changes generated box behavior, not semantics. Use inline-block for a box that should flow inline while accepting block-like geometry.

  1. Why is normal flow already a layout system?
  2. What changes when a link becomes inline-block?
  3. Does display: block give a <span> paragraph semantics?
  4. Why is opacity: 0 unsafe for hiding interactive content?
  5. When would flow-root be useful?

Official references