Skip to content
Browse the guides

For developers

Declare a setting once. The editor builds the field.

Every section on a Mirin website is a React component with a schema.json beside it. See how the schema becomes the fields owners edit, and which value reaches your component.

On this page

A component and its schema

A section on a Mirin website is a component and a schema. The component draws the section. The schema names it and lists every setting an owner may change, each with a type and a default.

Session Prices

3 items

Salt & Stone Pilates

Private sessions

Fifty five minutes with an instructor who knows your goals. Book one session or a series of five.

Book a session
Two people doing lunges on reformers in a bright studio with a window over a harbor.
  • Studio session55 minutes$95
  • Duet session55 minutes$70 each
  • Intro session45 minutes$60
{  "type": "session_prices",  "displayName": "Session Prices",  "category": "pricing",  "description": "Heading, intro and photo beside a grid of sessions with prices.",  "props": {    "heading": {      "type": "string",      "displayName": "Heading",      "default": "Private sessions"    },    "intro": {      "type": "textarea",      "displayName": "Intro",      "default": "Fifty five minutes with an instructor who knows your goals. Book one session or a series of five."    },    "photo": {      "type": "image",      "displayName": "Photo",      "default": {        "url": "/images/home-next/salt-stone-pilates.webp",        "alt": "Two people doing lunges on reformers in a bright studio with a window over a harbor."      }    },    "photoSide": {      "type": "select",      "displayName": "Photo side",      "options": [        "Left",        "Right"      ],      "default": "Right"    },    "showPrices": {      "type": "boolean",      "displayName": "Show prices",      "default": true    },    "columns": {      "type": "number",      "displayName": "Columns",      "min": 1,      "max": 3,      "step": 1,      "default": 3    },    "accentColor": {      "type": "color",      "displayName": "Accent color",      "default": "#3f5539"    },    "button": {      "type": "object",      "displayName": "Button",      "default": {        "text": "Book a session",        "link": "/book"      },      "properties": {        "text": {          "type": "string",          "displayName": "Text",          "default": "Book a session"        },        "link": {          "type": "link",          "displayName": "Link",          "default": "/book"        }      }    },    "sessions": {      "type": "array",      "displayName": "Sessions",      "fields": [        {          "name": "name",          "type": "string",          "displayName": "Name",          "default": "New session"        },        {          "name": "length",          "type": "string",          "displayName": "Length",          "default": "55 minutes"        },        {          "name": "price",          "type": "string",          "displayName": "Price",          "default": "$0"        }      ],      "default": [        {          "name": "Studio session",          "length": "55 minutes",          "price": "$95"        },        {          "name": "Duet session",          "length": "55 minutes",          "price": "$70 each"        },        {          "name": "Intro session",          "length": "45 minutes",          "price": "$60"        }      ]    }  }}
Session Prices from the sample Salt & Stone Pilates website. Open a setting and change it. The section updates, and the code panel lights the lines for that setting. Switch the code panel to see the props your component receives.
The component
A React component, exported as the default export of its file. Mirin renders it to HTML on the server, then hydrates it in the browser.
The schema
A schema.json file with the component’s type, displayName, category and description, and a props object. Each key under props is one setting.
The settings panel
The editor reads the schema and lists one row per setting, named by its displayName and marked by its type. Opening a row shows the field.
The saved value
What an owner enters is saved on the page under the setting’s key. Your component receives it as the prop with that name.

Write the component

Mirin renders React components. Export the component as the default export. Accept one props object whose keys match the keys under props in schema.json.

This is Session Prices, the section in the figure above. It reads every setting the schema declares, and nothing else.

SessionPrices/index.tsx
type Photo = { url: string; alt: string };
type Session = { name: string; length: string; price: string };

type SessionPricesProps = {
  heading?: string;
  intro?: string;
  photo?: Photo;
  photoSide?: string;
  showPrices?: boolean;
  columns?: number;
  accentColor?: string;
  button?: { text: string; link: string };
  sessions?: Session[];
};

