Basics
Features
Best Practices
Addon Development
Related Docs
Dark Mode

#Components

Components are reusable, self-contained, configurable chunks of code you can use throughout your site. They encapsulate related markup, style, and script which makes them very easy to use, understand, and maintain.

The Component system works by using custom HTML tags in your markup that are then replaced with the Component markup during the Build. Any style for the Component is then included on the Page that uses it as well as any Script.

Components can be extremely simple, such as a button with some style associated with it that can be used across the Site, or more complex such as a reactive Vue Component that lists sorted Blog Posts and has integrated search functionality.

You are encouraged to use Components often. Each website will have many custom Components created for it. Any time you need to use the same code multiple times, even within the same individual Page, you should make a Component. The Component system is designed to be easy to quickly make any Components you may need and use them without needing the help of a developer.

#Component Basics

Component names determine their tag using kebab-case. For example, a Component named "Site Button" will be used anywhere you use <site-button></site-button> in a Page, Layout, or other Component's markup.

You can use multiple of the same Component on a single Page and each will be given a unique ID. They can each have their own style and attributes that may differ from each other, which you'll learn about below.

#Markup and Style

The simplest Components combine some basic markup you want to reuse with its associated style. Components must always have a single root tag. Any extra tags will be ignored. This allows the parent markup that uses it to be able to add attributes to it and modify its style such as height, width, and margin.

Here's an example of a simple button that says "Click Me".

.site-button Click Me
.site-button
  display inline-block
  padding 8px 16px
  border 1px solid rgba(0, 0, 0, 0.1)
  border-radius 4px
  cursor pointer
  transition background 0.2s
  &:hover
    background rgba(0, 0, 0, 0.1)

And here's what it looks like if we use it on a Page:

.home-page
  h1 Site Button Test
  p Hey! Check out my cool new site buttons!
  site-button
  site-button
  site-button
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Home</title>
        <link rel="stylesheet" href="style.css">
        <meta property="og:title" content="Home">
        <meta property="og:type" content="website">
        <meta property="og:url" content="https://example.com/">
        <meta property="og:description" content="Home">
        <meta property="og:locale" content="en_US">
        <meta property="og:site_name" content="My Site">
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <meta name="description" content="Home">
    </head>
    <body>
        <div class="home-page">
            <h1>Site Button Test</h1>
            <p>Hey! Check out my cool new site buttons!</p>
            <div class="site-button" data-cid-1>Click Me</div>
            <div class="site-button" data-cid-2>Click Me</div>
            <div class="site-button" data-cid-3>Click Me</div>
        </div>
    </body>
</html>
.site-button {
    display: inline-block;
    padding: 8px 16px;
    cursor: pointer;
    border: 1px solid rgba(0,0,0,0.1);
    border-radius: 4px;
    transition: background .2s
}

.site-button:hover {
    background: rgba(0,0,0,0.1)
}

Notice how each site button was replaced with its defined markup and the style was added to the Page a single time to cover all of the buttons. It's recommended to always give your Component's root tag a class the same as its name (in this case .site-button). If you don't do this, it will automatically be added so its parent has a way to easily style the Component to control its size and position.

#Basic Configuration

Components are supposed to be configurable so you don't have to write a new version of a Component for every small change you want to make. For this, we can use the Attributes tab.

Let's add an attribute called "Label". In the Attributes tab, add a new attribute and call it "Label". Give it a default value of "Click Me" since our website has been using this button and expects that value if Label isn't specified.

Now let's give it another attribute called "Big" and make it a Boolean type. We'll leave the default value as false.

Now let's update the markup and style to use the Label and Big attributes.

.site-button(class=attr.big && 'big') #{ attr.label }
.site-button
  display inline-block
  padding 8px 16px
  cursor pointer
  border 1px solid rgba(0, 0, 0, 0.1)
  border-radius 4px
  transition background 0.2s
  &.big
    padding 16px 24px
  &:hover
    background rgba(0, 0, 0, 0.1)

Now we can update the Page to use the new Attributes.

.home-page
  h1 Site Button Test
  p Hey! Check out my cool new site buttons!
  site-button(label="Self Destruct")
  site-button(label="Release the Kraken" big)
  site-button

You can see that attributes use kebab-case in the parent template within the tag and use camelCase within the Component's code.

