Skip to content

Stamp

<quiet-stamp> experimental since 1.0

Renders templates with simple expressions, logic, and custom data.

This component lets you "stamp out" repetitive HTML from a single template. Instead of copying and pasting similar markup, define the pattern once in a <template> element and let the stamp render it with different values you provide.

Templates support curly brace expressions like {name}, conditional attributes such as if and unless, and loops with for. Data can be passed through data- attributes for simple values or embedded JSON for complex structures.

<div id="stamp__overview">
  <!-- Template -->
  <template id="user-card">
    <quiet-card class="user" orientation="horizontal">
      <quiet-avatar slot="header" label="{label}" image="{image}"></quiet-avatar>
      <div class="info">
        <h3>{name}</h3>
        <p>{tagline}</p>
      </div>
      <quiet-button slot="footer" if="{canEdit}" icon-label="Edit" pill>
        <quiet-icon name="edit"></quiet-icon>
      </quiet-button>
    </quiet-card>
  </template>

  <!-- First stamp -->
  <quiet-stamp
    template="user-card"
    data-name="Meowy McGee"
    data-tagline="Freedom's just another word for nothing left to lose."
    data-label="Profile pic"
    data-can-edit="true"
    data-image="https://images.unsplash.com/photo-1672487209629-4d52e0c043d0?q=80&w=256&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
  ></quiet-stamp>

  <!-- Second stamp -->
  <quiet-stamp
    template="user-card"
    data-name="Lady Pawington"
    data-tagline="Professional sunbeam chaser and nap enthusiast."
    data-label="Profile pic"
    data-can-edit="true"
    data-image="https://images.unsplash.com/photo-1516310789627-2ff305829fbb?q=80&w=256&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
  ></quiet-stamp>

  <!-- Third stamp -->
  <quiet-stamp
    template="user-card"
    data-name="Sir Whiskertons III"
    data-tagline="Living all nine lives to the fullest, one treat at a time."
    data-label="Profile pic"
    data-can-edit="false"
    data-image="https://images.unsplash.com/photo-1569591159212-b02ea8a9f239?q=80&w=256&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
  ></quiet-stamp>
</div>

<style>
  #stamp__overview {
    display: flex;
    flex-direction: column;
    gap: .5rem;

    .user {
      --spacing: 1rem;

      .info {
        flex: 1 1 auto;
        display: flex;
        gap: .25rem;
        flex-direction: column;
      }

      h3 {
        font-size: 1.125rem;
        line-height: 1;
        margin-block: 0;
      }

      p {
        margin-block-end: 0;
      }
    }
  }
</style>

Examples Jump to heading

Writing templates Jump to heading

Start by creating a <template> element with an ID. Inside, write the HTML you want to stamp out. Use curly brace expressions like {name} as placeholders for dynamic values. Expressions work in text content and attributes, but not for tag names or other structural parts of the template.

<template id="greeting">
  <p>Hello, {name}!</p>
</template>

Curly brackets are optional in special attributes such as for, if, and unless. For example, you can use if="isActive" instead of if="{isActive}".

Then add a <quiet-stamp> element and set its template attribute to the template's ID. The template must already exist in the DOM when the stamp connects. Pass data using data attributes or JSON, and the stamp will render the template with your values.

<quiet-stamp template="greeting" data-name="World"></quiet-stamp>

<!-- Renders: <p>Hello, World!</p> -->

You can reuse the same template with different data to stamp out multiple instances. However, avoid putting <script> and <style> tags inside your template, as they'll be duplicated each time it renders.

Templates are not reactive. Changing a stamp's data after the initial render won't update the content automatically. To manually rerender, call the renderTemplate() method. This replaces the content entirely, so any references to previously rendered elements will break.

Passing data with data attributes Jump to heading

To pass data to a stamp, add one or more data attributes to it. You can reference the data in the template using expressions that look like {propertyName}, where propertyName is a dataset property name that corresponds to a data attribute you've supplied, e.g. data-first-name can be referenced in a template using {firstName}.


<!-- Create a template -->
<template id="user-template">
  <h4 class="name">{name}</h4>
  <div class="tagline">{tagline}</div>
</template>

<!-- Stamp it out -->
<quiet-stamp
  template="user-template"
  data-name="Meowy McGee"
  data-tagline="Freedom's just another word for nothing left to lose."
></quiet-stamp>

<br>

<quiet-stamp
  template="user-template"
  data-name="Lady Pawington"
  data-tagline="Sophistication in a silk scarf and sassy whiskers."
></quiet-stamp>

Passing data with JSON Jump to heading

For simple values data attributes work well, but for complex or nested data structures, you can slot a <script type="application/json"> element inside the stamp. This keeps everything declarative while allowing proper JSON formatting.

You can access nested properties at any depth using dot notation, e.g. {company.department.name}. The following example is contrived to demonstrate working with deeply nested properties.

<template id="cat-hierarchy">
  <div class="cat-card">
    <h4>{household.name}</h4>
    <p>Territory: {household.territory.name}</p>
    <p>Nap Spot: {household.territory.napSpot.name}</p>
    <p>Current Occupant: {household.territory.napSpot.occupant.name}</p>
    <p>Occupant's Title: {household.territory.napSpot.occupant.title}</p>
  </div>
</template>

<quiet-stamp template="cat-hierarchy">
  <script type="application/json">
    {
      "household": {
        "name": "Whisker Manor",
        "territory": {
          "name": "The Sunny Living Room",
          "napSpot": {
            "name": "The Forbidden Couch",
            "occupant": {
              "name": "Chairman Meow",
              "title": "Chief Napping Officer"
            }
          }
        }
      }
    }
  </script>
</quiet-stamp>

<style>
  .cat-card {
    padding: 1rem;
    border: 1px solid var(--quiet-neutral-stroke-default);
    border-radius: 0.5rem;

    h4 {
      margin-block: 0 0.5rem;
    }

    p {
      margin-block: 0.25rem;
    }
  }
</style>

Unlike JavaScript objects, remember proper JSON requires double quotes around property names, e.g. {"name": "value"} instead of {name: "value"}.

You can combine data attributes with JSON data. When the same key exists in both, the JSON value takes precedence.

<quiet-stamp template="my-template" data-title="My Title">
  <script type="application/json">
    {
      "settings": { ... }
    }
  </script>
</quiet-stamp>

Boolean attributes Jump to heading

Boolean attributes, such as <button disabled>, are true when the attribute is present and false when the attribute is absent. Boolean attributes can be denoted in templates using a ? prefix, e.g. ?disabled.

A truthy comparison is done to determine if the attribute should be applied. All values aside from false, null, undefined, "", NaN, 0, -0, and 0n are considered truthy.

<template id="button-template">
  <button ?disabled="{isDisabled}">Click me</button>
</template>

<quiet-stamp template="button-template" data-is-disabled="false"></quiet-stamp>
<quiet-stamp template="button-template" data-is-disabled="true"></quiet-stamp>

HTML expressions Jump to heading

Expressions are HTML-escaped by default, but you can add :html to any content expression to render the raw, unescaped HTML.

<div>{content:html}</div>

The :html suffix only works in text content, not in attributes. Using it in an attribute will look for a property literally named content:html.

Using this option can be dangerous! Make sure you trust the included content, otherwise your app may become vulnerable to XSS exploits!

Escaping expressions Jump to heading

To escape an expression, add a \ character immediately before the opening curly bracket.

<div>\{name}</div>

Escaped expressions will not be evaluated and will be rendered as-is minus the backslash.

Conditionals Jump to heading

To show an element based on a truthy value, use the special if attribute with an expression. The element will only be shown when the expression is truthy.

<div if="someValue">
  I will only be shown when someValue is truthy
</div>

The inverse of this is the unless attribute, which shows an element when the value is not truthy.

<div unless="someValue">
  I will only be shown when someValue is falsy
</div>

Like boolean attributes, all values aside from false, null, undefined, "", NaN, 0, -0, and 0n are considered truthy.

Loops Jump to heading

To iterate over an array, use the for attribute on an element. The element will be repeated for each item in the array. Use the as attribute to specify a name for the current item.

The for attribute supports dot notation for nested arrays, e.g. for="data.users.list".

<template id="tag-list">
  <div class="tags" style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
    <quiet-badge for="tags" as="tag">
      {tag}
    </quiet-badge>
  </div>
</template>

<quiet-stamp template="tag-list">
  <script type="application/json">
    {
      "tags": ["Napping", "Zooming", "Knocking Things Off Tables"]
    }
  </script>
</quiet-stamp>

Inside a loop, you have access to special variables:

  • {$index} - The zero-based index of the current item
  • {$count} - The one-based index of the current item (human-readable)
  • {$key} - The property name when looping over objects (empty for arrays)
  • {$length} - The total number of items in the array
  • {$first} - True if this is the first item
  • {$last} - True if this is the last item
  • {$odd} - True if the index is odd (useful for zebra striping)
  • {$even} - True if the index is even
  • {$parent} - Access to the parent loop's context (for nested loops)

If you omit the as attribute, the current item will be available as $item by default.

<template id="loop-variables-demo">
  <ol>
    <li for="cats" as="cat">
      {cat}
      <span if="$first">(first)</span>
      <span if="$last">(last)</span>
    </li>
  </ol>
  <p>Total cats: {cats.length}</p>
</template>

