# Writing Content (Markdown, MDX & Shortcodes)

## Writing Content

**Skill level:** Beginner · **Used in:** blog posts, service and area pages, legal and text pages

Every content file has two parts: the **frontmatter** between the two `---` lines at the top (settings and short texts, written in YAML), and the **body** below it (longer text, written in Markdown). Files that end in `.mdx` can also use **shortcodes**: small components such as callouts, checklists, and buttons.

```md
---
title: "Cancellation policy"        # frontmatter: YAML
description: "When you can change or cancel a visit."
date: 2026-09-01
draft: false
---

## Changing a visit                 ← body: Markdown

Free up to 24 hours before your arrival window.
```

In Homio, blog posts are `.mdx` files. Services, areas, projects, and legal pages are `.md` files, so they can use Markdown and the business tokens below, but not shortcodes.

### Frontmatter Basics
- Keep the keys exactly as they are; change only the values.
- Put text in double quotes: `title: "Deep clean"`. Write a quote inside text as `\"` or use single quotes around the text.
- Dates are `YYYY-MM-DD`: `date: 2026-09-08`.
- Lists start each item with `- ` (or use `["a", "b"]`), and nested keys are indented with two spaces. Copy the indentation of the item above.
- `src/content/_file_format.md` lists the common page fields with a comment on each.

[File Formats](/homio/beginner/file-formats/) explains YAML, Markdown, and TOML for beginners.

### The `# sample` Comments
Text after `#` on a YAML line is a comment: the site ignores it. Homio uses comments in two ways:

- **To list options.** `layout: "default" # default | list` tells you the allowed values.
- **To mark sample facts.** Values you must replace before launch end with `# sample`:

  ```yaml
  - value: 38 # sample
    label: "Crew members"
  ```

Search the project for `# sample` to find every sample price, rating, count, promise, phone number, and date. Replace the value and delete the comment once the fact is yours. If you can't confirm a figure, remove the item instead of keeping an invented number.

### Markdown Basics
```md
## A section heading
### A smaller heading

Plain text. **Bold**, _italic_, ~~struck through~~, and [a link](/pricing/).

- A list item
- Another item

1. A numbered step
2. Another step

> A quote from a client or a crew member.

| Room     | Time   |
| -------- | ------ |
| Kitchen  | 2 h    |
```

- Start headings at `##`: the page title is already the `#` heading. Posts get a table of contents from their headings (`[settings.markup.tableOfContents]` in `config.toml`).
- Links to other sites open in a new tab automatically.
- Code blocks with three backticks get syntax colours and a copy button; `[settings.markup.codeBlocks] theme` is `auto` (follows light and dark mode) or `dark`.

### Heading Sizes
Add a class in square brackets at the end of a heading to set its size without changing its level:

```md
## A door that sticks: three causes
### Before the crew arrives
```

The heading stays an `h2` or `h3` for search engines and screen readers, but is drawn at the `h4` or `h5` size.

### Business Details In Text
In `.md` files, these tokens are replaced with your details from `[business]` in `config.toml`, so you never type them twice:

| Token | Becomes |
| --- | --- |
| `{name}` | The business name |
| `{phone}` | The phone number as shown |
| `{email}` | The email address |
| `{address}` | The address |
| `{hours}` | The opening hours |

```md
Call {phone} or email {email} to change a booking.
```

In `.mdx` files, `{` starts code, so the tokens don't work there: write the details out, or put the text in a `.md` file. Form messages and several section fields (for example `"Call {phone}"` in a form error) also accept them, as noted in their comments.

### Images In The Body
- Markdown images with a path that starts with `/` point to the `public/` folder: `![Alt text](/images/gallery/1.jpg)` is `public/images/gallery/1.jpg`, served as it is.
- A relative path to `src/assets/images/` is optimized at build time, as in `components.mdx`: `![Close-up of carpet fibres](../../../assets/images/blog/carpet-texture.jpg)`.
- Frontmatter image fields such as `image` work differently: `"/images/blog/door-hinge-fix.jpg"` reads `src/assets/images/blog/door-hinge-fix.jpg` and is optimized. See [Icons & Images](/homio/customization/icons-images/).

### Shortcodes
These shortcodes work in every `.mdx` file without an import. The list is `src/lib/shortcodes.ts`, and the `/components/` page shows each one with its code (source: `src/content/pages/english/components.mdx`).

