Skip to content

Responsive

About 60-70% of email opens happen on mobile. The web-style “responsive” approach — media queries that swap layouts at breakpoints — works in modern clients, but Outlook for Windows ignores media queries entirely. The reliable pattern is hybrid responsive: build a layout that works at any width by default, and use media queries only as enhancement.

Every responsive-safe email uses the same three nested table levels. Get this right and most mobile bugs disappear before you write a media query.

hybrid-3-level-wrapper.html
<!-- LEVEL 1: OUTER wrapper. Full viewport width. Page background goes here. -->
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%"
style="width:100%;background-color:#F5F5F5;">
<tr>
<td align="center" style="padding:20px 10px;">
<!-- LEVEL 2: CONTAINER. 600px desktop, fluid on mobile. ONLY level with a fixed width. -->
<!--[if mso | IE]>
<table role="presentation" align="center" width="600" cellpadding="0" cellspacing="0" border="0">
<tr><td>
<![endif]-->
<table role="presentation" cellpadding="0" cellspacing="0" border="0"
style="max-width:600px;width:100%;margin:0 auto;background-color:#FFFFFF;">
<tr>
<td>
<!-- LEVEL 3: INNER content tables. Always width="100%". NEVER 600. -->
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
<tr>
<td>Header content</td>
</tr>
</table>
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
<tr>
<td>Body content</td>
</tr>
</table>
</td>
</tr>
</table>
<!--[if mso | IE]>
</td></tr>
</table>
<![endif]-->
</td>
</tr>
</table>

Three rules to keep in your head:

  • OUTER is width="100%". It’s the page background, edge-to-edge.
  • CONTAINER is the only place a fixed 600 shows up — and it’s inside the MSO conditional. Modern clients see only max-width:600px;width:100%, so they shrink it on narrow viewports.
  • INNER content tables are width="100%" — never width="600". Hardcoding 600 on inner tables is the #1 cause of horizontal scroll on mobile.

Most of the rest of this page is variations on this base. If you’re getting horizontal scroll at 320-400px, the first thing to check is whether an inner table inherited width="600".

Strategy 1 — Media queries (modern clients)

Section titled “Strategy 1 — Media queries (modern clients)”

<style> blocks in the <head> work in Apple Mail, Gmail web, Outlook.com, mobile clients. They do NOT work in Outlook desktop (Windows). For any client that supports them, this is the cleanest pattern:

<style>
@media only screen and (max-width: 600px) {
.email-container { width:100% !important; }
.stack-column { display:block !important; width:100% !important; }
.stack-column img { width:100% !important; height:auto !important; }
.mobile-hidden { display:none !important; }
.mobile-only { display:block !important; }
.mobile-padding { padding:16px !important; }
}
</style>

Then mark each element you want to behave responsively with a class:

<table class="email-container" width="600">
<tr>
<td class="stack-column" width="50%">Column 1</td>
<td class="stack-column" width="50%">Column 2</td>
</tr>
</table>

Strategy 2 — Hybrid (works in Outlook desktop too)

Section titled “Strategy 2 — Hybrid (works in Outlook desktop too)”

The hybrid approach builds a fluid layout using max-width + ghost tables. Even without media queries, the email collapses cleanly when the viewport is narrow:

hybrid-responsive-row.html
<!--[if mso]>
<table role="presentation" width="600" align="center" border="0" cellspacing="0" cellpadding="0">
<tr>
<td width="300" valign="top">
<![endif]-->
<div style="display:inline-block; width:100%; max-width:300px; vertical-align:top;">
<table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td>Column 1 content</td>
</tr>
</table>
</div>
<!--[if mso]>
</td>
<td width="300" valign="top">
<![endif]-->
<div style="display:inline-block; width:100%; max-width:300px; vertical-align:top;">
<table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td>Column 2 content</td>
</tr>
</table>
</div>
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->

This produces:

  • Outlook desktop: sees the MSO ghost table → renders two 300px columns side-by-side.
  • Every other client: sees the inline-block <div>s → they sit side-by-side when the viewport is ≥600px, stack vertically when narrower (because max-width:300px lets them shrink to 100% wrapping).

The hybrid approach works without ANY media query.

Email is conventionally designed at 600px wide because:

  • Outlook desktop reading panes default to ~575-625px.
  • Most webmail apps cap the rendered email at 600-700px even on wide screens.
  • Mobile phones in portrait are 320-440px — narrow enough that 600px content needs to scale or stack.

Some templates use 640px or 680px; this is fine as long as you’re consistent. Going wider than 700px usually means horizontal scroll on the reading pane.

The most common responsive change: two side-by-side columns become two stacked rows on mobile.

