Skip to content

Comark syntax

Comark (formerly MDC, “Markdown components”) is a way to use components and attributes without writing JSX. It is off by default. Turn it on in the headmatter:

---
title: My talk
comark: true
---

mdc: true is accepted as the older name. The Vite plugin, kadal-deck check, the VS Code extension and the hosted builder all read it. Everything MDX already does keeps working beside it.

:name[content]{attributes} is a component inside a line. The name is a component’s name in kebab-case or as written (badge and Badge are both <Badge>), and the brackets hold its children:

The release is :badge[New]{tone="good"} and ships :icon{name="rocket"} today.

That is <Badge tone="good">New</Badge> and <Icon name="rocket" />. Any built-in component works, and so do your own from src/components.tsx (:price-tag is <PriceTag>). A name that is an HTML element (:span, ::section) renders as that element.

{…} straight after a span of text, bold, code, a link or an image puts attributes on it:

This is [red text]{style="color: crimson"} and this is [the key number]{.big-number #headline}.
**Bold**{.accent} and `code`{style="font-size: 2em"} take them too.
[The docs](https://deck.kadal.cc/docs/){target=_blank} open in a new tab.
![A dark slide](/photo.png){width=240 lazy}
In {…} Means
.name a class (several: .a .b or .a.b), from src/styles.css or Tailwind
#name an id
key="value", key=value an attribute or prop, as a string
key alone true (lazy on an image becomes loading="lazy")
style="color: red; font-size: 2em" a style, turned into an object for a component
:key="…" data: JSON or a JavaScript-style literal (:n="3", :tags='["a", "b"]'), read and never run

A {…} that follows nothing it can apply to stays as text, and kadal-deck check warns about it. Event handlers (onclick="…") can’t be strings in a deck and are dropped with a warning.

A line ::name{attributes} opens a block component and a line :: closes it. What is between is Markdown, the component’s children:

::callout{kind="warn"}
The **default** slot is the body.
#title
A *named* slot becomes the `title` prop.
::

A line #name inside the block starts a named slot: everything after it, up to the next slot or the end, becomes the prop of that name (#my-footer becomes myFooter). A slot of one paragraph arrives as its text; a longer one keeps its blocks. #default returns to the body.

Bound values pass data rather than strings:

::callout{kind="tip" title="Bound props"}
A tag with `:name="…"` passes data: :badge[3 tags]{:tone="'accent'"}
::

A block inside a block takes one colon more than the block it is in:

::cols{:n="2"}
:::glass{tone="accent"}
Left card, inside `<Cols>`.
:::
:::glass
Right card.
:::
::

An HTML element name makes a plain element that can hold any Markdown, lists and code included:

::section{.two-up}
- an HTML element block
- holds any Markdown
::

A block left open closes at the end of what it is in, with a warning.

With Comark on, the deck’s own colon syntax is read first and behaves as before:

  • ::right::, ::left:: and the other slot markers still split a layout;
  • ::code-group … :: is still a code group;
  • {expression} that is not an attribute list is still an MDX expression ({1 + 1} prints 2);
  • code spans and fences are never read as Comark;
  • colons in prose, times (10:30), URLs and emoji shortcodes (:smile:) stay text.

kadal-deck check reads Comark components like JSX ones: an unknown name is an error with the nearest match, and a Comark mistake (an unclosed block, attributes that follow nothing) is a warning on its line. Without comark: true, a slide written in Comark is an MDX error, and the message says how to turn Comark on.

Send feedback