Here's the output we get:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Home</title>
        <link rel="stylesheet" href="style.css">
        <meta property="og:title" content="Home">
        <meta property="og:type" content="website">
        <meta property="og:url" content="https://example.com/">
        <meta property="og:description" content="Home">
        <meta property="og:locale" content="en_US">
        <meta property="og:site_name" content="My Site">
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <meta name="description" content="Home">
    </head>
    <body>
        <div class="home-page">
            <h1>Site Button Test</h1>
            <p>Hey! Check out my cool new site buttons!</p>
            <div class="site-button" data-cid-1>Self Destruct</div>
            <div class="site-button big" data-cid-2>Release the Kraken</div>
            <div class="site-button" data-cid-3>Click Me</div>
        </div>
    </body>
</html>
.site-button {
    display: inline-block;
    padding: 8px 16px;
    cursor: pointer;
    border: 1px solid rgba(0,0,0,0.1);
    border-radius: 4px;
    transition: background .2s
}

.site-button.big {
    padding: 16px 24px
}

.site-button:hover {
    background: rgba(0,0,0,0.1)
}

#Scoped Styling

You'll notice as well that each button was given a data-cid-n attribute. This is used for scoped styling. You can see that the resulting markup on the Page is generated per button, and so far, the style has applied to all of the different variations of the button. But what if we want there to be custom style per button?

Let's add a "Main Color" attribute with a type of color and use a default value of "#ffffff". Then we can update the Component's style:

.site-button
  display inline-block
  padding 8px 16px
  background {{ attr.mainColor }}
  cursor pointer
  border 1px solid rgba(0, 0, 0, 0.1)
  border-radius 4px
  transition background 0.2s
  &.big
    padding 16px 24px
  &:hover
    background darken({{ attr.mainColor }}, 10%)

Now let's update the colors of the buttons on the Page:

.home-page
  h1 Site Button Test
  p Hey! Check out my cool new site buttons!
  site-button(label="Self Destruct" main-color="green")
  site-button(label="Release the Kraken" big main-color="red")
  site-button

Here's what the HTML and CSS will look like:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Home</title>
        <link rel="stylesheet" href="style.css">
        <meta property="og:title" content="Home">
        <meta property="og:type" content="website">
        <meta property="og:url" content="https://example.com/">
        <meta property="og:description" content="Home">
        <meta property="og:locale" content="en_US">
        <meta property="og:site_name" content="My Site">
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <meta name="description" content="Home">
    </head>
    <body>
        <div class="home-page">
            <h1>Site Button Test</h1>
            <p>Hey! Check out my cool new site buttons!</p>
            <div class="site-button" data-cid-1>Self Destruct</div>
            <div class="site-button big" data-cid-2>Release the Kraken</div>
            <div class="site-button" data-cid-3>Click Me</div>
        </div>
    </body>
</html>
.site-button[data-cid-1] {
    display: inline-block;
    padding: 8px 16px;
    background: #008000;
    cursor: pointer;
    border: 1px solid rgba(0,0,0,0.1);
    border-radius: 4px;
    transition: background .2s
}

.site-button[data-cid-1].big {
    padding: 16px 24px
}

.site-button[data-cid-1]:hover {
    background: #007300
}

.site-button[data-cid-2] {
    display: inline-block;
    padding: 8px 16px;
    background: #f00;
    cursor: pointer;
    border: 1px solid rgba(0,0,0,0.1);
    border-radius: 4px;
    transition: background .2s
}

.site-button[data-cid-2].big {
    padding: 16px 24px
}

.site-button[data-cid-2]:hover {
    background: #e60000
}

.site-button[data-cid-3] {
    display: inline-block;
    padding: 8px 16px;
    background: #fff;
    cursor: pointer;
    border: 1px solid rgba(0,0,0,0.1);
    border-radius: 4px;
    transition: background .2s
}

.site-button[data-cid-3].big {
    padding: 16px 24px
}

.site-button[data-cid-3]:hover {
    background: #e6e6e6
}

You can see that MercuryCMS generates unique CSS for each button and scopes the style using the [data-cid-n] selector.

#Script

Some Components will need script associated with them. For example, you may want to make a simple Tab List Component. The user can click the tabs to view the content.

