Custom vs legacy components
Custom components replace and enhance our older, legacy components.
Custom components are available on the Pro and Business tiers. Standard components are available across all plans.
If you started using Parcel on or after April 23, 2024, you're already set up to create our latest, custom components!
Custom components are not new to Parcel. You may be familiar with our legacy implementation where you could create components then reference them across any email to render the same code. Later, if you updated the original component, those changes would cascade down across all of your emails. The same applies with our new version of custom components, but below we'll get into the differences and how our next generation is even more powerful.
Overall, the concept and way you reference components in emails has stayed the same. It’s the way you build them that's changed.
Our latest and greatest custom components enable you to:
- run more advanced and complex JavaScript logic - no more complex objects mixed inside your markup. You can pass properties/variables defined in the
<script>
tag to both the<style>
and<template>
tags. - set the HTML inside of MSO comments completely dynamically making components a lot more flexible
- differentiate ESP syntax from the component using the
#is:raw
directive
Here's a high-level summary of the differences between our legacy and latest implementation of custom components.
With our latest, custom components:
- There are now six top-level elements you can utilize. Previously, there were four.
- You can use dashes within your naming structure like
first-name
. - Setting a property is now required by default. You must specify if it's optional, which is opposite from our legacy implementation.
- You must use
#isolated
instead ofdiscrete
with the<style>
tag. - Directives:
- now require hashtags, like
#if
. foreach
has changed to#each
.elseif
is now#else-if
. (note the dash!)- You can now pass an object to the
#set
directive to set multiple attributes at once. You can also use it to set HTML content. - We've added
#is
. It's a new directive that can change the element used.
- now require hashtags, like
- You can now render variables in the
<style>
tag and conditional comments.
Read more about each below. Then proceed to upgrade when you're ready!
Our legacy components had four, top-level elements:
<meta>
- to label the component<fieldset>
- to create variables and set up slots<style>
- to add CSS<component>
- to add the HTML
In our updated components, we now have six, top-level elements:
<script>
- this is used for a wide range of things including labelling the component and setting presets usingconfig
, creating variables including validation and UI settings for the visual editor usingprops
, usingslots
, as well as doing anything you’d normally do in a<script>
tag. For example, you can now create complex logic based on the properties that are passed into the component.<style>
- this is very similar to what we had in the legacy components; however, now you can render variables inside the style tag.<title>
- this can be used to insert a title element into the<head>
of the email.<link>
- this can be used to insert a link element into the<head>
of the email.<meta>
- this can be used to insert a meta element into the<head>
of the email.<template>
- This replaces the<component>
tag from before.
<script>
and <template>
are required in all components.
Where our legacy components allowed for some basic Javascript expressions in the HTML such as toUpperCase()
or JSON.stringify()
, our new components have a <script>
tag that lets you run more advanced and complex JavaScript logic. You can also create new variables and use them in the <template>
. By doing this, it keeps the HTML in the <template>
much cleaner and easier to read.
This means <fetch>
and <scrape>
, which previously worked in the HTML, are now deprecated. Use <context>
in the <script>
tag, which allows for a lot more flexibility.
Setting things in a <script>
tag also opens up more possibility in terms of data validation because it supports the full JavaScript run-time allowing us to do all kinds of advanced and complex JavaScript logic.
Previously, you created variables using <input>
elements inside <fieldset>
. This had a nice familiar feel to it for anyone comfortable with HTML. However, it was also quite limited as we only had the input types and validation that was included in the HTML. This has now changed to creating props
in a <script>
tag.
export const props = Component.defineProps({
'first-name': Component.props.string(),
age: Component.props.number(),
'profile-picture': Component.props.string().url().optional(),
});
This format may be familiar to JavaScript developers, but potentially less so to email developers. We’ll break it down for you here:
export const props = Component.defineProps({})
- where you create and define
props
; we export these so you can edit them in the properties menu of the visual editor.
'first-name': Component.props.string(),
- an example property; the prop is called
first-name
(Note how we have a dash in the name. That’s something that caused issues before but no longer!); theComponent.props
is set tostring
, similar to how we used<input type="text">
before.
'age': Component.props.number(),
- this replaces
<input type="number">
from before.
'profile-picture': Component.props.string().url().optional(),
- this replaces
<input type="url">
from before. Note that this is set to.optional()
. In our legacy components, the default state wasoptional
andrequired
needed to be set. In our latest components, this is reversed.
Previously, you created slots with <input type="slot" name="__content" accept />
, but these now export from the <script>
tag.
export const slots = Component.defineSlots({
default: Component.slots.any(),
});
Here we're exporting the default
slot and saying it can contain any
other components. default
is the default name for a slot; however, if you use named slots, then you can replace this with the slot name.
To limit a slot to only accept certain elements, you can set a list like so:
schema: Component.slots.children(['my-nested-component1','my-nested-component2']),
To set a text slot, you can use:
schema: Component.slots.text()
Styling is similar to legacy components, but now you can render variables inside the <style>
tag. This lets you create more complex logic in the <script>
tag, then easily pass the result into the style.
In addition, we've replaced the discrete
attribute with #isolated
. Like before, apply it to a <style>
tag to isolate it on its own. #isolated
can also take a name like #isolated="outlookweb"
. We group any other isolated styles with the same name together.
The <template>
tag replaces the <component>
tag of legacy components.
You'll reference variables in the same way as before, except they're referred to as props
now. For instance, ${name}
is now ${props.name}
. We now have a more flexible naming structure, so you can set dashed attributes like first-name
too: ${props["first-name"]}
.
Directives are the special attributes that control (aka direct) an element's behavior. With our new, custom components, directives now start with #
. This makes them easier to pick out compared to regular attributes:
foreach
has changed to#each
if
is now#if
else
is now#else
elseif
is now#else-if
- also note the dash here, which helps make it more readable
These all render the same as before; only the names have changed.
set
has also changed to #set
and renders the same as before but has some new features.
You can pass an object to the #set
directive to set multiple attributes at once.
<!-- source -->
<div #set="{ id: 'my-id', class: 'my-class' }"></div>
<!-- result -->
<div id="my-id" class="my-class"></div>
You can now use #set
to set HTML content, too. When dynamically inserting values into text, the output is escaped by default to prevent accidentally injecting HTML. To skip this escaping step, use the #set:html
directive.
<div #set:html="rawHTMLVariable"></div>
Use it with <fragment>
to avoid adding an extra element to your output.
<fragment #set:html="rawHTMLVariable" />
#is
is a new directive that can change the element used.
<fragment #set:is="styles ? 'span' : 'fragment' " #set:style="styles">
If there are styles. this will output a span. If not, then this will be plain
text.
</fragment>
#is:raw
will skip any processing of the content inside. This is helpful when you're inserting text or attributes with conflicting syntax and you don’t want Parcel to process it.
<div #is:raw>
<a #if="I am not evaluated">${I am also not evaluated}</a>
</div>
Previously, we were unable to render variables inside conditional comments. This caused some frustration and required some long-winded workarounds. With our new components, this is no longer an issue. It just works!