Skip to content

feature(html/mjml): updates/reduction to underlying html - #3059

Open
dazgreer wants to merge 21 commits into
masterfrom
feature/html-updates-and-tidy
Open

feature(html/mjml): updates/reduction to underlying html#3059
dazgreer wants to merge 21 commits into
masterfrom
feature/html-updates-and-tidy

Conversation

@dazgreer

@dazgreer dazgreer commented Mar 5, 2026

Copy link
Copy Markdown
Collaborator

Highlights

  • mj-title throws an error if missing or empty
  • optional Outlook and dark mode support
  • add space to preview text
  • comprehensive tidy of HTML to reduce code bloat
  • updated tests and docs

Details

Features

  • mj-title throws an error if missing or empty (added test, updated doc)
  • added support-outlook-classic option to mjml tag to remove support for Outlook (removes ghost tables and other Outlook specific code. Set to `true by default) (updated doc)
  • added option to mj-preview to add blank space after preview text (updated doc) [Fixes Create automatically white space after <mj-preview> text #1829]

HTML changes

General

  • auto removes any empty attribute instance of class=“” or style=“”
  • removed | IE from all conditional comments
  • updated docs for all attribute changes

html

  • removed namepsacexmlns attribute as no longer needed
  • only shows namepsace xmlns:o attribute when support-outlook-classic=“true"
  • only shows namepsace xmlns:v if background-url is present on mj-hero or mj-section

meta

  • updated charset tag
  • removed X-UA-Compatible tag as no longer useful
  • updated viewport tag to include user-scalable=yes
  • added format detection tags to prevent clients from creating links from dates, contact details etc
  • added dark mode tags behind support-dark-mode option (on <mjml> tag) to allow dark mode support

head (skeleton)

  • removed .mj-outlook-group-fix CSS as only applies to older unsupported Outlook versions (2003 and less)
  • removed -ms prefixed reset styles when support-outlook-classic="false"
  • changed -webkit-text-size-adjust to text-size-adjust
  • changed default margin from 13px to 1em
  • removed <link> font import, now uses just @import CSS
  • added dark mode :root CSS behind support-dark-mode option (on <mjml> tag) to allow dark mode support

style (skeleton)

  • removed type=“text/css” as no longer required
  • removed some spacing between CSS rules
  • concatenates <style> blocks where possible
  • removed width declaration for mj-column-per-100 as it’s declared by default in the tag
  • added format detection CSS to prevent clients from creating links from dates, contact details etc

table

  • updated all instances of role=“presentation” to role=“none”
  • removed all <tbody> tags [breaking change for any user added CSS that relies on it in a selector]

font-family declarations

  • removed Helvetica and Arial as fallbacks [small breaking change as fonts will change to system default sans-serif font]

mj-body

  • added xml:lang=“” (populated from globalData language, declared on mjml tag)
  • removed word-spacing as declared on the child div

mj-accordion

  • concatenated CSS selectors with identical declarations
  • changed border-bottom declaration to 0 and now only outputs when border is not already set to 0 or none
  • removed vertical-align as it’s default to the tag
  • fixed Gmail specific CSS (broken by Prettier) and broken out accordion CSS into its own <style> block as it broke other things in Gmail

mj-accordion-element

  • removed padding attribute as it’s default to the tag

mj-accordion-title

  • moved background-color attribute to parent table tag rather than both td tags

mj-button

  • removed text-transform, font-weight attributes as default to the tag
  • removed target attribute as not necessary in email
  • set display to block and removed mso-padding-alt declaration to solve issue where the full button is not clickable when the width is set
    • added multiline option to allow users to negate issues with the above in Outlook classic when button text wraps
  • removed default cursor: auto set on <td> and margin: 0 set on <a>
  • added function to remove 1px padding to compensate for the added border

mj-carousel

  • added prettier-ignore comment to fallback image as Prettier was breaking it
  • concatenated CSS selectors for identical declarations, where possible
  • changed carousel unique ID from 16 characters to 6
  • removed mso-hide-all attributes as the carousel is not displayed in Outlook
  • removed border-radius from Outlook fallback image as Outlook does not display it
  • created shared CSS for multiple carousel instances as previously it was duplicated
  • removed default target on links and height on image as not needed.
  • removed [owa] class as no longer used

mj-carousel-image

  • now respects target attribute
  • checkbox set to just checked for first input rather than checked="checked"

mj-column

  • removed direction attribute as default to the tag
  • removed vertical-align declaration from table as set in children
  • removed mj-outlook-group-fix class as only applies to older unsupported Outlook versions (2003 and less)

mj-divider

  • now uses either table or p (based on support-outlook-classic) and only outputs one
  • removed px units for html table width as they don’t work
  • added max-width to both table and p to better handle width

mj-group

  • removed direction attribute as default to the tag
  • removed 'text-align': 'left' from div as default
  • removed mj-outlook-group-fix class as only applies to older unsupported Outlook versions (2003 and less)

mj-hero

  • removed unnecessary background-url and padding defaults
  • removed duplication of background-repeat declaration in background shorthand value
  • reduced value of background-position declaration to single center
  • added closing </td> tags for fluid height hero as self-closing tag is not valid
  • removed vertical-align declaration from <tr> tag
  • fixed issue where left padding disappeared in Outlook when background-url (VML) added

mj-image

  • removed fluid-on-mobile CSS when not set
  • removed unnecessary class on td and changed CSS selector to accommodate
  • removed text-decoration, outline and font-size as attributes as default to the tag
  • removed target attribute as not necessary in email

mj-navbar

  • hamburger now takes set font-family / font-size / line-height when declaring in mj-attributes > mj-all. Previously it was using its own
  • removed hamburger CSS when hamburger is not set [Fixes Navbar without hamburger unnecessary css #2848]
  • removed ico-text-decoration attribute as default to the tag
  • concatenated CSS selectors with same rules
  • removed 'mso-hide': 'all' and '-moz-user-select': 'none' as not required
  • changed navbar unique ID from 16 characters to 6

mj-navbar-link

  • removed font-weight as attribute as default to the tag
  • removed target attribute as not necessary in email

mj-section

  • removed direction, background-repeat and background-size as attributes as default to the tag
  • removed multiple declarations of background-color or color reference in background as unnecessarily declared multiple times
  • changed text-align option to column-align as it was poorly named. Aliased the deprecated option for backwards compatibility [updated docs]
  • fixed issue where left padding disappeared in Outlook when background-url (VML) added

mj-social

mj-social-element

  • removed text-align and vertical-align as attributes as default to the tag

mj-table

  • removed width declaration when user sets to auto as default to tag
  • removed table-layout attribute as default to the tag. If the user adds, it will also not be be written out
  • updated associated test

mj-wrapper

  • removed text-align from docs as it doesn't do anything

@guigui64 guigui64 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good to me.

Just one remark on this:

removed all tags [potential small breaking change for any user added CSS that relies on it in a selector]

I think it is a breaking change (not a small one) and you should list it in the release notes.

Comment thread packages/mjml-core/src/index.js Outdated
@dazgreer
dazgreer force-pushed the feature/html-updates-and-tidy branch from 3842f49 to 3184a11 Compare March 18, 2026 15:35
@totocap
totocap changed the base branch from fix/replace-html-minifier to master April 16, 2026 07:58
@dazgreer
dazgreer force-pushed the feature/html-updates-and-tidy branch from d23ff3b to 01e2d5e Compare April 28, 2026 13:08
Comment thread packages/mjml-core/src/helpers/styles.js Outdated
Comment thread packages/mjml-core/src/createComponent.js Outdated
Comment thread packages/mjml-core/src/index.js Outdated
Comment thread packages/mjml-core/src/index.js Outdated
Comment thread packages/mjml-core/src/index.js Outdated
Comment thread packages/mjml-validator/src/rules/requiredTitle.js Outdated
@dazgreer
dazgreer force-pushed the feature/html-updates-and-tidy branch from 8ca10ab to 1f4e431 Compare April 29, 2026 09:52
@dazgreer
dazgreer force-pushed the feature/html-updates-and-tidy branch from 1f4e431 to 9549fec Compare May 19, 2026 13:16
@dazgreer
dazgreer force-pushed the feature/html-updates-and-tidy branch from 9ac8c4a to 09954e2 Compare May 27, 2026 09:20
Comment thread packages/mjml-core/src/helpers/mergeHeadStyleBlocks.js
Comment thread packages/mjml-core/src/helpers/mergeHeadStyleBlocks.js Outdated
Comment thread packages/mjml-core/src/helpers/mergeHeadStyleBlocks.js Outdated
dazgreer and others added 11 commits July 7, 2026 12:07
- mj-title throws an error if missing or empty
- optional Outlook and dark mode support
- add space to preview text
- comprehensive tidy of HTML to reduce code bloat
- updated tests and docs
mjml-core
- Updated Prettier to use single quotes inside of CSS
- Broken out the accordion CSS into its own style block as it was breaking other CSS and causing non functional components, e.g. carousel in Gmail and updated test

mjml-section
- fixed issues caused by removing background-size and background-repeat as default attributes whereby the default values were used to determine VML settings. Created automated test
- removed multiple declarations of the background color and concatenated two divs that were split because of this
- updated table to use role=“none”
cheerio is a webpack external for the browser bundle, so calling
load() inside mergeHeadStyleBlocks() crashed the smoke test with
'Cannot read properties of undefined (reading load)'.

Replace the cheerio DOM walk with a plain character scanner that
tokenises <head> content into plain-style / whitespace / other
segments and merges consecutive eligible <style> blocks inline.
The merged output is identical; the import of load from cheerio
is retained for the mj-html-attributes feature at the call-site
that is already correctly guarded by an isEmpty() check.

Also fixes no-continue lint errors by using an 'advanced' flag
instead of continue statements in the tokenizer loop.
…t tests

- extracted mergeHeadStyleBlocks into its own helper module and imported
- added test file covering 29 unit tests.
…ibutes

- background-color applied to body tag in mj-body
- background-color applied to parent table tag instead of both td tags in mj-accordion-title
- now scans mj-wrapper when deciding whether to include the xmlns:v namespace.
- now changes the support-outlook-classic="false" attribute to a string before comparing, correctly evaluating to false. Added automated test
- Updated extraction boundaries from tbody to the raw div itself in in mj-raw
- Removed fallback fonts from mj-text
- Updated indentation outpu

Co-authored-by: Copilot <copilot@github.com>
- Refactored expressions for simplicity
- Updated printWidth value for Prettier
- Removed mj-title validation and testing
…r following rebase

The beautify tests were written against the old Prettier formatter. Now that mjml-core uses js-beautify, two fixtures needed updating:

- "keeps raw html comment spacing": js-beautify does not insert a space after HTML comments, so expectedBeautified now matches actual output. Added skipFragmentDiffCheck: true since the fragment is unchanged by beautify (the document-level check still asserts overall reformatting).

- "wraps long raw html start tags": deleted. js-beautify uses wrap_line_length: 0 by default and does not split long attribute lists across lines. The same input is already covered by the passing standalone it() "keeps long raw html start tags and attributes intact while beautifying".

Also deleted a duplicate standalone it() for the comment-spacing case that searched for <tbody> as its start token. MJML never emits that tag inside an mj-raw block, so the test always failed with "Missing start token".

Co-authored-by: Copilot <copilot@github.com>
dazgreer and others added 9 commits July 7, 2026 12:08
Now that beautification uses js-beautify instead of prettier, prettier-ignore comments serve no purpose and should not appear in rendered output.

- Removed the post-render strip of /* prettier-ignore */ and <!-- prettier-ignore --> from mjml-core

- Removed /* prettier-ignore */ comments in mj-accordion and mj-carousel
…apple message formatting

- Updated column-align default and inheritance from text-align as the colum-align default was overriding the user specified text-align value

- removed <meta name="x-apple-disable-message-reformatting"> from skeleton as it's legacy code (iOS9 and bellow)

Co-authored-by: Copilot <copilot@github.com>
…ed meta for CSS

- reintroduced mso-padding-alt for mj-button with multiline="true" to work the same as the current MJML

- added function to remove 1px padding to compensate for the added border

- removed <meta name="format-detection"> as it was ineffective. Replaced with CSS

- Added regression tests for border comensation fix
- shorthand cover declared in background attribute was not working in Yahoo. Updated to remove and add in its own explicit background-size attribute
- updated docs due to changed column-align default in mj-section

- restored default font-size to mj-image so ALT text shows when images are missing

- updated mj-preview default characters based onsupport testing

- restored defult background-size for mj-section

- fixed issue where adding background-url to mj-section causes left padding issues in child mj-column and mj-button and added regression tests
…ormed-head regression test

- added explicit bounds checks before positional indexing in head scanner

- removed advanced state flag and simplify scanner control flow (lint-safe if/else)

- preserved existing merge behavior for plain/attributed style blocks and comments

- added regression test for malformed short token in head (<head><!</head>) to ensure no throw and unchanged output

- synced compiled helper output with source changes
- restored shorthand 'cover' to fix issue with display in Outlook classic

- restored background-color when background-url is set to fix display issue in Gmail/Yahoo on iOS
- changed max-height and max-width to height and width which prevents the spacing characters bleeding into the email body in some email clients
…tons

- only emit VML button class and button-specific mso margin rules for left-aligned mj-button instances

- added section class vml only when a section emits left-aligned button VML rules

- kept non-left-aligned button sections free of unnecessary VML hooks

- removed mj-outlook-group-fix class emission from column output and align section gutter expectations

- add regression coverage for section/button Outlook behavior and template syntax sanitization under minify with mixed multiline/property/value tokens

- clean up unused import in mj-hero
@dazgreer
dazgreer force-pushed the feature/html-updates-and-tidy branch from 351710b to 1b4dd1d Compare July 7, 2026 15:00
dazgreer added a commit that referenced this pull request Jul 9, 2026
…x undefined background-size

- Added background-color to background styles when background-url is present (fixes Gmail/Yahoo iOS rendering)
- Changed background-size default from undefined to 'auto' (prevents '/ undefined' in CSS)
- Aligns with PR #3059 commit defdf0f
dazgreer added a commit that referenced this pull request Jul 9, 2026
Merges feature/html-updates-and-tidy improvements into feature/dark-mode-support:

- Cherry-Picked Commits:
  - f35a14f - fix(mj-preview): updated CSS to remove space in email body
  - 1b4dd1d - fix(mj-section): scope Outlook VML button helpers to left-aligned buttons
- Manually updates
  - 846bcc1 - fix(eslint): remove unused widthParser import from mjml-hero
  - bcc63dd - fix(mj-section): restored background-color with background-url and fix undefined background-size
  - 435ff0c - fix(mj-button/format-detection): reintroduced mso-padding-alt / swapped meta for CSS

Plus additional changes to mjml-core, mjml-head-preview, and mjml-image that are currently staged.

This commit completes the merge of feature/html-updates-and-tidy into feature/dark-mode-support with all PR #3059 changes applied and tested.
dazgreer added a commit that referenced this pull request Jul 9, 2026
…e-support

Merge PR #3059 HTML optimizations into dark-mode-support branch.

Resolved merge conflicts:
- mjml-carousel: Accepted refactored CSS structure + fix hover selector scoping
  - Changed `.mj-carousel-thumbnail:hover` to `.mj-carousel-${ID}-thumbnail:hover`
  - Preserved dark-mode hooks

- mjml-section, mjml-social, mjml-hero, mjml-image: Already integrated in prior commits

- All other conflicts resolved with incoming
dazgreer added a commit that referenced this pull request Jul 9, 2026
…e-support

Merge PR #3059 HTML optimizations into dark-mode-support branch.

Resolves merge conflicts:
- mjml-carousel: Accept refactored CSS structure + fix hover selector scoping
  - Changed `.mj-carousel-thumbnail:hover` to `.mj-carousel-${ID}-thumbnail:hover`
  - Preserves dark-mode hooks and regression test compliance
- All other conflicted files: Resolved by keeping feature/dark-mode-support versions
- README conflicts: Resolved across 12+ component packages

All 283 tests passing.
@totocap
totocap requested a balanced review from Copilot August 27, 2026 14:00

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR reduces generated email HTML, introduces configurable Outlook/dark-mode support, and updates component rendering, documentation, and regression coverage.

Changes:

  • Simplifies generated markup, styles, conditional comments, and table structures.
  • Adds Outlook-classic, dark-mode, preview-spacing, alignment, and button options.
  • Updates component defaults, compatibility fixes, documentation, and tests.

Reviewed changes

Copilot reviewed 61 out of 61 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
packages/mjml/test/utils.js Safely extracts CSS properties.
packages/mjml/test/template-syntax-sanitization.test.js Tests mixed template syntax.
packages/mjml/test/tableWidth.test.js Updates table-width expectations.
packages/mjml/test/support-outlook-classic.test.js Tests Outlook option parsing.
packages/mjml/test/section-gutter.test.js Updates reduced column markup expectations.
packages/mjml/test/section-button-outlook-margin.test.js Tests VML button alignment.
packages/mjml/test/section-background-url-no-background-size.test.js Tests section background defaults.
packages/mjml/test/button-inner-padding-border.test.js Tests button border compensation.
packages/mjml/test/beautify-output.test.js Updates beautification fixtures.
packages/mjml-wrapper/src/index.js Uses shared MSO helpers.
packages/mjml-wrapper/README.md Updates wrapper defaults.
packages/mjml-validator/src/rules/validTag.js Permits root mjml validation.
packages/mjml-validator/src/index.js Validates formerly skipped roots.
packages/mjml-text/src/index.js Simplifies text and Outlook markup.
packages/mjml-text/README.md Updates text defaults.
packages/mjml-table/src/index.js Omits redundant table widths/layouts.
packages/mjml-table/README.md Updates table defaults.
packages/mjml-social/src/SocialElement.js Reduces social-element markup.
packages/mjml-social/src/Social.js Simplifies social tables and conditionals.
packages/mjml-social/README.md Updates social defaults.
packages/mjml-section/src/index.js Adds alignment and VML handling changes.
packages/mjml-section/README.md Documents section API changes.
packages/mjml-parser-xml/test/preprocessors.test.js Adds a title to preprocessing coverage.
packages/mjml-navbar/src/NavbarLink.js Simplifies Outlook link rendering.
packages/mjml-navbar/src/Navbar.js Reduces and deduplicates hamburger output.
packages/mjml-navbar/README.md Updates navbar defaults.
packages/mjml-image/src/index.js Reduces image markup and conditional CSS.
packages/mjml-image/README.md Updates image target defaults.
packages/mjml-hero/src/index.js Simplifies hero and VML markup.
packages/mjml-hero/README.md Updates hero defaults.
packages/mjml-head-title/README.md Adds title-validation documentation.
packages/mjml-head-preview/src/index.js Adds preview filler options.
packages/mjml-head-preview/README.md Documents preview filler attributes.
packages/mjml-group/src/index.js Simplifies group markup and classes.
packages/mjml-group/README.md Updates group defaults.
packages/mjml-divider/src/index.js Selects table or paragraph rendering.
packages/mjml-core/tests/mergeHeadStyleBlocks.test.js Tests style-block merging.
packages/mjml-core/src/index.js Initializes options and post-processes styles.
packages/mjml-core/src/helpers/styles.js Consolidates generated style blocks.
packages/mjml-core/src/helpers/skeleton.js Reduces skeleton and adds feature flags.
packages/mjml-core/src/helpers/mergeOutlookConditionnals.js Supports updated MSO conditionals.
packages/mjml-core/src/helpers/mergeHeadStyleBlocks.js Merges adjacent head styles.
packages/mjml-core/src/helpers/mediaQueries.js Consolidates media-query CSS.
packages/mjml-core/src/helpers/fonts.js Uses CSS-only font imports.
packages/mjml-core/src/helpers/conditionalTag.js Adds configurable Outlook conditionals.
packages/mjml-core/src/createComponent.js Omits empty and false attributes.
packages/mjml-column/src/index.js Reduces columns and adds VML corrections.
packages/mjml-column/README.md Updates column defaults.
packages/mjml-carousel/src/CarouselImage.js Simplifies carousel images and targets.
packages/mjml-carousel/src/Carousel.js Deduplicates carousel CSS and markup.
packages/mjml-carousel/README.md Updates carousel target defaults.
packages/mjml-button/src/index.js Changes button sizing and multiline behavior.
packages/mjml-button/README.md Documents button API changes.
packages/mjml-body/src/index.js Simplifies body markup and language output.
packages/mjml-body/src/helpers/preview.js Updates hidden preview sizing.
packages/mjml-accordion/src/AccordionTitle.js Consolidates title styles.
packages/mjml-accordion/src/AccordionText.js Reduces accordion text markup.
packages/mjml-accordion/src/AccordionElement.js Removes redundant element styles.
packages/mjml-accordion/src/Accordion.js Consolidates accordion CSS and markup.
packages/mjml-accordion/README.md Updates accordion defaults.
doc/components_1.md Updates generated HTML and root options.
Suppressed comments (3)

packages/mjml-section/src/index.js:459

  • Section padding accepts percentages, but getShorthandAttrValue parses away the unit and this rebuilds every value as pixels. For example, padding="10%" becomes a VML inset of 10px while Outlook's normal padding is disabled, producing substantially different spacing. Preserve the declared units or calculate the percentage against the relevant dimensions.
    packages/mjml-column/src/index.js:534
  • The same unit loss affects button outer padding: padding-left="25%" is emitted in the VML correction rule as 25px. Since % is allowed for button padding, left-aligned buttons in background sections receive the wrong Outlook margin. Preserve the unit or resolve the percentage against the button container.
            if (isLeftAlignedButton && hasSectionBackground) {
              const buttonClassName = `vml-button-${genRandomHexString(6)}`
              const buttonLeftPadding = `${component.getShorthandAttrValue('padding', 'left')}px`

packages/mjml-button/src/index.js:122

  • This second mso-padding-alt declaration also survives support-outlook-classic="false". Apply the same global support check here so the option consistently removes Outlook-only CSS.
        ...(this.getAttribute('multiline') === true && { 'mso-padding-alt': '0px' }),

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.


const tokens = []
let pos = 0
while (pos < headInner.length) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated so that scanner emits malformed remaining input and sets pos = headInner.length for:

  • Unterminated <!-- comments
  • Unterminated plain <style>
  • Attributed <style> without a closing tag
  • Attributed <style ... without a closing >

Also added regression testing for these cases

Comment on lines +115 to +116
} else if (nt.type === 'plain-style') {
combinedCss += `\n${nt.css}`

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated to detect a later plain <style> block whose CSS begins with @import or @charset and keeps it separate instead of concatenating it after earlier CSS.

Added regression tests for both cases

Comment thread packages/mjml-carousel/src/Carousel.js Outdated
Comment on lines +56 to +60
if (globalData && includeSharedStyles) {
globalData.carouselSharedStylesEmitted = true
}

const carouselCss = `
if (!length) return ''

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated to check length and returns before setting globalData.carouselSharedStylesEmitted = true.

Added regression test for this case

Comment on lines +83 to +85
const icoFontFamilySet =
this.props && this.props.rawAttrs &&
Object.prototype.hasOwnProperty.call(this.props.rawAttrs, 'ico-font-family')

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to preserve resolved global and class attributes in rawAttrs.

Added regression testing for both cases to demonstrate inheritance

Comment on lines +124 to +126
...(this.context.hasSectionBackgroundUrl === true && {
'mso-para-margin-left': `${this.getShorthandAttrValue('padding', 'left')}px`,
}),

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated to add a unit-preserving getPaddingLeft() function and applied it in two instances

  • the column gutter preserves % instead of appending px.
  • the left aligned button VML margin

Added test verifying both of these

Comment thread packages/mjml-head-title/README.md Outdated
Comment on lines +9 to +13
- The validator emits a warning when `mj-title` does not exist or is empty or whitespace-only.

Note: MJML always outputs a `<title>` element in the final HTML. If you omit `mj-title`, the `<title>` is included but may be empty; provide a meaningful `mj-title` to avoid empty titles.

In projects using `validationLevel: 'strict'`, this warning becomes an error and will stop rendering.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed this from the docs. I initially added validation but removed it as overreach.

}

