Skip to content

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

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

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

Conversation

@dazgreer
Copy link
Copy Markdown
Collaborator

@dazgreer dazgreer commented Mar 5, 2026

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 detections 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

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>

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

Copy link
Copy Markdown
Collaborator

@guigui64 guigui64 left a comment

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 and others added 13 commits May 27, 2026 11:19
- 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>
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>
@dazgreer dazgreer force-pushed the feature/html-updates-and-tidy branch from 9ac8c4a to 09954e2 Compare May 27, 2026 09:20
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants