Components Reference
Components
Components are small, reusable custom elements you author in one file and drop into any page.
Each one is a single .dmc file with up to four sections — <template>,
<props>, <script> and <style> — and the CMS
compiles it to a self-contained browser module that registers a
<dm-name> custom element. No build step, no framework wiring.
Manage them under Data → Components. This page is the complete reference; for guided learning start with the Walkthrough or the How-To recipes.
A component is a single file made of named blocks, much like a single-file component in other
frameworks. Three blocks are required (<template>,
<props>, <script>); <style> is optional.
Block order does not matter.
<template>
<div class="dm-greeting">Hello, {{name}}!</div>
</template>
<props>
{
"name": { "type": "string", "default": "world", "label": "Name to greet" }
}
</props>
<script>
export default {};
</script>
<style>
.dm-greeting { font-weight: 600; }
</style>
Saving compiles the source before it is written to disk — a malformed component never reaches the filesystem. The compiler reports precise, per-block errors (see Rules for every error code).
Plain HTML plus a Handlebars-style expression syntax. Data comes from the component's
props and reactive data() state. Supported syntax:
| Syntax | Purpose |
|---|---|
{{ value }} | Interpolate a prop or state value (HTML-escaped). |
{{#if cond}}…{{/if}} | Render the block only when cond is truthy. |
{{#unless cond}}…{{/unless}} | Render the block only when cond is falsy. |
{{#each list}}…{{/each}} | Repeat the block for each item. Inside, {{.}} is the item (for primitives), {{@index}}, {{@first}} and {{@last}} are available; object items expose their own keys. |
{{#with obj}}…{{/with}} | Scope the block to an object's keys. |
<slot></slot> | Project the light-DOM content placed between the element's tags. |
<template>
<ul class="dm-list">
{{#each items}}
<li class="{{#if @first}}first{{/if}}">{{@index}}. {{.}}</li>
{{/each}}
</ul>
{{#if title}}<h4>{{title}}</h4>{{/if}}
<slot></slot>
</template>
A JSON object (not an array). Each key is a prop name; each value declares a
type and may add a default and a label (shown in the editor's
preview panel). The type is validated at compile time.
Valid types: string, number, boolean, array,
object.
{
"initial": { "type": "number", "default": 0, "label": "Initial value" },
"step": { "type": "number", "default": 1, "label": "Step size" },
"showReset": { "type": "boolean", "default": false, "label": "Show reset button" },
"tags": { "type": "array", "default": [], "label": "Tag list" }
}
Props are passed as HTML attributes where the value is always a string; the runtime coerces it to
the declared type (so step="2" arrives as the number 2). Read props inside
onMount() and methods via this.props — see the script API below.
An ES module with a single export default { … } object describing the component. The
export is the only requirement; every key inside it is optional.
export default {
// Reactive state. Called WITHOUT `this` — do not read props here.
data() { return { count: 0 }; },
// Methods are bound to the component context (`this`).
methods: {
inc() { this.set({ count: this.data.count + this.props.step }); }
},
// Lifecycle — bound to `this`; props and the element are available here.
onMount() { this.set({ count: this.props.initial }); },
onUnmount() { /* clean up timers, listeners, etc. */ }
};
| Key | What it is |
|---|---|
data() | Returns the initial reactive state object. Called with no this binding — props are not available here; initialise from props in onMount(). |
methods | Object of functions bound to the component context. |
onMount() | Lifecycle hook fired when the element is attached to the DOM. Props and the element node are available. |
onUnmount() | Lifecycle hook fired when the element is removed — clear intervals/listeners here. |
Inside methods and lifecycle hooks, this exposes:
| Member | Description |
|---|---|
this.props | The (type-coerced) props the element was used with. |
this.data | The current reactive state snapshot. |
this.set({ … }) | Patch reactive state; the template re-renders with the new values. |
this.el | The custom-element DOM node. Its rendered markup lives in this.el.shadowRoot — attach event listeners there. |
Domma's browser globals ($, H, S, E, D,
_, …) are available inside the script just as on any page, so a component can fetch data,
read storage, or pop a toast.
Optional. CSS here is scoped to the component's shadow DOM, so selectors can't leak out and the
page's styles can't leak in. By convention components prefix their root class with
dm-name for clarity, but scoping is automatic either way.
<style>
.dm-counter { text-align: center; padding: 1rem; }
.dm-counter .value { font-size: 2.5rem; font-weight: 700; }
</style>
Two equivalent ways, both server-rendered into the page so the right component module is auto-injected:
Shortcode (Markdown)
[component name="counter" initial="10" step="2" showReset="true" /]
Custom element (HTML)
<dm-counter initial="10" step="2" show-reset="true"></dm-counter>
Both pass {initial, step, showReset} to the component. Note the attribute casing:
camelCase props map to kebab-case attributes on the raw tag
(showReset → show-reset); the shortcode accepts the prop name as written.
When the page is rendered the server collects every dm-* tag used and injects one
<script type="module" src="/api/components/name.js"> at the end of the
body, which registers the element via Domma.component().
To project content into a component's <slot>, use the custom-element form with
children:
<dm-reveal-panel heading="Click to reveal">
<p>Hidden content appears here when toggled.</p>
</dm-reveal-panel>
Source files live in content/components/name.dmc, with an optional sidecar
content/components/name.meta.json holding the bundled flag and
project tag. The REST surface (all under /api/components):
| Method & path | Auth | Purpose |
|---|---|---|
GET /api/components | read | List components with metadata + parsed props. |
GET /api/components/:name | read | Raw .dmc source + parsed props. |
GET /api/components/:name.js | public | Compiled browser module (what pages load). |
POST /api/components/compile | update | Transient compile for the live preview (nothing written). |
PUT /api/components/:name | update | Create or update (compiles first; errors returned, file untouched on failure). |
DELETE /api/components/:name | delete | Delete source + sidecar. |
GET /api/components/:name/export | read | Download a .dmcomponent.json bundle. |
POST /api/components/import | update | Import a bundle (?overwrite=true to replace). |
A bundle is a small JSON envelope you can move between sites (Export / Import buttons on the Components page):
{ "format": 1, "name": "counter", "source": "<template>…", "bundled": false }
Bundled & plugin components
The bundled flag marks a component as part of fresh-install seed data. Plugins may
also contribute components via the registerComponent() hook — these appear in the list
with a plugin badge, are served from memory, and are
read-only from the admin (you can't edit or delete them, and you can't save a
component whose name a plugin already owns).
Fresh installs ship with these (never overwritten if you've edited them) — open any in Components to read the full source:
counter— state, methods and lifecycle in one small examplecopy-button— an async method using the clipboard APIreveal-panel— conditional rendering plus a<slot>testimonial-card— presentational only (emptyexport default {})countdown-timer— an interval started inonMountand cleared inonUnmount
Next: How-To recipes →