**Callout**: a tip or a warning pulled out of the text. `type` is `note` (default), `tip`, `warning`, or `danger`. Without a `title`, the type name is shown. The older name `<Notice>` still works.

```mdx
<Callout type="tip" title="Test on a hidden spot first">
  Try any cleaner inside a cupboard door before you use it on the whole surface.
</Callout>
```

**Steps**: numbers the headings inside it. Each `##` or `###` heading starts a step.

```mdx
<Steps>

### Find where it rubs

Close the door slowly and look for the worn patch on the frame.

### Tighten the top hinge

Use a hand screwdriver, not a drill.

</Steps>
```

**ListCheck**: turns a Markdown list into a checklist. `columns={2}` shows two columns on wider screens.

```mdx
<ListCheck columns={2}>

- **Oven and hob**: the first door most inspectors open.
- **Fridge and freezer**: defrosted, dry, door left open.

</ListCheck>
```

**JobTicket**: a checklist drawn as a job ticket. With `service` and no `total`, it shows that service's from-price from the rate card and a **Get a price** button.

```mdx
<JobTicket title="Before the crew arrives" code="TIP-021" label="Deep clean" service="deep-cleaning">

- Clear the kitchen counters
- Leave a key or a door code

</JobTicket>
```

**PriceCta**: the price band with a service's from-price and a button to the estimator. Blog posts add it on their own from the `service` field in their frontmatter; use the shortcode elsewhere.

```mdx
<PriceCta service="window-cleaning" />
```

**Button**: a theme button, with the same options as buttons in content files (`label`, `url`, `variant`: `fill`, `soft`, `outline`, `accent`, `fill-white`, or `text`).

```mdx
<Button label="Get a price" url="/book/" />
<Button variant="outline" label="Call us" url="/contact/" />
```

**Accordion**: a question that opens. Rows with the same `group` close each other; `expanded="true"` opens one on load.

```mdx
<Accordion label="Does someone have to be in during the visit?" group="visit" expanded="true">

No. Most clients leave a key or a door code.

</Accordion>
```

**Tabs**: content in tabs.

```mdx
<Tabs>
  <Tab name="Kitchen">Degrease the hood filter and wipe the cabinet fronts.</Tab>
  <Tab name="Bathroom">Descale the taps and the shower screen.</Tab>
</Tabs>
```

**ImageList and ImageItem**: photos in a grid with an optional `caption`. `width="1/2"` or `"1/3"` sets the share of the row; `videoSrc` adds a play button.

```mdx
<ImageList>
  <ImageItem imageSrc="/images/blog/door-hinge-fix.jpg" imageAlt="Screwdriver on a door hinge" caption="Tighten the top hinge first." />
</ImageList>
```

**VideoInline**: a video in the text. `provider` is `youtube` (the video id), `vimeo`, or `html5` (a file in `public/videos/`, with an optional `poster`). YouTube videos use the privacy-friendly `youtube-nocookie.com` player.

```mdx
<VideoInline src="rFVpSwgCkCo" provider="youtube" />
```

<Aside type="caution" title="MDX is stricter than Markdown">
  In `.mdx` files, `{`, `}`, and `<` have a special meaning. If a build fails
  on a line with one of them in normal text, put the text in backticks or
  write `\{` and `&lt;`. Plain `.md` files don't have this rule, but can't use
  shortcodes.
</Aside>

### Drafts
`draft: true` in the frontmatter keeps a page or entry out of the built site, the lists, the sitemap, and `llms.txt`. It still shows in `npm run dev`, so you can keep working on it. A `draft: true` in a collection's `-index.md` hides the whole collection. The four homepages don't read `draft`: delete their files to remove them.

`updated: 2026-10-01` marks the last meaningful edit: posts show it as "Updated", and it is used in structured data and the sitemap.

### Do It With AI
```text
Write a blog post about [TOPIC] as an .mdx file in src/content/blog/english/. Match the
frontmatter of the other posts there (title, description, image, imageAlt, date, author,
categories, tags, service, draft). Use ## headings, one <Callout type="tip"> and one <ListCheck>
where useful, and set service to the rate-card id of [SERVICE] so the price band shows. Keep it
under [N] words and use only facts I give you. Then write the French version in
src/content/blog/french/ with the same file name.
```