Skip to content

Styling Recipes

Trees, cards, and columns do not require new Carve syntax. Containers, classes, and attributes provide the HTML hooks required by CSS:

HTML hookCarve sourceGenerated HTML
A container name with no extension::: cards<div class="cards">
A span class[beta]{.badge}<span class="badge">
An attribute line above a block{#id .x data-y="2"}the id, classes and data-* on that element

If an application has no extension named name, ::: name becomes a div with the class name. The tree below therefore needs CSS but no parser extension or configuration.

carve-css ships that CSS as an opt-in layer:

css
@import "@markup-carve/carve-css";
@import "@markup-carve/carve-css/recipes.css";

Every example on this page is rendered live by that stylesheet.

Trees

A nested list inside ::: tree. The connectors are pseudo-elements on the list items, not characters in the source, so the markup stays a real <ul>: a screen reader still announces the nesting, in-page search still finds the leaves, and every node can be a link. Pasted tree(1) art in a code fence gives up all three.

carve
::: tree
- src/
  - parser/
    - blocks.crv
    - inline.crv
  - render/
    - html.crv
- tests/
:::
html
<div class="tree">
  <ul>
    <li>src/
      <ul>
        <li>parser/
          <ul>
            <li>blocks.crv</li>
            <li>inline.crv</li>
          </ul>
        </li>
        <li>render/
          <ul>
            <li>html.crv</li>
          </ul>
        </li>
      </ul>
    </li>
    <li>tests/</li>
  </ul>
</div>

Per-instance variation comes from an attribute rather than a second class. data-guides takes dotted or none.

carve
{data-guides="dotted"}
::: tree
- docs/
  - index.crv
:::
html
<div class="tree" data-guides="dotted">
  <ul>
    <li>docs/
      <ul>
        <li>index.crv</li>
      </ul>
    </li>
  </ul>
</div>

Cards

A list, laid out as panels. It stays a list, which matters: the same source renders as ordinary bullets in the Markdown, ANSI and plain-text targets.

carve
::: cards
- *Parse* - source to AST.
- *Render* - AST to a target.
- *Format* - AST back to source.
:::
html
<div class="cards">
  <ul>
    <li><strong>Parse</strong> - source to AST.</li>
    <li><strong>Render</strong> - AST to a target.</li>
    <li><strong>Format</strong> - AST back to source.</li>
  </ul>
</div>

Columns

data-columns sets the count, so one class covers every arity instead of multiplying into .columns-2, .columns-3 and whatever comes next. Blocks are kept whole across a column boundary, and the layout collapses to a single column on a narrow screen.

carve
{data-columns="3"}
::: columns
First run of text.

Second run of text.
:::
html
<div class="columns" data-columns="3">
  <p>First run of text.</p>
  <p>Second run of text.</p>
</div>

Steps

An ordered list whose numbers come from a CSS counter, so they can be a shape the list marker cannot be while the document still says "ordered list".

carve
::: steps
1. Install the package.
2. Import the stylesheet.
3. Add the recipes layer.
:::
html
<div class="steps">
  <ol>
    <li>Install the package.</li>
    <li>Import the stylesheet.</li>
    <li>Add the recipes layer.</li>
  </ol>
</div>

Margin notes

::: aside floats into the gutter where there is a gutter to float into, and becomes an ordinary indented block on a narrow screen - which is what the content meant anyway.

carve
::: aside
A note set beside the text on a wide screen.
:::
html
<div class="aside">
  <p>A note set beside the text on a wide screen.</p>
</div>

Lead paragraphs and badges

Neither needs a container: an attribute line above a paragraph, and a span class inline.

carve
{.lead}
An opening paragraph, marked by an attribute line.
html
<p class="lead">An opening paragraph, marked by an attribute line.</p>

data-tone picks from the same semantic pairs the admonitions use, so a badge and a warning agree about what warning looks like.

carve
Status: [beta]{.badge}, [stable]{.badge data-tone="success"}.
html
<p>Status: <span class="badge">beta</span>, <span class="badge" data-tone="success">stable</span>.</p>

Table modifiers

Attributes on the line above a table reach the <table>, and attributes on a row's closing pipe reach its <tr>. Between them that covers most of what people install a table plugin for.

carve
{.striped .compact}
|= Engine |= Recipes |
| carve-js | yes |{.ok}
| carve-rs | partial |{.warn}
html
<table class="striped compact">
  <thead>
    <tr><th scope="col">Engine</th><th scope="col">Recipes</th></tr>
  </thead>
  <tbody>
    <tr class="ok"><td>carve-js</td><td>yes</td></tr>
    <tr class="warn"><td>carve-rs</td><td>partial</td></tr>
  </tbody>
</table>

There is no cell-level attribute in Carve, so a row is the finest grain these can address. {.ok} written inside a cell is literal text, not a class.

Where CSS stops

CSS cannot hold state. Anything that has to remember whether it is open, or which of several things is selected, needs an element carrying that state - or script.

You wantCSS alone?Reach for
A branch that foldsno::: details per branch, or a host renderer
Tabsnothe Tabs extension
A copy button, search, sortingnoyour own page script
Syntax highlightingnoany highlighter, on the language-* class
Highlighting particular code linesnoa highlighter that emits per-line elements

The first one needs no custom code either. ::: details is a standard-tier extension shipped in all three engines and turned on with a single call; nest one per branch and the tree folds, with no JavaScript at all. The cost is the source - the branch becomes a container, so the fence widens at every level.

carve
::: tree
- :::: details "src/"
  - blocks.crv
  ::::
- tests/
:::
html
<div class="tree">
  <ul>
    <li>
      <details>
        <summary>src/</summary>
        <ul>
          <li>blocks.crv</li>
        </ul>
      </details>
    </li>
    <li>tests/</li>
  </ul>
</div>

Why this is not syntax

Every recipe here could have been a construct in the language. None of them needs to be. Ordinary containers and attributes keep the document readable when the stylesheet is absent and let a project add its own visual conventions without inventing specialized syntax.

That is also why these class names are a convention rather than a spec rule, and why the layer is a separate import: adding the stylesheet opts into the meaning that carve-css gives those words.

Released under the MIT License.