Styling

Learn how to style your components.

You can style custom components in two ways:

  1. Define properties in the <script> element and pass them in the <style> and/or <template> tags.
  2. Define CSS in the <style> element and set them in the <template> tag.

You can use JavaScript in the <script> tag, but not the <style> tag.

Style using JavaScript

You can define properties in the <script> tag that you then pass to a <style> or <template> tag to style your content.

Define properties

Properties allow you to thoroughly customize a component. Here we’ll add some text styling.

<script>
  export const config = {
    "label": "My paragraph",
  };
  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">
    This is a paragraph
  </p>
</template>

Begin by adding export const props = Component.defineProps({}).

Above, we added three properties: color, font-size and text-align. Each has a number of additional settings. These are all optional, but we recommend you set some of them to help improve the user experience in the visual editor.

  • section - This groups properties together in the main properties menu.
  • label - This sets a more user friendly name for the property.
  • schema - This defines the type of property, validation (e.g number, string, boolean, enum, etc), default value, and whether it's optional. For a deeper look at validation, check out the zod validation library which our validation is built on.
  • type - This defines the UI control that will show in the properties menu. You must set this to manage it in the visual editor. See a full list below.
  • min, max - used to set minimum and maximum values of a number
  • options - this is a way to set user-friendly labels for enum properties. When using this, the value must match the enum value. Also, the icons set here are from Google icons.

Options for type

You must set a type to allow team members to modify the property in the component's properties menu. If your component has no type set, we default it to type text.

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.
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).

color: {
  label: 'Text color',
  section: 'Text styles',
  schema: Component.props.string().optional(),
  type: 'color',
}
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 visual editor, 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 value
  • max for a maximum number value
  • step 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.

'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 list
  • value for what the dropdown item should mean
'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...'
}
toggle

An enum input that will display a toggle in the properties menu of the visual editor.

Use the property 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 the label. 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...'
}

Create a style object

After you define your properties, you'll create another object to help build the component and pass styles to your message. Here we'll call it the styleObject, but you can name this anything you like. Then, when you add a style attribute to your template, we will automatically pass this Javascript object to output your styles.

  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, it will have a value of undefined and will not be included in the output code. You can also include hard-coded styles which aren’t included in the props, like line-height in the example above.

Pass properties into the <template> tag

In the example above, we set the style of the paragraph tag: <p #set:style="styleObject">.

Pass properties into the <style> tag

<style> tags support variables defined in your <script> tag. Pass them as CSS values with the set() function.

.button {
	background: set(background); /* quotes are optional when using alpha chararacters */
	color: set('color');
}

If the value you want to access is inside of an object or array, you can use dot notation.

<script>
	const theme = { background: 'blue' }
<script>
<style>
	.button {
		background:	set('theme.background');
	}
</style>

In your template, you'd then pass the CSS variable:

<a style="button"></div>

Style using CSS

Use <style> tags to define CSS for your component then reference them in your <template> tag.

Conditional CSS

You can use all the conditional directives (#if, #else-if, #else) to render CSS.

<style #if="variant === 'primary'">
	.primary-button {
		background: blue;
		color: white;
	}
</style>
<style #else>
	.default-button {
		background: white;
		color: blue;
	}
</style>

Multiple <style> tags

By default, we merge all <style> tags together to avoid bloat. You can change this with the #isolated> attribute.

For example, these two <style> tags will be merged:

Source:

<style>
.a {
	background: blue;
}
</style>
<style>
.b {
	background: red;
}
</style>

Output:

<style>
.a {
	background: blue;
}
.b {
	background: red;
}
</style>

Standalone styles

Sometimes you need to guarantee that a particular <style> tag lives on its own and is not merged with other <style> tags. You can do so by adding the #isolated attribute.

You might want this when sending emails to Gmail. If the <style> element contains any invalid CSS, Gmail will strip the entire block. But if you wrap this CSS in a #isolated block, Gmail will not strip the rest of the CSS.

<style #isolated>
.a {
	background: blue;
}
</style>
<style>
.b {
	background: red;
}
</style>

You can also group styles together by assigning a value to the #isolated attribute. This can prevent people from receiving a half-styled email; when an email client removes a <style> block, it will also remove all styles with the same value. This practice can help reduce code bloat, too.

<style #isolated="outook-web">
[class~="a"] {
	background: blue;
}
</style>
<style #isolated="outook-web">
[class~="outlook-hide"] {
	display: none;
}
</style>
<style>
.b {
	background: red;
}
</style>
<style #isolated="interactive">
input:checked ~ .c {
	background: green;
}
</style>