Pushword renders CommonMark-style Markdown with Tempest, plus rules that keep it interchangeable with a WYSIWYG block editor. Syntax Tempest cannot render faithfully falls back to CommonMark. Usual Markdown works unchanged.
Content is parsed block by block, not as a single document. Blocks are separated by a blank line. A block starting with < passes through as raw HTML, its Markdown left unrendered even when it opens with inline markup such as <strong>. HTML may therefore contain blank lines, and a block of text between two of them is rendered as Markdown (see Groups).
Attributes can be defined using the syntax {#attribute-name}, placed on a separate line just before the Markdown block it applies to. For a paragraph, {.ico-tip} Text with **emphasis** also works on one line, with an id or several attributes too ({#tip .note} Text), and inside a blockquote (> {.ico-tip} Text). Escape the brace to keep it as text: \{.ico-tip} Text. The separate-line form is conflicting with Prettier Markdown.
Advanced content types such as galleries, attachments, or page lists are supported through Twig functions. They work in any page body: Twig is not a separate step of the default chain but runs inside the Markdown filter, on each block before it is rendered, so a failing call degrades its own block only.
Twig syntax inside inline code, a code block or a <pre> element is not executed. To run Twig in code, write the <code></code> HTML yourself.
A page’s main_content_filters front matter replaces the filter chain rather than extending it: main_content_filters: twig runs Twig alone, with no Markdown and no link filters. A site can forbid the override with entity_can_override_filters: false.
Two filters expose the parser in templates:
{{ text|markdown }} — full block-level rendering (paragraphs, headings, lists, tables…). The output is wrapped in block tags: a one-liner becomes <p>…</p>.{{ text|markdown_inline }} — inline-only rendering, for text injected inside existing markup (a component lede, a caption, a subtitle): <p class="lede">{{ lede|markdown_inline }}</p>. Links, emphasis, inline code, strikethrough, {attributes} and Pushword inline shortcodes render exactly as with markdown, but no block tag is ever emitted. Block syntax (#, -, >, tables…) stays literal text, and blank lines don’t create paragraphs — meant for one-line inputs.Both filters mark their output HTML-safe and pass raw inline HTML through as-is.
Consecutive blocks can be wrapped in a single <div> by placing an opening and a closing tag as blocks of their own (blank-line separated). The markdown between the two lines is rendered normally — the renderer passes the wrapper through as-is:
<div id="pricing" class="grid md:grid-cols-2">
## Plan A
## Plan B
</div>
The block editor shows the pair as Group markers carrying the anchor and the classes; the markers are inserted and deleted together. It only claims a <div> line whose attributes are id and/or class — anything richer stays a Raw block.
A group can wrap its blocks in a collapsible block instead of a <div> — the Collapsible checkbox on the Group marker, or the call written by hand:
{{ startShowMore('itinerary', 'mt-8') }}
## Day 1
## Day 2
{{ endShowMore() }}
#itinerary) and to remember that the reader opened it. Omitted, it is derived from the page slug, so it stays the same from one render to the next.{{ startShowMore(showMoreExtraClass: 'mt-8') }}.endShowMore() takes the gradient classes fading the cut-off text, when the page background is not white: {{ endShowMore('via-gray-100 to-gray-100') }}./component/show_more.html.twig — override it in your theme to restyle the button or the fade.An older spelling, <!--start-show-more--> / <!--end-show-more-->, still renders the same and still shows up as a Group in the editor. It carries neither id nor class: pw:show-more:convert rewrites it to the call above.
A blockquote whose first line is a > [!label] marker renders as a notice — the syntax DocFX introduced and GitHub adopted, with Obsidian’s tolerance: the label is case-insensitive, and a title may follow it on the same line.
> [!warning] Version
>
> Last updated: August 2026. Corrections welcome via [GitHub issues](/contribute).
note, tip, important, warning and caution ship with a palette, question with a component of its own (below); any other label (sponsored, deprecated…) renders neutral and is yours to style — the wrapper always carries notice notice-<label>.[!note] → “Note”), so the level never rests on colour alone.{#anchor .class} line placed above the notice applies to it, as for any block. It can also close the marker line — > [!warning] Version {#version} — the way a heading takes its anchor; write both and they merge./component/notice.html.twig: override it in your theme to change the palette, add icons, or drop the wrapper entirely.A blockquote that does not open with a marker stays a plain blockquote, so quoting someone is unaffected.
> [!question] has a component of its own: the answer folds into a <details>, and the notice carries schema.org microdata — the title as name, the body as the text of an acceptedAnswer.
> [!question] Can luggage be carried between night stops?
>
> Wherever a road serves the night stop, yes — it works on the Tour du Mont-Blanc.
The answer stays in the HTML while folded, so find-in-page reaches it — browsers open a <details> on a match — and so does a crawler.
The Question is a top-level microdata item, not the mainEntity of a FAQPage: its render cache is shared by every page, so it cannot know whether the page is a FAQ. To declare FAQ pages, add itemprop="mainEntity" to your own component/notice/question.html.twig and the itemscope itemtype="https://schema.org/FAQPage" scope to the content container.
A label can own a template: > [!faq] renders through /component/notice/faq.html.twig when the site defines one, and through the generic notice otherwise — a Markdown syntax for editorial block components (a FAQ entry, a definition, a step) instead of a Twig include:
> [!faq] Can luggage be carried between night stops? {#carry-luggage}
>
> Wherever a road serves the night stop, yes — it works on the Tour du Mont-Blanc.
>
> It does not on high-altitude refuges, where you carry what you sleep with.
The template receives the same variables as the generic notice — title (the text after the label), content (the body, already rendered), id, class — plus params, the other attributes of the {…} line, so {#carry-luggage tag="h2"} reaches it as params.tag.
Multi-field components (a call-to-action with a title, a button label and a URL) do not fit a title-plus-body shape: use a snippet and its parameter schema for those.
Use a fenced code block with the mermaid language. In the block editor, insert a Code block and select mermaid: the diagram preview follows your edits, and syntax errors appear below the source. The saved content remains Markdown.
```mermaid
flowchart LR
A[Write Markdown] --> B[Preview in the editor]
B --> C[Publish the diagram]
```
The default frontend renders diagrams as SVG with the official Mermaid library,
loaded from the site's assets only when a diagram is present. Without JavaScript,
or if rendering fails, the code stays readable. Mermaid's strict security mode
disables diagram click actions and encodes HTML labels.
Custom themes overriding the base template's `css_stylesheets` or `script` blocks
must retain `parent()` or include these assets through Twig's `asset()` function:
- Stylesheet: `bundles/pushwordcore/mermaid/front.css`
- Script with `type="module"`: `bundles/pushwordcore/mermaid/front.js`
Include `bundles/pushwordcore/mermaid` in `static_assets` when exporting a static
site that does not already copy `bundles`. Syntax highlighters must exclude
`code.language-mermaid`. Use `accTitle` and `accDescr` in diagram definitions to
provide accessible titles and descriptions.
## Tables
Standard GFM table syntax is supported. You can merge cells horizontally using `->` as the cell content — it merges into the preceding cell via `colspan`.
```markdown
| Service | Identifiant | -> |
| --------------- | ----------- | -------------- |
| Authentication | auth | oauth.provider |
```
Renders as a table where “Identifiant” spans two columns. Multiple -> cells can follow each other to span more columns.
Note: -> in the first cell of a row has no effect (no preceding cell to merge into).
Per-column alignment uses the GFM delimiter row: :--- (left), :--: (center), ---: (right). The block editor’s column menu writes these markers for you.
| Name | Price | Qty |
| :---- | ----: | :--: |
| Apple | 1.20 | 3 |
Add the {.table-sticky-header} block attribute on the line before a table to pin its heading row while scrolling (in the editor and on the front). The block editor’s “Sticky heading” toggle adds it for you.
{.table-sticky-header}
| Col 1 | Col 2 |
| ----- | ----- |
| Val | Val 2 |
{#example-content}
## Example Content
{#mainParagraph}
This is a paragraph.
{#mainGallery}
{{ gallery(['piedweb-logo.png', '1.jpg', '2.jpg', '3.jpg']) }}
Rendered text gets locale-aware typography — smart quotes ("…" becomes « … » in French, “…” in English, „…“ in German), curly apostrophes (l'ami → l’ami), ellipsis (... → …), dimensions (30 x 40 → 30 × 40), (c) (r) (tm), and the French non-breaking spaces before : ; ! ?. Dashes are never touched.
The sources stay plain ASCII: the characters above only exist in the rendered HTML, applied by the Typography entity filter after markdown rendering. pre, code, script, style, svg and comments are left untouched, and the flat export straightens any typographic quote or non-breaking space that found its way into a page, so .md files remain greppable and diff-friendly. Code and Twig tags ({{ }}, {% %}) keep their bytes: a straightened ’ would close a single-quoted Twig string.
Disable it per page with filter_typography: 0 in the front matter, or per site by overriding the filters config key.