<quiet-stamp template="loop-variables-demo">
  <script type="application/json">
    { "cats": ["Whiskers", "Mittens", "Sir Fluffington"] }
  </script>
</quiet-stamp>

When looping over an array of objects, access their properties using dot notation on the loop variable.

<template id="team-list">
  <ul>
    <li for="members" as="member">
      {member.name} - {member.role}
    </li>
  </ul>
</template>

<quiet-stamp template="team-list">
  <script type="application/json">
    {
      "members": [
        {"name": "Whiskers", "role": "Keyboard Warmer"},
        {"name": "Mittens", "role": "Paper Shredder"},
        {"name": "Sir Fluffington", "role": "Meeting Disruptor"}
      ]
    }
  </script>
</quiet-stamp>

Nested loops are supported. Inner loops can access variables from outer loops, but if the same alias is used, the inner loop's value will take precedence.

In nested loops, you can use {$parent} to access the parent loop's context. This is useful when you need the parent's loop variables like {$parent.$index} or {$parent.$first}. For deeply nested loops, chain multiple $parent references like {$parent.$parent.$first}.

<template id="nested-parent-demo">
  <div for="teams" as="team" class="team">
    <strong>{team.name}</strong>
    <ul>
      <li for="team.players" as="player">
        {$parent.$count}.{$count} — {player}
      </li>
    </ul>
  </div>
</template>

<quiet-stamp template="nested-parent-demo">
  <script type="application/json">
    {
      "teams": [
        { "name": "Red Team", "players": ["Whiskers", "Mittens", "Luna"] },
        { "name": "Blue Team", "players": ["Mochi", "Biscuit", "Noodle"] }
      ]
    }
  </script>
</quiet-stamp>

<style>
  .team {
    margin-block-end: 1rem;

    ul {
      margin-block: 0.25rem 0;
      padding-inline-start: 1.5rem;
    }
  }
</style>

Loops work well with conditionals. For example, you can show a message when an array is empty:

<template id="user-list">
  <ul>
    <li for="users" as="user">{user.name}</li>
  </ul>
  <p unless="users">No users found.</p>
</template>

In addition to arrays, you can also loop over an object's properties. The $key variable gives you each property name while the as alias provides the value.

<template id="cat-stats">
  <table class="quiet-striped">
    <tr for="stats" as="value">
      <th>{$key}</th>
      <td>{value}</td>
    </tr>
  </table>
</template>

<quiet-stamp template="cat-stats">
  <script type="application/json">
    {
      "stats": {
        "Name": "Whiskers",
        "Age": "3 years",
        "Favorite Nap Spot": "Sunny windowsill",
        "Daily Treats": "5"
      }
    }
  </script>
</quiet-stamp>

Replacing on render Jump to heading

If you don't want the <quiet-stamp> element to remain in the DOM after rendering, use the replace attribute. With this option, the component will stamp out the specified template and replace itself with the content.

This can be useful for controlling exactly what appears in the DOM, but it can also be confusing for developers who may not understand how the stamped content was generated.

When using replace, calling renderTemplate() again is not possible because the stamp element is removed from the DOM.

<template id="replaceable-template">
  <h3 style="margin-block: 0;">Where did it go?</h3>
  <p>
    Go ahead and inspect the DOM. The stamp has been 
    replaced by this content.
  </p>
</template>

<quiet-stamp 
  template="replaceable-template"
  replace
></quiet-stamp>

API Jump to heading

Importing Jump to heading

The autoloader is the recommended way to import components but, if you prefer to do it manually, the following code snippets will be helpful.

CDN Self-hosted

To manually import <quiet-stamp> from the CDN, use the following code.

import 'https://cdn.quietui.org/v4.0.0/components/stamp/stamp.js';

To manually import <quiet-stamp> from a self-hosted distribution, use the following code. Remember to replace /path/to/quiet with the appropriate local path.

import '/path/to/quiet/components/stamp/stamp.js';

Properties Jump to heading

Stamp has the following properties that can be set with corresponding attributes. In many cases, the attribute's name is the same as the property's name. If an attribute is different, it will be displayed after the property. Learn more about attributes and properties

Property Description Reflects Type Default
template The ID of the <template> element to use as a stamp. string ''
replace When true, the stamped content will replace the <quiet-stamp> element instead of being appended to it. This can be useful for controlling exactly what appears in the DOM, but it can also be confusing for developers who may not understand how the stamped content was generated. boolean false

Methods Jump to heading

Stamp supports the following methods. You can obtain a reference to the element and call them like functions in JavaScript. Learn more about methods

Name Description Arguments
renderTemplate() Processes the associated template and renders it to the DOM.
Search this website Toggle dark mode View the code on GitHub Follow @quietui.org on Bluesky Follow @quiet_ui on X

    No results found