export default function SessionPrices({
  heading = '',
  intro = '',
  photo,
  photoSide = 'Right',
  showPrices = true,
  columns = 3,
  accentColor = '#3f5539',
  button,
  sessions = [],
}: SessionPricesProps) {
  const image = photo?.url ? <img src={photo.url} alt={photo.alt} /> : null;
  const list = Array.isArray(sessions) ? sessions : [];

  return (
    <section className="session-prices">
      <div className="session-prices-hero">
        {photoSide === 'Left' && image}
        <div>
          <h2>{heading}</h2>
          {intro && <p>{intro}</p>}
          {button?.text && (
            <a href={button.link} style={{ background: accentColor }}>
              {button.text}
            </a>
          )}
        </div>
        {photoSide !== 'Left' && image}
      </div>
      <ul style={{ display: 'grid', gridTemplateColumns: `repeat(${columns}, 1fr)` }}>
        {list.map((session, index) => (
          <li key={index}>
            <strong>{session.name}</strong>
            <span>{session.length}</span>
            {showPrices && <b>{session.price}</b>}
          </li>
        ))}
      </ul>
    </section>
  );
}
SessionPrices/schema.json
{
  "type": "session_prices",
  "displayName": "Session Prices",
  "category": "pricing",
  "description": "Heading, intro and photo beside a grid of sessions with prices.",
  "props": {
    "heading": { "type": "string", "displayName": "Heading", "default": "Private sessions" },
    "intro": { "type": "textarea", "displayName": "Intro", "default": "Fifty five minutes with an instructor who knows your goals." },
    "photo": { "type": "image", "displayName": "Photo", "default": { "url": "", "alt": "" } },
    "photoSide": { "type": "select", "displayName": "Photo side", "options": ["Left", "Right"], "default": "Right" },
    "showPrices": { "type": "boolean", "displayName": "Show prices", "default": true },
    "columns": { "type": "number", "displayName": "Columns", "min": 1, "max": 3, "step": 1, "default": 3 },
    "accentColor": { "type": "color", "displayName": "Accent color", "default": "#3f5539" },
    "button": {
      "type": "object",
      "displayName": "Button",
      "default": { "text": "Book a session", "link": "/book" },
      "properties": {
        "text": { "type": "string", "displayName": "Text", "default": "Book a session" },
        "link": { "type": "link", "displayName": "Link", "default": "/book" }
      }
    },
    "sessions": {
      "type": "array",
      "displayName": "Sessions",
      "fields": [
        { "name": "name", "type": "string", "displayName": "Name", "default": "New session" },
        { "name": "length", "type": "string", "displayName": "Length", "default": "55 minutes" },
        { "name": "price", "type": "string", "displayName": "Price", "default": "$0" }
      ],
      "default": [
        { "name": "Studio session", "length": "55 minutes", "price": "$95" },
        { "name": "Intro session", "length": "45 minutes", "price": "$60" }
      ]
    }
  }
}

Four habits that keep a component easy to live with

  • Give every setting a default. A section added to a page starts with the defaults in the schema, so the first render already looks finished.
  • Expect empty values. An owner can clear any text, image or list. Render nothing for an empty value instead of a broken element.
  • Keep browser code in effects. The first render happens on the server, where window and document do not exist.
  • Keep keys stable. Saved content follows the key. A renamed key leaves the old value behind, as Keep settings stable explains below.

Setting types

The type decides which field the editor draws, and the shape of the value your component receives.

string
A one line text box. text draws the same box. Arrives as a string.
textarea
A text box over several lines, without formatting. Arrives as a string, line breaks included.
richtext
A formatting editor. Arrives as HTML when the page renders.
number
A number box with minus and plus buttons. The limits show underneath. Arrives as a number.
boolean
A switch. Arrives as true or false.
color
A swatch and a color code box that accepts hex or RGB. Arrives as the color the owner entered.
image
A preview, Image URL, Alt text and Browse Files. Arrives as an object with url and alt.
link
A text box that accepts a full URL or a path starting with /. url draws the same box. Arrives as a string.
select
A dropdown of the options you list. Arrives as the chosen option.
object
The settings under properties, one after another. Arrives as an object with those keys.
array
A list of entries built from fields, with add, move and delete. Arrives as an array of objects.
content_list
Connect a Content List. Arrives as an array of list entries.

