025: 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
- Normal flow: Default layout where block boxes stack and inline content wraps in line boxes. — Source: CSS Display 3: Flow layout
- Block box: A box participating in block layout, normally starting a new line. — Source: CSS Display 3: Block-level boxes
- Inline box: A box flowing inline within line boxes alongside text. — Source: MDN: Visual formatting model
- Inline-block: Outer display inline with inner block formatting: flows inline yet accepts block sizing. — Source: CSS Display 3
- Outer display type: How a box participates in its parent formatting context (block/inline). — Source: CSS Display 3: Outer display
- Inner display type: How the element lays out its children (flow, flex, grid…). — Source: CSS Display 3: Inner display
- Formatting context: The set of rules used to lay out a box’s children. — Source: MDN: Visual formatting model
- Anonymous box: A generated box wrapping text or markup without an explicit element box. — Source: CSS Display 3: Anonymous boxes
- Block Formatting Context (BFC): "A block formatting context is a part of a visual CSS rendering that contains floats and prevents margin collapsing through its boundaries." — Source: MDN: Visual formatting model & CSS Display Level 3: BFC
- Line box: "A rectangular area that contains inline-level content on one line." — Source: MDN: Visual formatting model
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
<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>
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:
.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:
.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/heightexpectations to inline text: inline boxes fragment across lines. - Setting navigation links to
inline-blockwith no wrap testing: long labels can create overflow. - Using
display: noneto visually hide an accessible label: it becomes unavailable to assistive technology. - Using
opacity: 0as 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.
.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:
.a { display: none; }
.b { visibility: hidden; }
.c { opacity: 0; }
display: noneremoves the box from layout and generally from the accessibility tree.visibility: hiddenkeeps layout space but hides the box and its descendants visually/interaction-wise unless overridden in certain cases.opacity: 0makes 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
.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
<article class="story">
<img class="story__thumb" src="author.jpg" alt="">
<p>Long article text wraps beside the image...</p>
</article>
.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
<p>
Read the
<a class="tag" href="/css">CSS course</a>
today.
</p>
.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:
<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:
[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:
.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:
- What formatting context does its parent establish?
- Is this element block-level, inline-level, flex item, or grid item?
- Was it removed from layout with
display: none? - Is it transparent but still interactive?
- Did
float,position, ordisplay: contentschange box generation? - 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.
<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>
.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.
- Why is normal flow already a layout system?
- What changes when a link becomes
inline-block? - Does
display: blockgive a<span>paragraph semantics? - Why is
opacity: 0unsafe for hiding interactive content? - When would
flow-rootbe useful?
