FullStack Course LogoFullStack Course
Module: CSS
CSS·037·7 MIN READ

037: Floats, Multi-Column Layout, Lists, and Tables

TOPICS COVERED: Floats, Multi-Column Layout, Lists, and Tables

Learning outcomes

By the end of this lesson, you should be able to choose floats when text genuinely needs to wrap around an object, clear and contain those floats, and build editorial layouts with CSS multi-column flow. You will also be able to customize list markers without throwing away list semantics, style data tables accessibly, and recognize when Flexbox or Grid is a better fit.

Prerequisites and retrieval

Bring back the normal-flow model from lesson 025, along with the HTML module's semantics for lists and tables. The topics here are specialized tools: they have useful, well-defined jobs, and they appear as separate areas on the CSS roadmap rather than as a replacement for the general layout systems.

Floats: what they are still good for

Suppose an article opens with a photograph beside its text. The image is part of the content, and the paragraph should flow around it rather than sit in a separate layout column:

html
<article class="story">
  <img class="story__image" src="speaker.jpg" alt="Conference speaker on stage">
  <p>
    The conference opened with a discussion of browser layout...
  </p>
</article>
css
.story {
  display: flow-root;
}

.story__image {
  float: inline-start;
  inline-size: min(40%, 14rem);
  margin-inline-end: 1rem;
  margin-block-end: 0.5rem;
}

The paragraph wraps around the floated image, which is the use case floats still handle naturally. The logical inline-start value also avoids hard-coding left-to-right assumptions.

Do not use this technique to build the main columns of a modern page:

css
/* historical technique; avoid for ordinary page layout */
.sidebar {
  float: left;
  width: 30%;
}

That approach makes the relationship between the sidebar and the rest of the page indirect. Flexbox and Grid describe those layout relationships directly, making sizing, alignment, and responsive changes easier to reason about.

Clear

css
.footer {
  clear: both;
}

clear says that a box must not sit beside earlier floats on the specified sides. It is useful when a following element should begin below the floated content. When the goal is instead to make a parent contain its floated children, putting display: flow-root on the containing block is usually clearer than adding a separate clearing element.

Multi-column layout

Multi-column layout is for flowing one body of content through several columns. You can ask for columns of at least a particular width:

css
.article {
  column-width: 18rem;
  column-gap: 2rem;
}

Or you can request a specific number of columns:

css
.article {
  column-count: 3;
}

Add a rule between the columns when that boundary helps the reader:

css
.article {
  column-rule: 1px solid #cbd5e1;
}

Headings can otherwise land in an awkward position at a column boundary. Ask the browser to avoid breaking immediately after one:

css
.article h2 {
  break-after: avoid;
}

For a title that belongs above the entire article rather than inside one column, span all columns:

css
.article__title {
  column-span: all;
}

Worked example: editorial article

css
.long-read {
  max-inline-size: 75rem;
  margin-inline: auto;
  column-width: 20rem;
  column-gap: clamp(1.5rem, 4vw, 3rem);
}

.long-read p {
  margin-block-start: 0;
}

.long-read h2 {
  break-after: avoid;
}

The content flows from top to bottom in one column, then continues at the top of the next. That suits print-like or editorial reading, where the reader expects columns. On a tall scrolling screen, though, the next column may begin above the current viewport, forcing the reader to move back upward. Test the reading experience with realistic content and viewport sizes instead of choosing columns only because they make the page look denser.

Lists

Removing the browser's default list styling is reasonable for a navigation list when the replacement design still communicates its structure:

css
.nav-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

For a content list, be more careful. Keep the markers, or replace them with a visual treatment that still makes the list unmistakable. A custom marker can change appearance without changing the underlying list:

css
.features li::marker {
  color: #16a34a;
  content: "✓ ";
}

You can also control where the marker sits and which marker type is used:

css
.steps {
  list-style-position: outside;
  list-style-type: decimal;
}

Worked example: numbered process

This version creates a more designed step indicator, but it keeps the numbering tied to the list items through CSS counters:

css
.steps {
  counter-reset: step;
  list-style: none;
  padding: 0;
}

.steps li {
  counter-increment: step;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.75rem;
}

.steps li::before {
  content: counter(step);
  display: grid;
  place-items: center;
  inline-size: 2rem;
  aspect-ratio: 1;
  border-radius: 50%;
  background: #2563eb;
  color: white;
  font-weight: 700;
}

Use the semantic <ol> whenever order or sequence carries meaning. CSS counters and pseudo-elements control presentation; they do not turn an unordered list into an ordered one for browsers or assistive technology.

Tables

Start with a real table and a restrained baseline style:

css
table {
  width: 100%;
  border-collapse: collapse;
}

th,
td {
  padding: 0.75rem 1rem;
  border-block-end: 1px solid #e2e8f0;
  text-align: start;
}

th {
  font-weight: 700;
}

The table remains a table; the CSS establishes readable spacing, visible row boundaries, and alignment that works with the writing direction. Zebra striping can make it easier to track across a wide row:

css
tbody tr:nth-child(even) {
  background: #f8fafc;
}

A hover highlight can help someone scanning with a pointer:

css
tbody tr:hover {
  background: #eff6ff;
}

Do not make hover the only indication of essential row information. Touch users and keyboard users may never produce that hover state.

Table layout algorithm

The fixed table layout algorithm can make column sizing more predictable and, for some data tables, more performance-friendly:

css
table {
  table-layout: fixed;
}

That predictability is a trade-off. Long content may no longer get the space it would receive under automatic sizing, so give the content somewhere sensible to wrap:

css
td {
  overflow-wrap: anywhere;
}

Use fixed deliberately rather than treating it as a universal table improvement.

Responsive table wrapper

When a table is wider than its viewport, preserve the table and provide a horizontal scroll region:

html
<div class="table-wrap" role="region" aria-label="Quarterly sales" tabindex="0">
  <table>
    ...
  </table>
</div>
css
.table-wrap {
  overflow-x: auto;
  max-inline-size: 100%;
}

.table-wrap:focus-visible {
  outline: 3px solid #2563eb;
  outline-offset: 3px;
}

The region label tells a user what the scrollable area contains, and the focus outline makes keyboard focus visible. Add tabindex="0" only when keyboard access and discoverability for that scroll region are needed in the target environment. Unnecessary focusable wrappers make keyboard navigation noisier.

Caption styling

css
caption {
  text-align: start;
  font-weight: 700;
  padding-block-end: 0.75rem;
}

Keep the actual <caption> in the HTML. A styled paragraph may look similar, but it does not provide the same semantic relationship to the table.

Deep dive: formatting contexts, fragmentation, and counters

Containing floats

Floats are taken out of ordinary block flow enough that a parent can appear to have no height around them. A new block formatting context contains the float effects within the parent:

css
.article-lead {
  display: flow-root;
}

.article-lead img {
  float: inline-start;
  margin-inline-end: 1rem;
  margin-block-end: 0.5rem;
}

flow-root expresses that containment directly and is generally easier to understand than the older clearfix hacks.

Shape-aware wrapping

Sometimes the object being wrapped is intentionally non-rectangular. Where that editorial treatment is appropriate, shape-outside lets the wrapping boundary follow a shape:

css
.profile-photo {
  float: inline-start;
  inline-size: 12rem;
  aspect-ratio: 1;
  border-radius: 50%;
  shape-outside: circle(50%);
  margin-inline-end: 1.5rem;
}

This is still an editorial text-wrapping technique. It is not a substitute for Flexbox or Grid when the problem is arranging components.

Multi-column fragmentation

Because multi-column layout fragments content from one column into the next, rules that control page-like breaks become relevant:

css
.article {
  columns: 18rem 3;
  column-gap: 2rem;
  column-rule: 1px solid var(--border);
}

.article h2 {
  column-span: all;
}

.article figure,
.article blockquote {
  break-inside: avoid;
}

Do not use columns for controls that depend on a predictable left-to-right interaction order. They work best for long-form reading, not for forms, toolbars, or other interactive arrangements where the fragmented order would make navigation harder to follow.

CSS counters for semantic numbering

Keep the list semantics in HTML and customize only how the marker is presented:

css
.steps {
  counter-reset: step;
  list-style: none;
  padding: 0;
}

.steps > li {
  counter-increment: step;
}

.steps > li::before {
  content: counter(step) ".";
  font-weight: 700;
  margin-inline-end: 0.5rem;
}

If the sequence is genuinely ordered, prefer <ol> even when CSS supplies a custom marker. The counter changes the visual output, not the document's semantic meaning.

Table layout decisions

table-layout: fixed uses declared or available widths in a more predictable way:

css
.pricing-table {
  inline-size: 100%;
  table-layout: fixed;
  border-collapse: collapse;
}

This can stop one exceptionally long cell from dominating the sizing of every column. The cost is that overflow management becomes your responsibility, so check wrapping and readability with real data.

On narrow screens, first consider horizontal scrolling while retaining the real table structure:

css
.table-scroll {
  overflow-x: auto;
  overscroll-behavior-inline: contain;
}

Avoid turning every cell into a pseudo-label block by default. A transformation can change reading order and obscure the relationships between headers and cells, so it must be tested with assistive technology before it replaces the table.

Decision rule

  • Float: text wraps around an object.
  • Columns: editorial content flows into newspaper-like columns.
  • Flexbox: one-dimensional component/layout relationship.
  • Grid: row-and-column alignment.
  • Table: genuinely tabular data with header relationships.

Choose from this information structure first. Visual preference alone is a poor reason to select a layout mechanism.

Common mistakes

  • Using floats for entire application layouts.
  • Clearing floats with meaningless extra HTML.
  • Applying multi-column layout to interactive forms/cards where reading order becomes confusing.
  • Removing list markers from ordinary content without another visual cue.
  • Turning a real data table into display: block fragments and destroying relationships.
  • Setting display: grid on table internals without understanding the semantic/accessibility consequences.
  • Forcing narrow table columns that make content unreadable.

Practice set

  1. Float an editorial image and contain it with flow-root.
  2. Build a two/three-column article using column-width.
  3. Style an ordered process without removing <ol>.
  4. Build an accessible data table with caption and scoped headers.
  5. Add a horizontal overflow wrapper and keyboard focus style.
  6. Compare table-layout: auto and fixed.

Recap

Floats still earn their place when content must wrap around an object. Multi-column layout is an editorial flow system, not a general-purpose component layout. Lists should retain list semantics, and tables should retain their data relationships. The specialized CSS tools in this lesson work well when they are chosen for the problem they were designed to solve.

Official references

Reader page: /css/lesson/037/floats-multi-column-layout-lists-and-tables