Give an image setting an object default

An image setting whose default is a plain URL string stays a string, and the editor hides Alt text for it. Keep an object default such as { "url": "", "alt": "" } so owners can describe the picture.

A rich text setting arrives as HTML, so render it as HTML. The editor draws a one line text box for any type it does not recognize. Content List settings have their own guide, Content Lists in code.

Lists and groups

Use array for anything an owner adds to, reorders or removes. Describe each entry with fields, a list of settings that each carry a name. Every entry is then an object with those names as keys.

Open a row to edit its Text field.

What to Bring

3 items

Before your first class

What to bring

  • Grip socks
  • A water bottle
  • Layers you can move in
{  "props": {    "whatToBring": {      "type": "array",      "displayName": "Checklist items",      "fields": [        {          "name": "text",          "type": "string",          "displayName": "Text",          "default": ""        }      ],      "default": [        {          "text": "Grip socks"        },        {          "text": "A water bottle"        },        {          "text": "Layers you can move in"        }      ]    }  }}
The same checklist written two ways. With object entries and a Text field, each row opens onto a field. With plain strings, each row opens onto nothing, so an owner cannot change a word.
An array setting inside props
"whatToBring": {
  "type": "array",
  "displayName": "Checklist items",
  "fields": [
    { "name": "text", "type": "string", "displayName": "Text", "default": "" }
  ],
  "default": [
    { "text": "Grip socks" },
    { "text": "A water bottle" }
  ]
}

A row takes its label from the entry’s title, text or name field. Use object to keep settings together, such as a button’s text and link, and list them under properties. An object without properties does not appear in the settings panel.

Store a list of words as objects

Write [{ "text": "Grip socks" }] with a field named text, never ["Grip socks"]. The editor does not read itemSchema, so an array described with it has no fields to edit.

Setting options

Besides its type, a setting takes these keys. The schema itself also carries type, displayName, category and description, which name the component in Design Library.

displayName
The name on the setting’s row and above its field. Without it the editor shows the key.
description
A line of help under the setting’s name.
default
The value a new section starts with. A new list entry starts with each field’s default.
options
The choices for select. Write them as strings, or as objects with label and value.
min, max and step
Limits for number. A typed value is held between min and max, and step sets how far minus and plus move it.
fields and properties
The settings inside each array entry, and the settings inside an object.

Keys the editor does not read

The editor ignores label, required and placeholder. Use displayName for the name, and a default for the starting text.

How designs appear in Design Library

Design Library lists the components on a website. Each design shows its displayName and description, with its category and component version underneath. A category such as pricing reads Pricing.

Design Library

Components

Hero Banner

Salt & Stone PilatesClassesPrivate sessionsWorkshopsBook a session

Pilates by the water

Strength you can feel from the first class.

Small reformer classes and private sessions in a bright studio with room to breathe.

Book a session
Two people doing lunges on reformers in a bright studio with a window over a harbor.
Design Library with the sample studio’s designs, drawn at desktop width. Search, filter by category, choose a design to preview it, change the preview size, and open Inspect design to read its Fields and Details.

Search matches a design’s name, description, category, version and type, and the displayName of every setting. Inspect design shows the fields in schema order with their types, and Details shows the type as the Design reference.

Keep settings stable

Each page stores its content by key. When a new release of a theme pack changes a component’s schema, Page updates compares every page that uses the component with the new schema.

A key you removed or renamed
Listed under Fields to remove, with the value the page still holds. Remove unused fields deletes it from the page.
A key you added
Listed under Fields to add, with its default. Add missing fields with defaults writes that default onto the page.

A rename therefore replaces what an owner typed with the new default. Keep the old key while its content still matters. Owners review these changes in Theme packs.