How Websites Were Built Before Flexbox and Grid
Modern CSS feels almost effortless.
Need a responsive layout? Use Grid.
Want perfect alignment? Flexbox solves it in a few lines.
Need rounded corners, gradients, shadows, or animations? Every browser supports them without much thought.
It wasn’t always like this.
If you started building websites in the last decade, many of the techniques below will probably look strange. Some will seem unnecessarily complicated. Others will make you wonder how websites ever worked at all.
Yet these techniques powered millions of websites. Online stores, newspapers, company portals, forums, and blogs all relied on them.
Many of those ideas have disappeared.
Some never really did.
CSS Was Much Less Capable
Today’s browser engines provide hundreds of layout features.
Fifteen or twenty years ago, developers had only a fraction of that toolbox.
There was no Grid.
Flexbox either didn’t exist or wasn’t supported consistently.
CSS variables were years away.
Container queries were science fiction.
Even centering a simple block could become a small puzzle.
As a result, front-end developers became experts at finding creative workarounds.
Sometimes those workarounds were surprisingly clever.
Sometimes they were simply painful.
Life Before `box-sizing`
Most modern projects start with a universal rule.
*,
*::before,
*::after {
box-sizing: border-box;
}Today, when you write:
.card {
width: 320px;
padding: 20px;
border: 2px solid #222;
}
the element still occupies exactly 320 pixels.
That wasn’t the default behavior years ago.
Without `border-box`, browsers calculated the final width like this:
width
+ padding left
+ padding right
+ border left
+ border rightInstead of trusting the browser, developers often reached for calculators.
Changing a single padding value frequently meant recalculating an entire layout.
One tiny design adjustment could break multiple columns.
Building Columns With Floats
Before Flexbox, most websites relied on floats.
.wrapper::after {
content: "";
display: block;
clear: both;
}.card {
float: left;
width: 300px;
margin: 10px;
}

At first glance, everything looked fine.
Until it didn’t.
Floating elements were removed from the normal document flow.
The parent suddenly had zero height.
The solution became one of the most famous CSS snippets ever written.
.clearfix::after {
content: "";
display: block;
clear: both;
}Every experienced front-end developer has probably written a version of this class.
It appeared in almost every framework of that era.
Floats Had Another Problem
Different element heights could completely change the layout.
Imagine six cards.
The second one becomes slightly taller because someone writes a longer title.
Suddenly the fourth card jumps upward.
The fifth moves sideways.
The sixth wraps to another row.
Nothing in your CSS changed.
Only the content did.
Developers spent hours chasing one missing pixel.
Modern Grid simply doesn’t have this problem.
Inline-Block Wasn’t Perfect Either
Some developers abandoned floats and switched to `inline-block`.
.item {
display: inline-block;
width: 240px;
}It solved one problem.
Then introduced another.
Whitespace between HTML elements became visible spacing.
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>Those line breaks were rendered as actual spaces.
One common workaround looked like this.
.container {
font-size: 0;
}.item {
display: inline-block;
font-size: 16px;
}
Another trick was even stranger.
Developers literally removed every line break from the HTML.
<div class="item"></div><div class="item"></div><div class="item"></div>Some template engines even generated HTML exactly this way.
Others inserted HTML comments between elements to hide whitespace.
It wasn’t elegant.
It worked.

Centering Was Surprisingly Difficult
Today we usually write this.
.container {
display: grid;
place-items: center;
}Or this.
.container {
display: flex;
justify-content: center;
align-items: center;
}Years ago, neither option existed.
One popular solution abused table layouts.
.container {
display: table-cell;
text-align: center;
vertical-align: middle;
}.box {
display: inline-block;
}