static defaultAttributes = {
'fill-space': '0',

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't added space by default (to prevent a breaking change) but this does gives the user control over adding it.

I have normalised negative values to 0, added regression testing and updated documentation for clarity

Comment thread packages/mjml-core/src/helpers/skeleton.js Outdated
Comment thread packages/mjml-section/src/index.js Outdated
'padding-right': this.getAttribute('padding-right'),
'padding-top': this.getAttribute('padding-top'),
'text-align': this.getAttribute('text-align'),
...(hasBackground && { 'mso-padding-alt': '0' }),

@dazgreer dazgreer Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated to include supportOutlookClassic from the global context and emit mso-padding-alt:0 only when it's not set to false.

line 459 doesn't contain an mso-padding-alt declaration.

Comment thread packages/mjml-button/src/index.js Outdated
'font-style': this.getAttribute('font-style'),
height: this.getAttribute('height'),
'mso-padding-alt': this.getAttribute('inner-padding'),
...(this.getAttribute('multiline') === true && { 'mso-padding-alt': this.getContentPadding() }),

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated to gate the mso-padding-alt declaration on both multiline="true" AND supportOutlookClassic in two instances.

Added regression testing for this case

- Updated mergeHeadStyleBlocks so that scanner emits malformed remaining input and sets `pos = headInner.length` for various cases, thus preventing malformed. Added regression testing.

- Updated `mj-carousel` to check `length` and returns before setting `globalData.carouselSharedStylesEmitted = true` to prevent empty carousel from omitting shared carousel CSS. Added regression testing

- Updated to `mj-navbar` to preserve resolved global and class attributes in `rawAttrs`. Added regression testing

- Updated `mj-column` to add a unit-preserving `getPaddingLeft()` function and applied it in two instances. Added regression testing

- Removed `mj-title` validation explanation from docs as validation was perviously removed

- Normalised negative values of `fill-space` to `0` for `mj-head-preview`. Added regression testing and updated documentation for clarity

- Updated CSS reset in skeleton so Outlook rule is only emitted when `supportOutlookClassic` isn't enabled.

- Updated `mj-section` to include `supportOutlookClassic` from the global context and emit `mso-padding-alt:0` only when it's not set to `false`.

- Updated `mj-button` to gate the `mso-padding-alt` declaration on both `multiline="true"` AND `supportOutlookClassic` in two instances. Added regression testing
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

4 participants