.tab-list
  .tabs
    .tab(onclick="goToTab(event, 0)") First Tab
    .tab(onclick="goToTab(event, 1)") Second Tab
  .tab-content
    div This is the first tab's content.
  .tab-content
    div This is the second tab's content.
.tab-list
  .tabs
    display flex
    .tab
      padding 8px 16px
      border 1px solid rgba(0, 0, 0, 0.1)
      border-left-width 0
      border-bottom-color transparent
      border-top-left-radius 4px
      border-top-right-radius 4px
      cursor pointer
      position relative
      top 1px
      z-index 1
      &:first-child
        border-left-width 1px
      &.selected
        border-bottom-color white        
  .tab-content
    border 1px solid rgba(0, 0, 0, 0.1)
    padding 16px
    border-radius 4px
    border-top-left-radius 0
// show the clicked tab
window.goToTab = function (e, tabIndex) {
  let tabListElement = e.target.closest('.tab-list')

  // add selected class to selected tab only
  tabListElement.querySelectorAll('.tab').forEach((tabElement, index) => {
    if (index == tabIndex && !tabElement.classList.contains('selected')) tabElement.classList.add('selected')
    if (index != tabIndex) tabElement.classList.remove('selected')
  })

  // show the tab-content for the clicked tab
  tabListElement.querySelectorAll('.tab-content').forEach((tabContentElement, index) => {
    if (index == tabIndex) tabContentElement.style.display = 'block'
    else tabContentElement.style.display = 'none'
  })

}

// hide all but the first tab within each tab-list when the page loads
document.querySelectorAll('.tab-list').forEach(tabListElement => {

  // add selected class to the first tab
  tabListElement.querySelector('.tab').classList.add('selected')

  // hide all tab-content divs except the first
  tabListElement.querySelectorAll('.tab-content').forEach((tabContentElement, index) => {
    if (index) tabContentElement.style.display = 'none'
  })

})

This script is added to any Page that uses the Component. Even if you add multiple of the same Component to the Page, the script will only be added once to handle all of them, so it's up to you to ensure the script works with multiple instances of the Component on the Page.

Because of this, you cannot use the attr object within the script since that only applies to individual Component instances.

#Slots

This <tab-list> Component works well, but the content is hardcoded into it. If you want the Page to be able to determine what's in each .tab-content area, you'll need to use Slots.

Slots allow the parent to put content into the child Component's markup. The Component decides whether it supports Slots, and if it does, where that content goes.

Let's work our way up to adding Slots to the <tab-list> and instead make a new example Component for now.

.site-notification
  .heading Notification:
  slot
  .button(onclick="dismissSiteNotification(event)") Dismiss
.site-notification
  padding 16px
  border 1px solid rgba(0, 0, 0, 0.1)
  border-radius 4px
  .heading
    color rgba(0, 0, 0, 0.7)
  .button
    border 1px solid rgba(0, 0, 0, 0.1)
    border-radius 4px
    padding 8px 16px
    display inline-block
    cursor pointer
    transition background 0.2s
    &:hover
      background rgba(0, 0, 0, 0.1)
window.dismissSiteNotification = function (e) {
  e.target.closest('.site-notification').remove()
}

Now let's use the <site-notification> Component on the Home Page:

.home-page
  site-notification
    p A very important message.
  site-notification
    p We've been trying to reach you about your car's extended warranty.

Here's the output we'll get:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Home</title>
        <link rel="stylesheet" href="style.css">
        <meta property="og:title" content="Home">
        <meta property="og:type" content="website">
        <meta property="og:url" content="https://example.com/">
        <meta property="og:description" content="Home">
        <meta property="og:locale" content="en_US">
        <meta property="og:site_name" content="My Site">
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <meta name="description" content="Home">
    </head>
    <body>
        <div class="home-page">
            <div class="site-notification" data-cid-1>
                <div class="heading">Notification:</div>
                <p>A very important message.</p>
                <div class="button" onclick="dismissSiteNotification(event)">Dismiss</div>
            </div>
            <div class="site-notification" data-cid-2>
                <div class="heading">Notification:</div>
                <p>We &apos;ve been trying to reach you about your car &apos;s extended warranty.</p>
                <div class="button" onclick="dismissSiteNotification(event)">Dismiss</div>
            </div>
        </div>
        <script type="module" src="script.js"></script>
    </body>
</html>

#Named Slots