Nothing about that code suggested “center this element.”
Yet browsers interpreted it exactly that way.
Absolute Positioning Required Math
Another classic approach involved negative margins.
.dialog {
position: fixed;
width: 320px;
height: 180px; left: 50%;
top: 50%;
margin-left: -160px;
margin-top: -90px;
}
Every size change required updating the negative margins.
If the dialog height became dynamic, the entire trick failed.
Eventually developers replaced it with something much cleaner.
.dialog {
position: fixed; left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
That felt revolutionary at the time.
Gradients Were Images
CSS gradients didn’t exist.
Designers created gradients in Photoshop.
Developers exported a one-pixel-wide image.
Then CSS stretched it across the page.
.hero {
background-image: url("gradient.png");
background-repeat: repeat-x;
}
Horizontal.
Vertical.
Diagonal.
Everything began as an image.
The browser simply repeated it.
Rounded Corners Were Surprisingly Expensive
Today we use:
.button {
border-radius: 12px;
}Done.
Earlier websites often built rounded buttons from multiple images.
Each corner became its own graphic.
Sometimes developers used four absolutely positioned elements.
Sometimes they relied on nested tables.
Sometimes the entire button was one large image.
Changing the button color often meant opening Photoshop again.
Table Layouts Didn’t Disappear Overnight
Many people associate table layouts with the early 2000s.
They’re not wrong.
Large websites frequently used deeply nested tables to build entire page layouts.
<table class="layout">
<tr>
<td class="sidebar">...</td>
<td class="content">...</td>
</tr>
</table>Visual editors such as Dreamweaver and FrontPage generated this markup automatically.
Photoshop’s “Save for Web” feature could even slice a design directly into tables.
The HTML wasn’t pretty.
It produced working pages.
Interestingly, tables never completely disappeared.
Email development still depends on them.
Spacer GIFs Solved Everything
One of the most memorable tricks involved transparent images.
Usually they were called `spacer.gif` or `0.gif`.
Developers inserted invisible images to create spacing.
<img src="spacer.gif" width="20" height="1" alt="">Need extra padding?
Spacer GIF.
Need a minimum table height?
Spacer GIF.
Need columns to align?
Spacer GIF again.
Looking back, it feels ridiculous.
Back then, it was considered perfectly normal.

Internet Explorer Changed Everything
Supporting Internet Explorer often meant writing completely different CSS.
PNG transparency was broken in older versions.
`:hover` worked only on links.
Some layout calculations differed by browser.
Developers accumulated collections of browser hacks.
Conditional comments became common.
<!--[if IE]>
<link rel="stylesheet" href="ie.css">
<![endif]-->Entire stylesheets existed solely for Internet Explorer.
Testing usually involved several browsers open at the same time.
Opera.
Firefox.
Internet Explorer.
Sometimes multiple IE versions running through virtual machines or compatibility tools.
JavaScript Filled CSS Gaps
Many features we now expect from CSS required JavaScript.
Equal-height columns.
Sticky headers.
Centered modals.
Animated menus.
Accordion widgets.
Image rollovers.
Even simple hover effects sometimes required scripts.
Libraries like jQuery became incredibly popular because they hid browser inconsistencies behind one API.
Performance Was Different
Modern websites are far more capable.
They are also significantly more complex.
Earlier pages often loaded faster simply because there wasn’t much JavaScript.
No hydration.
No client-side routing.
No hundreds of npm packages.
At the same time, developers had to compensate with much more manual work.
The browser was simpler.
The developer’s job was harder.
Some Old Techniques Never Really Left
Reading old CSS can feel like archaeology.
Yet several techniques remain surprisingly relevant.
Email templates still rely heavily on nested tables.
Corporate intranets sometimes target legacy browsers.
Industrial software often runs inside outdated rendering engines.
Embedded systems can still expose interfaces that behave like browsers from another decade.
For those projects, old knowledge is not nostalgia.
It is practical experience.
What Modern CSS Changed
Today’s solution is dramatically shorter.
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 24px;
}.card {
border-radius: 12px;
padding: 24px;
}

No clearfix.
No spacer GIFs.
No negative margins.
No fake table cells.
No Photoshop gradients.
The browser finally handles the work developers spent years solving manually.
LEGACY LAYOUT
Workarounds owned the geometry
- +Floats required clearing and careful source order.
- +Inline-block layouts exposed whitespace from the HTML.
- +Fixed-size centering depended on negative margins.
- +Images supplied gradients, corners, spacing, and rollover states.
- +Grid defines rows, columns, gaps, and responsive tracks.
- +Flexbox aligns and distributes items along one axis.
- +Intrinsic sizing adapts to content without pixel arithmetic.
- +Native gradients, radii, transforms, and variables stay editable in CSS.
MODERN CSS
The layout model owns the geometry
Tools for Reviewing Legacy CSS
Old stylesheets often mix valid historical techniques with rules that are now redundant, duplicated, or difficult to reason about. Preserve a working copy before changing them, then test the rendered result after every structural edit.
CSS CheckerInspect CSS syntax, duplicate declarations, and common structural issues before refactoring. CSS FormatterTurn compressed or irregular legacy stylesheets into readable CSS for review. CSS OrganizerApply a predictable declaration order while checking duplicate and fallback rules. HTML EditorTest old and modern layout snippets beside a live browser preview.
Final Thoughts
Looking back at old front-end code can be entertaining.
It also reminds us how much the platform has evolved.
Every modern CSS feature exists because developers spent years working around its absence.
Floats eventually became Flexbox.
Complex positioning became Grid.
Image slices became gradients.
Nested tables gave way to semantic layouts.
The next time `display: flex` solves a problem in seconds, remember that developers once needed dozens of lines of CSS, a few browser hacks, and sometimes even a transparent GIF just to achieve the same result.
Frequently Asked Questions
How did developers create columns before Flexbox and Grid?
Most multi-column layouts used floated elements, inline-block boxes, display table patterns, or actual HTML tables. Each technique required workarounds for clearing, whitespace, equal heights, alignment, or responsive behavior.
Why was the clearfix hack necessary?
Floated children were taken out of normal document flow, so their parent could collapse to zero height. A clearfix generated a final clearing box that forced the parent to contain its floated children.
Were table layouts always invalid HTML?
Tables are correct for tabular data, but using them to position an entire page mixed presentation with document structure. That made responsive changes, accessibility, maintenance, and content reordering more difficult.
Why are tables still used in HTML email?
Email clients have inconsistent CSS support and often use older rendering engines. Tables remain one of the most predictable ways to preserve columns and spacing across Outlook, Gmail, Apple Mail, and other clients.
Should old projects replace every float with Flexbox or Grid?
No. A working float used for text wrapping may still be appropriate. Replace legacy layout code when the change improves maintainability, responsiveness, or accessibility and can be tested across the project’s supported browsers.
What is the modern replacement for float-based page layouts?
Use Grid for two-dimensional rows and columns, and Flexbox for one-dimensional alignment and distribution. Floats remain useful when content should wrap around an image or similar element.