CSS in email: which properties survive each client, and what to use instead

By Mara Levin · Updated October 7, 2026 · caniemail data and tool docs read that day

The short answer

Email CSS is a lowest-common-denominator job. Write the layout in tables, put every rule that carries meaning (colour, size, padding, width) inline on the element, and treat the <style> block as an enhancement layer for hover, media queries and dark mode. Avoid anything that only Apple Mail renders: aspect-ratio, :has(), :not(), nesting and ::before. Use six-digit hex colours, because modern colour functions make Gmail delete the element's inline styles. The numbers below come from caniemail.com, with the feature page linked beside each claim.

Three places your CSS gets read

Support questions sound endless until you split clients into three groups. The first is Outlook for Windows (the desktop versions 2007 to 2019 in caniemail's data), the column with the most "No" answers: it skips many of the layout properties you reach for first and has its own quirks for sizes and line height. The second is Gmail, which does read CSS but filters it. Per the style element entry, Gmail does not support a style element inside the body, caps the style tag at 16 KB, and in the iOS and Android apps does not support it at all for non-Google accounts; the same non-Google-account limit appears on class selectors, @media, object-fit, calc(), clamp() and text-decoration. The third group is everything else: Apple Mail, Outlook.com, Samsung Email and Yahoo accept most things with scattered exceptions.

That grouping gives you the working rule. A property is safe when it is supported in all three groups, "enhancement only" when it is supported in Apple Mail and some others, and off limits when its absence would break the message. The next two tables apply it to the properties that cause most debates.

Boxes and text: what holds up

Property Gmail web Gmail iOS Outlook Win Outlook.com Apple Mail Yahoo Caveat
width Yes Yes Partial Yes Yes Yes Outlook for Windows ignores it on body, span, div, p and img; put widths on table cells and use the width attribute on images.
height Yes Yes Partial Yes Yes No Outlook for Windows skips it on div and p; Yahoo swaps height for min-height.
min-width Yes Yes No Yes Yes Yes Do not rely on it to hold a column open in Outlook.
box-sizing Yes Yes No Yes Yes No Treat padding as extra width in Outlook and Yahoo, and size cells accordingly.
opacity Yes Yes No Yes Yes Yes Use a solid colour instead of a transparent overlay.
border-spacing Yes Yes No Yes Yes Yes Set cellspacing="0" on the table as well.
line-height Yes Yes Buggy Yes Yes Yes Outlook for Windows treats em and px values oddly; add mso-line-height-rule:exactly.
letter-spacing Yes Yes Partial Yes Yes Yes Em values come out smaller than in a browser; big negative values render differently.
text-decoration Yes Partial Partial Yes Yes Yes Stay with a single value such as underline or none; overline and multi-value forms are unsupported in Outlook.

Source: caniemail.com data last updated September 16, 2026; the newest entry per client is shown. Outlook Win is the desktop line up to 2019. Where a cell says Partial, the linked page lists the exact limit.

Fixes for the usual Outlook problems

Outlook's line-height problem is one line to fix. The line-height entry says em and px behave weirdly there and to use mso-line-height-rule:exactly, which every other client ignores:

<td style="font-size:16px; line-height:24px; mso-line-height-rule:exactly; font-family:Arial, Helvetica, sans-serif;">
  Order 4812 shipped today.
</td>

Images need the same belt-and-braces approach. The width attribute entry notes that percentage widths on images are based on the file's physical width in some clients, and the CSS width entry says Outlook ignores it on img. So put the pixel size in the attribute, add the fluid rule in style, and keep display:block to avoid the stray gap under the image:

<img src="https://example.com/img/hero.jpg" width="600" height="300" alt="Autumn range"
     style="display:block; width:100%; max-width:600px; height:auto; border:0;">

Modern CSS: Apple Mail only, so enhancement at best

The features designers ask for after reading a browser changelog share one trait in the data: Apple Mail renders them and most others do not. That makes them useful as progressive enhancement and dangerous as a foundation.

Feature Gmail web Gmail iOS Outlook Win Outlook.com Apple Mail Yahoo Use instead
aspect-ratio No No No No Yes No Give images explicit width and height attributes.
object-fit Yes Partial No Yes Yes No Crop the image file itself.
clamp() Yes Partial No No Yes No Fixed px size plus one media-query step.
calc() Yes Partial No No Yes No Pre-compute widths in the build.
CSS nesting No No No No Partial No (nested rules stripped) Flatten rules before sending.
@supports No No No No Yes Yes (rewritten) Write the fallback as the base rule.
:has() No No No No Yes No Add a class to the element in the template.
:not() No No No No Yes No Use two classes instead.
::before No No No Partial Yes No Put the decoration in real markup.

Same source and date as the table above. Apple Mail is the macOS client; its nesting entry is partial because the plain nested form needs a leading ampersand.

Five ways CSS fails silently

The nastiest email bugs delete rules without a visible error. These are documented in the data rather than folklore:

  • Whitespace colour syntax. Per the rgba entry, rgb(0 128 0 / 1) makes Gmail strip the entire style attribute, or the entire style block if it appears there.
  • Modern colour spaces. The modern colour entry says that in Gmail one oklch() value removes all inline styles on that element.
  • Nesting in Yahoo. The nesting entry records that the nested selectors are removed, so the nested properties apply to the parent selector. A hover rule can end up colouring the whole button.
  • !important spacing. The !important entry says Gmail accepts it only in lowercase, Outlook for Windows only with a space before it (and not inline), and Yahoo drops it when there is no space and the rule sets a background image. Write color: #fff !important exactly like that.
  • Declaration order in Outlook. The style entry says Outlook for Windows needs a style element declared before its rules are used, which breaks designs that inject CSS late.
/* Do: hex, with a plain fallback line before any enhancement */
color: #1f2937;
background-color: #0a58ca;

/* Don't: whitespace colour syntax. caniemail notes Gmail strips the whole style attribute or style block */
color: rgb(31 41 55 / 1);

/* Don't: lab(), oklch(). Gmail removes every inline style on the element that uses them */
background-color: oklch(55% 0.2 260);

A separate habit worth keeping: write the plain version first, then the enhancement. A hex colour line before a gradient, a fixed pixel size before a clamp, a block level default before a media query. Clients that drop the enhancement fall back to the plain line.

Where to put the CSS

Use both places, each for its own job. Inline holds layout and typography; the head block holds what cannot live inline (pseudo-classes such as hover, media queries, dark-mode rules). The :hover entry shows why you cannot count on interactivity: hover is not supported in Gmail iOS or Outlook for Windows and is partial in Outlook.com. Keep that block short, because of the 16 KB cap, and put it in the head, since Gmail does not read style in the body.

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    /* Keep this block small: Gmail webmail limits it to 16 KB */
    .stack { display: block !important; width: 100% !important; }
    a.cta:hover { background-color: #0848a3 !important; }
  </style>
</head>
<body>
  <!-- Everything that must survive also sits in style="" on the element -->
  <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
    <tr>
      <td class="stack" width="300" style="width:300px; padding:16px; font-family:Arial, Helvetica, sans-serif; font-size:16px; line-height:24px; color:#333333;">
        Your plan renews on October 21.
      </td>
    </tr>
  </table>
</body>

Which tool writes this CSS for you

A framework, an inliner or a visual builder each takes a different part of the job. The table says what each documents for CSS, nothing more; none of the four documents a promise of identical rendering across clients.

Tool Job What its documentation says about CSS
MJML Compiles components into table markup mj-style adds CSS to the head by default and inlines it with inline="inline"; mj-attributes sets defaults per component, and attributes written on a tag win over those defaults.
React Email Renders React components to an HTML string The Tailwind component converts classes for you, and its docs tell you to apply pixelBasedPreset because Tailwind's default rem units are not supported by some email clients.
Maizzle Build pipeline with Tailwind and transformers CSS inlining is on by default, explained as important mainly because of Gmail and Outlook on Windows; a style tag carrying the embed attribute is skipped, and Juice options pass through.
Juice Inlines CSS into style attributes It copies rules into style attributes and, per its README, flattens nested rules and passes @container and @layer through; its own docs point readers to caniemail for client support.

In MJML, the two-block pattern looks like this: one mj-style inlined for rules that must survive, one left in the head for the media query.

<mjml>
  <mj-head>
    <mj-style inline="inline">
      .note { color: #5b6470; }
    </mj-style>
    <mj-style>
      @media (max-width: 480px) { .note { font-size: 14px !important; } }
    </mj-style>
  </mj-head>
  <mj-body>
    <mj-section>
      <mj-column>
        <mj-text css-class="note">Prices include VAT.</mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

With Juice on its own, inlining is a single call, shown in its README. Maizzle wraps the same engine, so the options it lists (such as removing inlined selectors from the style tags, or a safelist to keep selectors) carry over:

import juice from 'juice';

const inlined = juice(sourceHtml); // rules in <style> are copied into style="" attributes
import { inlineCss } from '@maizzle/framework'

const out = inlineCss(html, {
  removeStyleTags: true,
  preferUnitlessValues: true,
})

React Email inlines at render time through its Tailwind component, and its documentation says the component wraps the email content and takes a config object. The preset line is the CSS decision that matters, since it replaces rem with pixels:

import { Tailwind, pixelBasedPreset, Button } from "react-email";

<Tailwind config={{ presets: [pixelBasedPreset] }}>
  <Button href="https://example.com" className="bg-black px-3 py-2 text-white">
    Open the invoice
  </Button>
</Tailwind>

Visual builders such as Stripo and Beefree generate the CSS for you and hand over HTML at export. Whether the export inlines styles or keeps a style block is a question to answer by exporting a real template and reading it, as the builder guide recommends. Rendering services such as Litmus and Email on Acid show the result in clients; the Litmus vs Email on Acid page covers how they count previews, which decides how many CSS iterations a plan can afford.

A CSS test plan that finds these bugs

  1. Build four fixtures. One plain-text-heavy transactional email, one image-led promotion, one data table, and one with a button and a hero background. Most CSS regressions show up in one of those four shapes.
  2. Add the hostile inputs. A 40-character first name, a missing image, a product title that wraps to three lines, and a right-to-left sample if your list needs one.
  3. Check the three groups. Outlook for Windows, Gmail web plus Gmail app with a non-Google account, and Apple Mail or iOS. If all three pass, the others almost always follow.
  4. Diff the compiled HTML, not the source. After each tool upgrade, compare the output of the four fixtures; an inliner or framework change shows up as a diff before a subscriber sees it.
  5. Re-read caniemail when a client updates. Each feature page shows the date it was last tested, so a gap of years is a reason to check.

For how these limits shape layout, read the responsive email design guide; for colour-mode rules, the dark mode guide; and for the access side, HTML email accessibility.

Frequently asked questions

Why does my email look fine in Gmail on the web and lose its styles in the Gmail app?

Because of the account type. caniemail marks the style element, class selectors, media queries and several properties as unsupported in the Gmail apps when the mailbox is not a Google account, a case people call Gmailify or GANGA in email forums. Anything that must survive there needs to be inline on the element, and any layout that depends on a media query needs a readable single-column default.

Is it safe to use rgb() with a slash or oklch() for brand colours?

No. caniemail records that Gmail strips the whole style attribute when it meets the whitespace form of rgba(), and removes every inline style on an element that uses lab(), lch(), oklab() or oklch(). Write colours as six-digit hex, and if your design system stores them in a modern colour space, convert at build time.

Do I still need to inline CSS in 2026?

For anything that carries meaning, yes. Gmail limits and partly ignores style blocks, and Outlook for Windows needs a style declaration to come before the rules that use it. Maizzle's documentation says inlining remains important mainly because of Gmail and Outlook on Windows. Keep hover states, media queries and dark-mode rules in the style block as enhancements, because they cannot be inlined in the first place.

Which framework produces the safest CSS?

None can promise it, and none of their documentation does. MJML, React Email and Maizzle each compile or inline for you and each says to test in real clients. Pick by the language your team already writes, then confirm the output on the clients in your own list; the MJML versus React Email page on this site compares the two code-first options.