We'll need multiple, named Slots to get different content into each tab in the <tab-list> Component. If you don't give a Slot a name, it will be named "default".

Let's upgrade the <tab-list> to have named slots. We only need to update its markup:

.tab-list
  .tabs
    .tab(onclick="goToTab(event, 0)") First Tab
    .tab(onclick="goToTab(event, 1)") Second Tab
  .tab-content
    slot(name="firstTab")
  .tab-content
    slot(name="secondTab")

Now we can template in whatever content we want on the Home Page.

.home-page
  tab-list
    template(slot="firstTab")
      div This goes in the first tab of the first list.
    template(slot="secondTab")
      div This goes in the second tab of the first list.
  tab-list
    template(slot="firstTab")
      div This goes in the first tab of the second list.
    template(slot="secondTab")
      div This goes in the second tab of the second list.

Of course, this is pretty rigid since you can't change the tab names or how many tabs there are. Let's complete this example and at the same time, introduce Array attributes.

Let's add an Attribute called "Tab Names", make it an Array type, and give it a default value of []. Then let's update its markup again:

.tab-list
  .tabs
    each tabName, index in attr.tabNames
      .tab(onclick=`goToTab(event, ${index})`) #{ tabName }
  each tabName in attr.tabNames
    .tab-content
      slot(name=tabName)

Now let's put some content on the Home Page:

.home-page
  tab-list(tab-names=['Fruit', 'Vegetables'])
    template(slot="Fruit")
      ul
        li Apples
        li Oranges
        li Pears
        li Bananas
    template(slot="Vegetables")
      ul
        li Peas
        li Carrots
        li Potatoes
        li Onions
  tab-list(tab-names=['Bedroom', 'Living Room', 'Basement', 'Office'])
    template(slot="Bedroom")
      ul
        li Bed
        li Nightstands
        li Dresser
    template(slot="Living Room")
      ul
        li TV
        li Couch
        li Coffee Table
    template(slot="Basement")
      ul
        li Pool Table
        li Arcade Cabinet
        li Dart Board
    template(slot="Office")
      ul
        li Desk
        li Computer
        li Office Chair

#Additional Slot Features

You can also mix named and unnamed Slots. If you don't name the Slot in the Component, it's given the name "default". If you add content in the parent outside of a <template> tag, or don't give the <template> tag a name attribute, that content will go into the "default" Slot.

You can also add default content to all Slots. Within the Component, you can put data within any <slot> tag and that content will show up if the parent doesn't specify any content for that Slot. This allows you to have default content used in most places but then allow the parent to override that if needed, or it can be useful to remind you to replace placeholder content.

#Attributes

We've already used Attributes in the above examples, but we haven't talked about exactly how they work.

Attributes are used for configurability of Components. It's not very useful to have to make a whole new Component just because you want a slightly different sized button or different content within its tabs.

You are encouraged to think of Attributes as an options screen that can be modified by the user of the Component. For example, rather than templating {{ theme.mainColor }} repeatedly within your Component's style, use {{ attr.mainColor }}, then let the user type {{ theme.mainColor }} into Main Color's default value in the Attributes tab.

This also means the user can now use <some-component main-color="red"></some-component> on an individual Component. This approach implicitly documents all of the configurable options the Component has. This also makes it much easier to import and export Components between Sites.

Here's a quick explanation of each Attribute type:

TypeDescription
TextThe value will be cast to a string. This is used for most attributes.
Text AreaThe value will be a string with a multi-line input field.
HTMLThe value will be a string with an HTML input field.
ColorThe value is a color and has a color picker. This is useful for providing options to the user for how a Component should look.
NumberThe value will be cast to a number.
BooleanThe value will be either true or false. If the value is "false", "off", "no", or "0", the value will be false. If it's "true", "on", "yes", or "1", the value will be true. You can also use terse tags. For example, <site-button big></site-button> would set attr.big to true without having to write big="true". If the Attribute isn't present on the tag, it will be the default value.
MediaA string value representing the _id of a Media item.
AssetA string value representing a path to an Asset.
EnumEnum values allow you to specify a list of valid values separated by commas, which can then be picked from in a dropdown. This is an easy way to document what values the Component expects. For example, you could have a "Position" Attribute with "static, relative, absolute, fixed, sticky" available which is then used in the Component's style.
ArrayThe value expects an array of items. This can have whatever structure you want. It's recommended to provide a default value with placeholder content to easily show the user what the Component expects.
ObjectThe value expects an object with keys and values. This can have whatever structure you want. It's recommended to provide a default value with placeholder content to easily show the user what the Component expects.

