Style custom components
Design Studio New UpdatedLearn how to define and validate properties for your components.
This article applies to Design Studio
We are currently piloting Design Studio, our new email design system, and making some information available to customers. If you don’t have access to Design Studio, please continue to use our other email editors.
Define properties
To declare properties, export a variable like props
so the property can be set in the visual editor. Then set this equal to the Component.defineProps
object of your <script>
.
Each property must have a name that starts with an alphanumeric character and a validation schema.
<script>
export const config = {
"label": "My paragraph",
presets: [
{
label: "My paragraph",
content: `<my-paragraph></my-paragraph>`
}
]
};
export const slots = Component.defineSlots({
default: {
schema: Component.slots.text(),
marks: ['bold', 'italic', 'underline', 'strikethrough', 'link']
},
});
export const props = Component.defineProps({
color: {
section: 'Text',
label: 'Color',
schema: Component.props.string().optional(),
type: 'color',
},
'font-size': {
section: 'Text',
label: 'Size',
schema: Component.props.number().default(16),
type: 'number',
min: 14,
max: 30,
},
'text-align': {
section: 'Text',
label: 'Align',
schema: Component.props.enum(['left', 'center', 'right']).optional(),
type: 'toggle',
options: [
{ label: 'Left', value: 'left', icon: 'format_align_left' },
{ label: 'Center', value: 'center', icon: 'format_align_center' },
{ label: 'Right', value: 'right', icon: 'format_align_right' },
],
}
});
const styleObject = {
color: props.color,
'font-size': props['font-size'] + 'px',
'text-align': props['text-align'],
'line-height': '150%',
};
</script>
<template>
<p #set:style="styleObject">
<slot>This is a paragraph</slot>
</p>
</template>
For ease of use in the visual editor, we also recommend defining a type
to give teammates the most useful mechanism for modifying styles (like a slider vs toggle).
In this example, notice that text-align
has an array called options
— these are variants your teammates can choose from in the properties menu. You can set options on properties of type toggle
and select
.
You can optionally define an object to pass in properties which can help reduce code bloat and increase readibility of your code. You can also create your own stylesheets and pass in JavaScript variables.
For simple properties, where you don’t need to validate the values or let people modify properties in the visual editor, you can pass in an array of property names. Note the square brackets instead of curly braces.
<script>
export const props = Component.defineProps(['prop-1', 'prop-2', 'prop-3'])
</script>
Validate properties
Usually, you’ll want to specify what data type a property accepts. For example, if you create a property called name
, it should probably accept a string. If you create a property called age
, it should probably take a number.
Instead of simply declaring, “we take a prop called name
and age
," you can explicitly define validation and default values by passing in an object.
We can use the Component.props
variable to define how each property functions:
export const props = Component.defineProps({
'prop-1': Component.props.string().default("default value"),
'prop-2': Component.props.boolean(),
'prop-3': Component.props.string().array().optional(),
'prop-4': Component.props.enum(['left', 'center', 'right']).optional()
})
These are the validation schema we fully support:
string
boolean
number
enum
Each key is the name of a property like prop-1
. Each value is either validation schema or an object that includes the validation schema and other key/values that define it, like type
and label
.
If you use the property types select
or toggle
, you can pre-define variants that teammates can choose from in the visual editor.
Check out the zod validation library for more info on what powers our property validation.
(Optional) Define an object to pass in properties
After you set your properties, you may want to create an object to pass in properties. This isn’t necessary but helpful for readability of your code, especially if you have complex logic inside the styles, and to reduce code in the event that a variable renders undefined.
In this example, we’ve named our object styleObject
, but you can name this anything you like.
const styleObject = {
color: props.color,
'font-size': props['font-size'] + 'px',
'text-align': props['text-align'],
'line-height': '150%',
};
The names on the left are the CSS attributes you want to include, and on the right are the values. If a property is not set, the value is undefined
and won’t be included in the output code. You can also hard-code styles which aren’t defined in the props
script, like line-height
in the example above.
Pass the object into the component template
After you’ve defined your properties and an object to pass in the styles, set the object in the component template.
<template>
<p #set:style="styleObject">
<slot>This is a paragraph</slot>
</p>
</template>
When you add a style attribute to content in the <template>
tag, we automatically pass this JavaScript object to output your styles.
Keys for properties
When defining a property, you can set a number of keys that define how we validate the property and how people interact with it in the visual editor.
schema
- defines the validation typetype
- defines the type of property in the visual editor (a toggle, slider, etc.)label
- name of the proprety in the properties menuhelpText
- adds a hoverstate to the label in the properties menusection
- used to partition properties in the properties menu
Types of properties
Set a type
to define how team members modify the property in the visual editor’s properties menu. If your component has no type, we default it to type text.
background
A string input used to define a CSS color, image, or gradient for the background of a component. The output is a single CSS background
string. If opacity is less than 100%, then the value of rgba
is used instead, like rgba(255, 0, 0, 0.5)
.
background: {
label: 'Background',
section: 'Styles',
schema: Component.props.string().optional(),
type: 'background',
}
Note, there are some CSS properties we do not support for backgrounds:
background-attachment
background-clip
repeating-linear-gradient
andrepeating-radial-gradient
, though you can achieve similar results with Repeat (background-repeat
) in layout properties
box
A string input used to define pixel values on four sides of a box. The output is between one and four values in pixels as a single string. For instance, it could be any of these: 10px
or 10px 20px
or 10px 20px 5px
or 10px 20px 5px 15px
. Commonly used for padding
, margin
, and border-width
.
The collapsedInput
property defines the starting point of the box:
global
gives one input that applies to all sides of the box.axis
gives two inputs - one for top/bottom value and one for left/right values.
In the visual editor, you’ll see the option to select global styles for spacing too.
padding: {
label: 'Padding',
section: 'Layout',
schema: Component.props.string().optional().default('10px'),
type: 'box',
collapsedInput: 'axis',
}
code
A string input that’s the same as a text
input; however, the styling of type code
is monospace.
color
A string input used to define a CSS color. The output is a six digit hex code. If opacity is less than 100%, then the a value of rgba
is used instead, like rgba(255, 0, 0, 0.5)
.
In the visual editor, you’ll see the option to select global styles for colors too.
color: {
label: 'Text color',
section: 'Text styles',
schema: Component.props.string().optional(),
type: 'color',
}
font-family
A string input to define a font-family.
In the visual editor, you’ll see the option to select global styles for fonts too.
'font-family': {
label: 'Font family',
section: 'Styles',
schema: Component.props.string().optional(),
type: 'font-family',
}'
hidden
A property with type hidden
will not appear in the properties menu of the visual editor. However, you can still set the property from the code editor. This can be helpful if you want to limit access to advanced features to more technical users, if you want to keep new features hidden while you’re testing them, or if you want to support deprecated features behind-the-scenes.
media
A string input for a media asset such as an image or video. This is commonly used for the src
input on an image component.
accept
is an optional property that lets you define what file types, like image/png
or image/jpeg
, are allowed. You can also specify image/*
or video/*
to allow any image or video format respectively.
Use placeholder
to inform team members what to enter into the component in the visual editor. The placeholder text is never included in the output email code.
src: {
label: 'Source',
schema: Component.props.string(),
type: 'media',
accept: 'image/*',
placeholder: 'Paste the image url here...',
}
number
A number input. On the properties menu, you’ll see +/- buttons to increase or decrease the value.
Consider whether you want to set these optional properties too:
min
for a minimum number valuemax
for a maximum number valuestep
to define the increment by which a team member can increase/decrease the value with the +/- buttons
'font-size': {
label: 'Size',
section: 'Text styles',
schema: Component.props.number().optional(),
type: 'number',
min: '1',
max: '100',
}
radius
A string input for setting rounded corners. Similar to the box
input, the output is between one and four values in pixels as a single string. For instance, it could be any of these: 10px
or 10px 20px
or 10px 20px 5px
or 10px 20px 5px 15px
.
In the properties menu, you’ll see the option to select global styles for radii too.
'border-radius': {
label: 'Radius',
section: 'Styles',
schema: Component.props.number().optional(),
type: 'radius',
}
select
An enum or string input that displays a dropdown list of options to select from.
Include options
to define the dropdown list. Each item can include:
label
for the display name in the dropdown listvalue
for what the dropdown item should meanoptgroup
to organize specific options under the same header
'font-weight': {
section: 'Text Styles',
label: 'Weight',
schema: Component.props.string().optional(),
type: 'select',
options: [
{ label: 'Light', value: '300' },
{ label: 'Normal', value: '400' },
{ label: 'Bold', value: '700' },
],
}
shadow
A string input for a CSS box-shadow. The input allows you to set multiple shadows, but will output a single string.
'box-shadow': {
section: 'Styles',
label: 'Shadow',
schema: Component.props.string().optional(),
type: 'shadow',
}
size
A string input to define a size. Commonly used as width
and occasionally height
.
units
is an array of the allowed units you can select and include in the output:
auto
will output the keyword ‘auto’. The browser calculates the width.px
will add ‘px’ to the number in the output value.percentage
will add ‘%’ to the number in the output value.
'width': {
section: 'Layout',
label: 'Width',
schema: Component.props.string().optional(),
type: 'size',
units: ['auto', 'px'],
}
slider
A number input, but controlled with a slider.
You can optionally include min
as a minimum number value or max
as an optional maximum number value.
step
An optional step value to define the increment someone can increase/decrease the value by with the +/- buttons in the visual editor.
'letter-spacing': {
label: 'Letter spacing',
section: 'Text styles',
schema: Component.props.number().optional(),
type: 'slider',
min: 0,
max: 1,
step: 0.1,
}
switch
A boolean input. This is used for a simple true/false statement. You could use this in a footer component to choose if you include social icons, for example.
'social': {
label: 'Include social',
section: 'Options',
schema: Component.props.boolean().optional(),
type: 'switch',
}
text
A string input for setting text. If your component has no type set, we default it to type text
.
Use placeholder
to inform team members what to enter into the component in the visual editor. The placeholder text is never included in the output email code.
'alt': {
label: 'Alt text',
schema: Component.props.string().optional(),
type: 'text',
placeholder: 'Add image description...'
}
textarea
A string input for setting text. This is similar to ’text’ but has more options.
placeholder
- use this to inform team members what to enter into the component in the visual editor. The placeholder text is never included in the output email code.rows
- use this to require a number to define how many rows of text tall the component is, the default is 2multiLine
- use this to require a boolean to define if you want to preserve line breaks, the default is falseresize
- use this to require an enum to define if the component can be resized; options are none (default, can’t resize) or vertical (can resize)
const style = {
label: 'CSS Styles',
schema: Component.props.string().optional(),
type: 'textarea',
multiLine: true,
rows: 3,
resize: 'vertical'
}
toggle
An enum input that will display a toggle in the properties menu.
Use options
to set an array of toggles:
label
defines the display name for the toggle.value
defines how the code handles the toggle.icon
if included, replaces thelabel
. You can use Google material symbols and icons.
'text-align': {
section: 'Text Style',
label: 'Text Align',
schema: Component.props.enum(['left', 'center', 'right']).optional(),
type: 'toggle',
options: [
{ label: 'Left', value: 'left', icon: 'format_align_left' },
{ label: 'Center', value: 'center', icon: 'format_align_center' },
{ label: 'Right', value: 'right', icon: 'format_align_right' },
],
}
url
A string input for setting a url.
Use placeholder
to inform team members what to enter into the component in the visual editor. The placeholder text is never included in the output email code.
'href': {
label: 'Link',
schema: Component.props.string().optional(),
type: 'url',
placeholder: 'Add url of link destination...'
}