Styling the output
BMX emits plain semantic HTML with one hook. There are no utility classes, no wrapper divs, no generated identifiers — so any CSS you already have works, and Tailwind, SCSS or a stylesheet you wrote by hand all attach the same way.
What you get
Every document renders inside one element:
<article class="bmx"> … </article>
That is the only class BMX adds. Everything inside is the tag the construct means:
| You write | You get |
|---|---|
# Heading |
<h1> … <h6> |
| a paragraph | <p> |
- item |
<ul><li> |
1. item |
<ol><li> |
> quoted |
<blockquote> |
| ` ```burxt ` | <pre><code class="language-burxt"> |
*emphasis* |
<em> |
**strong** |
<strong> |
`code` |
<code> |
[text](target) |
<a href="target"> |
{{ slot }} |
the escaped value, as text |
A slot produces no element. Its value lands as text wherever the slot was, so a slot inside a
heading is part of the heading and inherits its styling. There is no <span class="slot"> to
target, deliberately — a hook there would make styling depend on where a value came from rather
than on what it is.
Plain CSS
Descendant selectors on the one class:
.bmx h1 { font-size: 2rem; line-height: 1.2; }
.bmx p { margin-block: 1em; }
.bmx blockquote{ border-inline-start: 3px solid #ddd; padding-inline-start: 1em; }
.bmx pre { overflow-x: auto; }
.bmx a { text-decoration-thickness: .08em; }
Because the markup is semantic, a classless stylesheet works with no configuration at all —
point one at .bmx and a document is styled.
Tailwind
Tailwind styles elements by class, and BMX emits none — which is exactly what
@tailwindcss/typography is for:
<article class="bmx prose prose-slate dark:prose-invert max-w-none"> … </article>
Two ways to get that second class onto the element:
- Wrap it. Put the rendered document inside a
<div class="prose">. Nothing to configure. -
Style
.bmxdirectly, so the document needs no extra class:@import "tailwindcss"; .bmx { @apply prose prose-slate dark:prose-invert max-w-none; }
One integration note: Tailwind’s JIT finds class names by scanning your source files. It has
no reason to look at .bmx files, and if you ever put a utility class in a slot value it will not
find it. Either add the glob:
content: ["./src/**/*.{html,js}", "./views/**/*.bmx"],
…or, better, keep utility classes out of documents entirely. A document that carries styling is a document you cannot restyle.
SCSS
Nesting maps onto the structure directly:
.bmx {
h1, h2, h3 { font-weight: 650; }
p + p { margin-block-start: 1em; }
pre { background: $code-bg; }
code { font-family: $mono; }
// code inside a paragraph, not inside a block
p code { padding: .15em .35em; border-radius: 3px; }
}
Syntax highlighting
A fenced block’s info string becomes class="language-<info>", which is the convention Prism,
highlight.js and Shiki all read. Nothing else is needed:
```burxt
let price: Decimal<2> = 19.99;
```
→ <pre><code class="language-burxt">let price: Decimal<2> = 19.99;\n</code></pre>
Note the escaping: the < in Decimal<2> is < in the output, as it must be. Highlighters
read the text content, so this is correct rather than a problem — but if you write your own
highlighting pass, read textContent and not innerHTML.
The info string is checked as a name before it becomes a class, so a document cannot inject an attribute through it.
What you cannot style, and why
There is no way to put a class on a paragraph from inside a document. No attribute syntax, no
{: .class} blocks. That is a real limitation and it is deliberate: the moment a document can
carry presentation, documents stop being restylable and every one of them has to be revisited
when the design changes.
If a particular block genuinely needs different treatment, it is a different view — and in a host at level 2 a view is a function, so composing two of them is ordinary code rather than a templating feature.