A very important behavior of Attributes is that they are "consumed" at Build time. This means any Attributes you specify in the Attributes tab will be removed from the Component's tag on the Page, processed, and become available in the attr object for templating into the Component's markup, style, script, and data.

Any attributes defined on a tag that are not defined in the Attributes tab are not consumed and are forwarded onto the Component's root tag defined in its markup. This allows the parent to add any classes, event handlers, etc. that it wants to. The Component's classes and the classes added by the parent are merged. All other attributes are overridden if the parent defines them.

With Pug, you can define class= multiple times and they will all merge. This is useful for dynamically adding classes due to boolean attributes

Attributes are consumed because you can pass in large and complex object references like entire Collection entries that shouldn't be baked into the output Page. They are intended to be used only at Build time.

Because they are consumed, you should avoid conflicting names. It's common to want to name an Attribute "Title", but this would conflict with the regular HTML attribute called "title" making it impossible for the parent markup to use the original functionality of this attribute. An easy way to avoid this is to always use two word names for Attributes.

You can, however, name Attributes a conflicting name and then still forward that attribute to the Component. For example, you could make a <site-button> Component with an "Href" Attribute that gets put into an <a> tag using <a href=attr.href></a>.

#Tags

There are often times you will write Components that depend on other scripts, stylesheets, or other tags to be present on the Page. For information about how this works, see Tags.

#Data

Components have access to all Collections, the Site, the Page, and their assigned Attributes. There may be times you want to process some of that data in a way that makes it easier to Template, or manipulate data passed in via the Component's attributes. For this, you can use the Data tab.

The script in the Data tab is run once per Component at Build time. We haven't talked about Collections or Media yet, but a common use case is to get a Media object or Collection Entry that has been uploaded and use it in the Component. Let's make a simple <image-info> Component for this.

First, add an Attribute called "Media ID" with no default value. Then write the markup:

if !attr.mediaId
  .image-info You must specify a Media ID.
else if !data.media
  .image-info The Media with ID #{ attr.mediaId } was not found.
else if data.media.type != 'image'
  .image-info The Media with ID #{ data.media._id } is not an image.
else
  table.image-info
    tr
      td ID
      td #{ data.media._id }
    tr
      td Name
      td #{ data.media.name }
    tr
      td Alt Text
      td #{ data.media.altText }
    tr
      td Caption
      td #{ data.media.caption }

In the Component's Data script:

data.media = mediaList.getById(attr.mediaId)

#Importing and Exporting Components

It's inevitable that you will build up an extensive catalog of configurable Components and use them across your websites. Rather than copy-pasting their markup, style, and script across Sites, you can export the Component from the Components screen and import it into another Site.

When you do this, you'll realize that you should write your Components in a way that doesn't directly interact with the Site's Theme Collection or other settings, but rather allows the user to Template those values into the Attributes screen on a per-site basis.

This makes maintaining your Components much easier and clearly documents what values the Component uses throughout its markup, style, and script.

#Build Scripts

Build scripts are an advanced feature used by developers to generate files for use on the website at Build time that the Component can use. This is most commonly done to create JSON configuration files and indexes for search Components. This is documented with an example in Vue Components.

#Help

To help others use your Component, you can write Help Pages. Help Pages are written in Markdown and support Github Flavor Features. This means you can add code blocks for your examples using triple-backtick syntax. For example:

# Site Button

This Component is used for all buttons on the site. Here's an example:

```pug
site-button(icon="check" secondary) Click Me
```

This syntax supports languages like pug, html, stylus, css, js, etc.

Help Pages work much like their own miniature documentation site. This means each Help Page you make has its own path. The Help Page with a / path will be shown by default in the Component's Help tab. You can make additional Help Pages with different paths and link between them.

To make a link to a Help Page with the path /another-page, you would write [Another Page](component:/another-page). Any link that starts with component: will link to this Component's Help Pages.

If you want to link to another component, use its HTML tag name in the link, like this: [Site Banner](/component:site-banner) or [Site Banner](/component:site-banner/some-page).

#Best Practices

For best practices regarding creating and using Components, see Components Best Practices.