Directive syntax
How a directive is written: its line, attributes, forms, groups, titles, what it applies to, and escapes.
For AI agents: the documentation index is at llms.txt, and this page is available as Markdown.
Ascribe source is CommonMark with a few additions: directives, lines that start with @ and annotate the content around them; title lines, which give a directive a title; and phrases, reusable strings written {key}. Everything else is ordinary Markdown, and an Ascribe page read by a plain Markdown renderer still reads sensibly.
This page is how a directive is written. Each built-in directive is in the directive reference, phrases, links, and images are on a page of their own, and so is frontmatter. The Ascribe specification is the normative definition; each section links to its part of the spec.
Directive lines
A directive line has a name, optional attributes in braces, and an optional primary after a colon:
@<name> {<attributes>}: <primary>
@note {type=caution}: Back up your database first.
@include: _snippets/prerequisites.md
@steps
A line is a directive only when all of these hold:
@is the first character of the line (up to three spaces of indentation are allowed, as for a heading).- The name is a known directive: a built-in one,
end, or a project widget declared inascribe.toml. - The line isn’t inside a code block or a raw HTML block.
Anything else is text. @astrojs/react at the start of a line and support@example.com in a sentence are both plain text. A line shaped like a directive but with an unknown name (@warning:) is also text, and Ascribe warns about it and suggests the closest directive; write \@warning: if you mean the text. Names are case-sensitive, so @Note: is text too, and Ascribe suggests @note.
Spaces between the parts don’t matter, and nothing else may be on the line: @steps foo is an error. Built-in names never contain a hyphen; project widget names always do.
Attributes written without their braces, as in @note type=warning: You need Node 20., are the same error, and it’s the one reported for the line: the errors that would follow from it, such as the note binding the heading below, aren’t. When the keys are the directive’s attributes, the fix adds the braces (@note {type=warning}: …), and ascribe check --fix applies it when a primary follows the colon.
Attributes
Attributes are key=value pairs in braces, separated by commas. They use the same syntax after a directive’s name, after an image, and at the end of a table row’s first cell (table rows).
@note {type=caution}: …
@include {heading=false}: guides/setup.md#install
{width=600}
- Keys are lowercase: a letter, then letters, digits, or hyphens. Each directive accepts only the keys its schema declares.
- Values are a token (
type=caution,since=3.4,width=600px), a quoted string (label="Using other images", with\"and\\inside), or, for keys declared as sets, a value set of tokens joined by|(platform=cloud|on-prem). - A value must be quoted when it contains a space or any of
,|{}=". - Booleans are written out:
permalink=true,heading=false. A key with no value is an error. {}is allowed and means no attributes.
A value’s type comes from the directive’s schema, never from how it looks: 3.10 is a string where the schema says string.
Code block info strings
A fenced code block’s info string holds its language, then words, some of them key=value pairs Ascribe reads: phrases, title, tested, request, and response. They take no braces or commas, and a value with a space or a quote is quoted:
```ts title="connect.ts" phrases=true
- The language comes first. A pair written as the first word means the block has no language, and a renderer such as Astro’s takes the pair as the language, so the title is lost. Ascribe reports it as a block with no language.
- A pair has nothing around its
=, and its key is lowercase. Any other word, such as a highlighter’s{1,3}orshowLineNumbers, is left to the renderer. - A quote inside a quoted value is written
\\". CommonMark takes one backslash off before the pair is read, sotitle="say \"hi\""ends its value at the second quote. Writetitle="say \\"hi\\"", and a backslash as\\\\.
A word that starts with one of these keys and = but isn’t a pair, such as phrases = true, Title=x, or title="connect.ts with no closing quote, is a warning, because the block quietly loses what the pair meant. The quick fix writes the pair it was meant to be.
Forms: line and container
A directive is either a single line, or a container that holds blocks until @end. The line itself says which: a colon with nothing after it opens a container; any other directive line is a single line.
@note {type=caution}: A one-line note.
@note {type=caution}:
A container note.
It holds several paragraphs.
@end
@endcloses the innermost open container. There are no named closers.- A container must close before the block around it ends: its list item, block quote, group arm, or the document.
- A trailing colon on a directive with no container form is an error, and so is a container-only directive (such as
@variant) without one. - A text primary that happens to end in a colon is still a primary:
@note: Important:is a one-line note.
Containers may nest. Ascribe warns when they’re more than two levels deep (SPEC §3.10).
Groups
Some directives, @variant among them, form groups: a run of openers of the same directive is one group of alternatives. Each opener starts an arm and ends the previous one, and one @end closes the whole group.
@variant {deployment=cloud}:
Sign in to Quill Cloud and copy an API key.
@variant {deployment=self-managed}:
Point the agent at your server.
@end
A group can’t be nested directly inside an arm of a group of the same directive. For @variant, put both dimensions on one arm instead.
Titles
A title line starts with . followed by a character that isn’t a space or another .. It sits directly above a directive that takes a title (a note, @details, or a labeled @variant arm), with no blank line between.
.Try it without installing
@note {type=tip}
You can run Quill in the browser with no local setup.
- A title line starts a block: it follows a blank line, a heading, a directive line, or the start of its container.
- A
.line that isn’t directly above a directive that takes a title is ordinary text, so.NET is a frameworkis safe. Directly above a directive that doesn’t take a title, it stays text and Ascribe warns; start it with\.to silence the warning. - A title whose text starts with a dot escapes it:
.\.NETis the title.NET.
The editor shows title lines distinctly, so a paragraph that accidentally became a title is easy to spot.
Binding: what a directive applies to
A directive in line form applies to something, according to its binding:
| Binding | Applies to | Directives |
|---|---|---|
| Self | Its own primary, or nothing | @include, @snippet; @note with a primary |
| Heading | The heading above it, and that heading’s section | @id, @term; @summary with a primary |
| Following block | The next block in the same container | @steps; @details, @note, and @summary without a colon |
| Heading or block | At the top of a section, the section; anywhere else, the next block | @available, @intended, @memo |
Heading-bound directives go at the top of their section, directly under the heading, before any other content. Several may stack:
## Streaming sync
@id: streaming-sync
@available: cloud, self-managed preview 3.4
Following-block directives go directly above the block they describe, touching it. A blank line between them is allowed but warned about, since it hides what the directive annotates. Several stack, and all apply to the block the last one touches. A heading can’t be bound this way.
The short version: at the top of a section, a directive describes the section; anywhere else, it describes the block it touches. Two exceptions: a container @memo touches the block after its @end, and @summary without a colon describes its section from anywhere in it, by the paragraph it touches.
Lists and block quotes
Directives follow CommonMark’s container rules, like headings and code fences:
- A directive indented to a list item’s content belongs to that item, and binds blocks within it.
- A container opens and closes within one list item, or contains whole lists.
- A directive line never continues a paragraph, so an unindented directive right after a list item ends the list. Ascribe warns when this happens.
- A directive indented four or more spaces past its container’s content is part of an indented code block, and is text. Ascribe warns about that too.
- Block quotes work the same way, with
>in place of indentation.
Escapes
Ascribe uses CommonMark’s backslash escapes, so an escaped character renders as itself in any Markdown renderer:
| Escape | Prevents |
|---|---|
\@ |
A directive |
\{ |
A phrase |
\. |
A title line |
They’re rarely needed: a directive needs a known name at the start of a line, a phrase needs a declared key, and a title needs a directive on the next line.
Canonical form
Ascribe accepts any spacing the syntax allows, and has one canonical spelling for each construct: one space between a name and {, no spaces inside braces or around = and |, attributes in the order their schema declares, values quoted only when needed, and no blank line between a directive and its block. ascribe fmt rewrites source into canonical form, changing only Ascribe constructs and never how a page renders; ascribe fmt --check reports files that would change.
A general Markdown formatter that reflows paragraphs, such as Prettier with proseWrap: always, doesn’t know that title and directive lines start new blocks, and joins them with the text below. Format Ascribe sources with ascribe fmt, and exclude them from other formatters.