You should have one (1) rule in Form Editor » Custom CSS
Only so many “banes of your existence” before the term is meaningless. But form-level Custom CSS (Form Settings » Form Theme » ⚙) makes the cut for me.
The Custom CSS box enables multiple bugs:
- Styles meant for all forms are isolated within the form. They persist if cloned, but are lost when someone starts a form from scratch (and they will!).
- Styles meant for one or a subset of forms are erroneously cloned to other forms, where they have to be overridden by sloppy
!importantrules. - Style changes aren’t propagated, by definition. It’s impossible to know whether a form uses the latest CSS.
#mktoForm_nnnn. That’ll break when somebody clones the form to make changes. Instead, assign a data-attribute like <form data-form-name="product-registration"> to find the form regardless of ID.The correct place for form styles is external .css stylesheets(s). In my horrifyingly long experience, it’s impossible to coordinate styles in Custom CSS with page-level and site-level <style>s and <link>s!
Conventional approach: Custom CSS is empty, <link> loads external styles
You can simply <link> external styles before calling loadForm():
<link rel="stylesheet" href="https://pages.example.com/rs/123-ABC-456/images/forms-core.css">
<script src="//pages.example.com/js/forms2/js/forms2.min.js"></script>
<form id="mktoForm_12213"></form>
<script>MktoForms2.loadForm("//pages.example.com", "123-ABC-456", 12213);</script>On Marketo LPs, a variable or token allows staged rollouts of new styles:
<link rel="stylesheet" href="https://pages.example.com/rs/123-ABC-456/images/forms-core-${stylesVersion}.css">
<script src="//pages.example.com/js/forms2/js/forms2.min.js"></script>
<form id="mktoForm_12213"></form>
<script>MktoForms2.loadForm("//pages.example.com", "123-ABC-456", 12213);</script><link>s are render-blocking by default, so with the above code, there’s zero chance of styles not being applied the moment the form appears. You do want style loading to block form rendering, otherwise the form restyles in a (sometimes) user-perceptible way, i.e. FOUC.[1]
Bad news is <link>s block all rendering, not just resources related to the form. Stylesheets usually download + parse fast enough, especially if hosted in Design Studio/Cl0udflare, users won’t notice a delay. But in extreme cases (slow servers and/or connections) the delay is user-facing.
The media-switch hack[2] makes a <link> effectively async:
<link rel="stylesheet" href="https://pages.example.com/rs/123-ABC-456/images/forms-core.css" media="(--nonblocking)" onload="removeAttribute('media')">But then, by design, the form might render before styles are ready. Hmm.
Better approach: Custom CSS with @import + tiny companion <style> in the page
Stylesheets can load other stylesheets via @import. So on a basic level, the Custom CSS box doesn’t need to have any style rules. It can have just one at-rule:
@import "https://pages.example.com/rs/123-ABC-456/images/forms-core.css"; But there’s still a problem! Forms 2.0 injects a <style> element with the Custom CSS into <head> before injecting the <form>, but that only guarantees DOM mutation is ordered. It doesn’t guarantee rules inside the <style> take effect synchronously. Browsers even differ on whether @import in script-injected <styles> is synchronous or not.[3]
So depending on browser quirks, users can see a flash of default-styled content:

For the recording, I deliberately overdramatized the effect of async styling by adding a 500ms delay to the @import-ed stylesheet. This isn’t realistic — you won’t see performance that bad from Design Studio assets — but ensures you see what I’m blathering about.😃
The solution is this <style> before the form embed:
<style>
#mktoStyleLoaded { border-top-color: #000000 !important; }
</style>
Then in your forms-core.css (doesn’t have to be at the top, but that’s clearest):
#mktoStyleLoaded { border-top-color: #123456 !important; }Why does it work?
Connecting a new <style> to the DOM doesn’t tell you when styles are applied. (In contrast, <link> and <style> in the initial HTML leave no mystery about when they take effect.) A load listener on the <style> would do it, but we can’t get inside the Forms 2.0 library to add that.
Luckily, the library has internal polling logic to wait for standard CSS + theme CSS to be applied before showing the form. Notice how those built-in styles aren’t affected by worries about custom styles?
We can leverage that logic. A “sentinel” style that tells the library styles are ready is border-top-color: #123456. (This style is on a hidden <div>, never shown to the outside world.) By taking control of that style, the form’ll wait for our stylesheet to be loaded and active.[4] Presto!
Yes, you could do the same thing with a media-switched <link>
You can do the #mktoStyleLoaded trick with a <link>’d stylesheet, too. Doesn’t have to be @import’d in Custom CSS. But once that one-line <style> is part of your standard embed code, you won’t have to ask IT to make changes later and can @import additional stylesheets on your own.
Notes
[1] It’s sometimes okay if styles load after the form, long as it’s very soon after. If styles are only for error messages or fields shown via Visibility Rules, those aren’t necessary on initial render. Still, you shouldn’t knowingly introduce a race condition.
[2] The typical maneuver is setting <link media="none"> or <link media="print"> and switching to media="all" on load. But none is invalid per spec, and print is misleading. I prefer media that’s syntactically valid but private: media="(--nonblocking)" is a feature check for a valid<ident>, but --nonblocking will never be standard ’cause it starts with --.
N.B. media-switch uses JS. That li’l onload could be blocked by CSP unless you allow it with unsafe-inline or unsafe-hashes. In fairness though, Forms 2.0 isn’t built for ultra-strict CSP environments. Can’t really have it both ways, “We control every piece of code” and “Marketo handles our forms”!
[3] In Blink (Chrome/Edge) a script-injected @import becomes asynchronous, so rules after @import take effect while the external sheet is still loading. In Firefox, script-injected @import remains synchronous. As usual, Webkit (Safari) uses obscure prerendering logic to avoid FOUC, which serves mostly to mislead you about standards-based behavior.
[4] Astute/awake readers will note forms-core.css becomes the sole styling dependency for the form (replacing forms2.css and forms2-theme-<theme>.css). There’s another way to hack through that, but provided all .css files are in Design Studio you’ll be fine 99.9999% of the time as the default files start loading first.