<style>
@media only screen and (max-width: 480px) {
.stack { display:block !important; width:100% !important; }
}
</style>
<table width="600">
<tr>
<td class="stack" width="300">Column 1</td>
<td class="stack" width="300">Column 2</td>
</tr>
</table>

Use the inline-block <div> approach from above. The columns naturally stack when they can no longer sit side-by-side without overflowing.

When you want to apply styles ONLY on mobile, use a media query targeting your breakpoint:

<style>
@media only screen and (max-width: 480px) {
/* Bigger touch targets for buttons */
.mobile-button {
width:100% !important;
max-width:none !important;
font-size:16px !important;
padding:14px 20px !important;
}
/* Larger body text */
.mobile-text {
font-size:18px !important;
line-height:28px !important;
}
/* Tighter spacing */
.mobile-pad-sm {
padding:16px !important;
}
}
</style>

The !important is required in all of these — without it, inline styles win over <style> block rules.

<style>
@media only screen and (max-width: 480px) {
.mobile-hidden { display:none !important; }
.mobile-only {
display:block !important;
width:100% !important;
height:auto !important;
max-height:none !important;
visibility:visible !important;
overflow:visible !important;
}
}
</style>
<!-- Hidden on mobile -->
<table class="mobile-hidden">
<tr><td>Desktop-only content</td></tr>
</table>
<!-- Shown only on mobile -->
<table class="mobile-only" style="display:none; max-height:0; overflow:hidden;">
<tr><td>Mobile-only content</td></tr>
</table>

The mobile-only block must start with display:none; max-height:0; overflow:hidden; inline — this is the “deep hide” pattern that works across Gmail and Outlook web.

Images on responsive emails need max-width:100% in their inline style:

<img src="https://placehold.co/600x300/E0E0E0/E0E0E0"
width="600" height="auto" alt="..."
style="height:auto; display:block; max-width:100%;">

The width="600" attribute is what desktop clients use; max-width:100% lets mobile clients shrink it down without overflowing.

For Retina-quality (sharp on high-DPI screens), serve images at 2× the displayed size. A 600px-wide hero should be a 1200px-wide source image, displayed at width 600.

A subtle bug that produces horizontal scroll even when your layout looks correct. The setup looks like this:

<style>
@media only screen and (max-width: 600px) {
.stack { width:100% !important; display:block !important; }
}
</style>
<table width="600" style="max-width:600px;width:100%;">
<tr>
<!-- Two columns with padding -->
<td class="stack" width="50%" style="padding:20px 40px;">Column 1</td>
<td class="stack" width="50%" style="padding:20px 40px;">Column 2</td>
</tr>
</table>

On mobile, the .stack rule kicks in: each <td> becomes display:block; width:100%. The padding (40px left + 40px right = 80px) is added on top of the 100% width because the default box-sizing is content-box. Result: each stacked <td> is 100% + 80px wide → horizontal scroll.

Two fixes. Pick the right one based on whether you can rely on box-sizing:

Section titled “Fix 1 — Padding on an inner wrapper (recommended)”

Move the padding off the cell. Put it on a <div> inside the cell:

stack-fix-inner-wrapper.html
<style>
@media only screen and (max-width: 600px) {
.stack { width:100% !important; display:block !important; }
}
</style>
<table width="600" style="max-width:600px;width:100%;">
<tr>
<td class="stack" width="50%">
<div style="padding:20px 40px;">Column 1</div>
</td>
<td class="stack" width="50%">
<div style="padding:20px 40px;">Column 2</div>
</td>
</tr>
</table>

Works in every client. The cell is fluid; the inner <div> carries the padding without inflating the cell’s box. This is the canonical fix.

Fix 2 — calc() to subtract padding from width

Section titled “Fix 2 — calc() to subtract padding from width”

If you must keep padding on the cell, subtract it explicitly:

<style>
@media only screen and (max-width: 600px) {
.stack {
width: calc(100% - 80px) !important;
display: block !important;
}
}
</style>

Brittle — you have to recompute every time padding changes, and calc() is fine in modern clients but inconsistent in older ones. Use Fix 1 when you can.

Before sending a responsive email, test on at least:

  • Mobile: iPhone Mail (iOS), Gmail iOS, Gmail Android, Outlook iOS
  • Desktop webmail: Gmail web (Chrome), Outlook.com (web)
  • Desktop apps: Outlook 2019 or 365 (Windows), Apple Mail (Mac)
  • Dark mode: Apple Mail dark, Outlook dark, Gmail dark — see Dark Mode for the dark-specific patterns

Litmus and Email on Acid render across this matrix automatically.

  • Outlook & MSO — the conditional comment pattern that powers hybrid responsive
  • Imagesmax-width:100% and scaling
  • Buttons — touch-target sizing for mobile
  • Dark Modeprefers-color-scheme works inside @media blocks