# Style custom components

[BetaThis feature is new and we're actively working on it.](/beta-experimental-features/#beta-features)

Learn how to define and validate properties for your components in Design Studio.

To create responsive custom components or enable dark mode, check out [Responsive Styles](/journeys/responsive-styles/#add-responsive-styles-to-custom-components) and [Dark Mode](/journeys/dark-mode/#add-dark-mode-styles-to-custom-components).

 Use our Design Studio GPT to create a custom component!

[Learn how to best utilize our GPT](/journeys/create-custom-component/#design-studio-gpt) to create code for reusable, custom content.

## Define properties[](#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](#validate-properties).

```html
<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`](#types-of-properties) 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`](#types-of-properties).

You can optionally [define an object to pass in properties](/journeys/component-properties/#optional-define-an-object-to-pass-in-properties) which can help reduce code bloat and increase readability of your code. You can also [create your own stylesheets and pass in JavaScript variables](/journeys/component-styling/).

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.

```html
<script>
    export const props = Component.defineProps(['prop-1', 'prop-2', 'prop-3'])
</script>
```

## Validate properties[](#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:

```javascript
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](#prop-keys), like `type` and `label`.

If you use the property types [`select`](#select) or [`toggle`](#toggle), you can pre-define variants that teammates can choose from in the visual editor.

Check out the [zod validation library](https://github.com/colinhacks/zod) for more info on what powers our property validation.

## (Optional) Define an object to pass in properties[](#optional-define-an-object-to-pass-in-properties)

After you [set your properties](#define-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.

```javascript
  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.

### Add global styles[](#add-global-styles)

In your style object, you can reference your global styles to keep your component on brand too.

You’ll reference them using `globalStyles.<global-style-type>.<global-style-variablename-variableid>`. This might look like `globalStyles.colors.pink_khyqtq8v5rcs`.

 Use autocomplete to reference the global style

Note the variable id needed at the end of the global style reference. You can find this by using autocomplete in the component editor.

```html
<script>
  export const config = {
    label: "global style test",
    presets: [
      {
        label: "global style test",
        content: `<test2></test2>`
      }
    ]
  }
  const styleObject = {
    background: globalStyles.colors.pink_khyqtq8v5rcs,
  };
</script>
```

### Pass the object into the component template[](#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.

```html
<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[](#prop-keys)

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 type
*   `type` - defines the type of property in the visual editor (a toggle, slider, etc.)
*   `label` - name of the property in the Properties menu
*   `helpText` - adds a hoverstate to the label in the Properties menu
*   `section` - used to partition properties in the Properties menu

### Types of properties[](#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](#text).

#### background[](#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)`.

```javascript
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` and `repeating-radial-gradient`, though you can achieve similar results with Repeat (`background-repeat`) in layout properties

#### box[](#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.

```javascript
padding: {
  label: 'Padding',
  section: 'Layout',
  schema: Component.props.string().optional().default('10px'),
  type: 'box',
  collapsedInput: 'axis',
}
```

#### code[](#code)

A string input that’s the same as a [`text`](#text) input; however, the styling of type `code` is monospace.

#### color[](#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.

```javascript
color: {
  label: 'Text color',
  section: 'Text styles',
  schema: Component.props.string().optional(),
  type: 'color',
}
```

#### font-family[](#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.

```javascript
'font-family': {
  label: 'Font family',
  section: 'Styles',
  schema: Component.props.string().optional(),
  type: 'font-family',
}'
```

#### hidden[](#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[](#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.

```javascript
src: {
  label: 'Source',
  schema: Component.props.string(),
  type: 'media',
  accept: 'image/*',
  placeholder: 'Paste the image url here...',
}
```

#### number[](#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 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

```javascript
'font-size': {
  label: 'Size',
  section: 'Text styles',
  schema: Component.props.number().optional(),
  type: 'number',
  min: '1',
  max: '100',
}
```

#### radius[](#radius)

A string input for setting rounded corners. Similar to the [`box`](#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.

```javascript
'border-radius': {
  label: 'Radius',
  section: 'Styles',
  schema: Component.props.number().optional(),
  type: 'radius',
}
```

#### select[](#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
*   `optgroup` to organize specific options under the same header

```javascript
'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' },
  ],
}
```

You can also reference global styles in the values of your options.

You’ll reference them using `globalStyles.<global-style-type>.<global-style-variablename-variableid>`. This might look like `globalStyles.colors.pink_khyqtq8v5rcs`.

 Use autocomplete to reference the global style

Note the variable id needed at the end of the global style reference. You can find this by using autocomplete in the component editor.

#### shadow[](#shadow)

A string input for a CSS box-shadow. The input allows you to set multiple shadows, but will output a single string.

```javascript
'box-shadow': {
  section: 'Styles',
  label: 'Shadow',
  schema: Component.props.string().optional(),
  type: 'shadow',
}
```

#### size[](#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.

```javascript
'width': {
  section: 'Layout',
  label: 'Width',
  schema: Component.props.string().optional(),
  type: 'size',
  units: ['auto', 'px'],
}
```

#### slider[](#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[](#step)

An optional step value to define the increment someone can increase/decrease the value by with the +/- buttons in the visual editor.

```javascript
'letter-spacing': {
  label: 'Letter spacing',
  section: 'Text styles',
  schema: Component.props.number().optional(),
  type: 'slider',
  min: 0,
  max: 1,
  step: 0.1,
}
```

#### switch[](#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.

```javascript
'social': {
  label: 'Include social',
  section: 'Options',
  schema: Component.props.boolean().optional(),
  type: 'switch',
}
```

#### text[](#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.

```javascript
'alt': {
  label: 'Alt text',
  schema: Component.props.string().optional(),
  type: 'text',
  placeholder: 'Add image description...'
}
```

#### textarea[](#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 2
*   `multiLine` - use this to require a boolean to define if you want to preserve line breaks, the default is false
*   `resize` - 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)

```javascript
const style = {
  label: 'CSS Styles',
  schema: Component.props.string().optional(),
  type: 'textarea',
  multiLine: true,
  rows: 3,
  resize: 'vertical'
}
```

#### toggle[](#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 the `label`. You can use [Google material symbols and icons](https://fonts.google.com/icons).

```javascript
'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' },
  ],
}
```

You can also reference global styles in the values of your options.

You’ll reference them using `globalStyles.<global-style-type>.<global-style-variablename-variableid>`. This might look like `globalStyles.colors.pink_khyqtq8v5rcs`.

 Use autocomplete to reference the global style

Note the variable id needed at the end of the global style reference. You can find this by using autocomplete in the component editor.

#### url[](#url)

Use `url` for links, emails, and phone numbers.

```javascript
'href': {
  label: 'Link',
  schema: Component.props.string().optional(),
  type: 'url',
  placeholder: 'Add destination...'
}
```

In the visual editor, the component’s properties menu shows the *Link* field. You’ll decide whether to set a URL, Email, or Phone number from the dropdown. [Learn more about each option](/journeys/standard-components/#manage-links).

Use `placeholder` to inform team members what to input if they choose URL or Email from the dropdown. The placeholder text is never included in the output email code.