<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"><channel><title>embedding-shapes</title><link>https://emsh.cat/</link>
<description>Welcome to my blog. I write about technology, Nix, and other topics.</description><language>en</language><lastBuildDate>27 Jan 2026 00:00:00 +0000</lastBuildDate><item><title>One Human + One Agent = One Browser From Scratch</title><link>https://emsh.cat/one-human-one-agent-one-browser/</link>
<guid isPermaLink="true">https://emsh.cat/one-human-one-agent-one-browser/</guid><pubDate>27 Jan 2026 00:00:00 +0000</pubDate><description>&lt;p&gt;Just for the fun of it, I thought I&apos;d embark on a week-long quest to
generate millions of tokens and millions of lines of source code to
create one basic browser that can render HTML and CSS (no JS tho), and
hopefully I could use this to receive even more VC investments.&lt;/p&gt;
&lt;p&gt;But then I remembered that I have something even better: a human
brain! It is usually better than any machine at coordinating and
thinking through things, so let&apos;s see if we can hack something together,
one human brain and one LLM agent brain!&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;/content/one-human-one-agent-one-browser.webm&quot;
controls=&quot;&quot;&gt;&lt;a
href=&quot;/content/one-human-one-agent-one-browser.webm&quot;&gt;Demonstration of
one-agent-one-browser running with a bunch of different websites on
Linux/X11&lt;/a&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;The above might look like a simple .webm video, but it&apos;s actually a
highly sophisticated and advanced browser that was super hard to build,
encoded as pixels in a video file! Wowzers.&lt;/p&gt;
&lt;h2 id=&quot;day-1---starting-out&quot;&gt;Day 1 - Starting out&lt;/h2&gt;
&lt;p&gt;For extra fun when building this, I set these requirements for myself
and the agent:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I have three days to build it&lt;/li&gt;
&lt;li&gt;Not a single 3rd party Rust library/dependency allowed&lt;/li&gt;
&lt;li&gt;Allowed to use anything (commonly) provided out of the box on the OS
it runs on&lt;/li&gt;
&lt;li&gt;Should run on Windows, macOS and common Linux distributions&lt;/li&gt;
&lt;li&gt;Should be able to render some websites, most importantly, my own
blog and Hacker News, should be easy right?&lt;/li&gt;
&lt;li&gt;The codebase can always compile and be built&lt;/li&gt;
&lt;li&gt;The codebase should be readable by a human, although code quality
isn&apos;t the top concern&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So with these things in mind, I set out on the journal to build a
browser &quot;from scratch&quot;. I started with something really based, being
able to just render &quot;Hello World&quot;. Then to be able to render some nested
tags. Added the ability of taking screenshots so the agent could use
that. Added specifications for HTML/CSS (which I think the agent never
used :| ), and tried to nail down the requirements for the agent to use.
Also started doing &quot;regression&quot; or &quot;E2E&quot; tests with the screenshotting
feature, so we could compare to some baseline images and so on. Added
the ability to click on links just for the fun of it.&lt;/p&gt;
&lt;p&gt;After about a day together with Codex, I had something that could via
X11 and cURL, fetch and render websites when run, and the Cargo.lock is
empty. It was about 7500 lines long in total at that point, split across
files with all of them under 1000 lines long (which was a stated
requirement, so not a surprise).&lt;/p&gt;
&lt;h2 id=&quot;day-2---moving-on&quot;&gt;Day 2 - Moving On&lt;/h2&gt;
&lt;p&gt;Second day I got annoyed by the tests spawning windows while I was
doing other stuff, so added a --headless flag too. Did some fixes for
resizing the window, various compatibility fixes, some performance
issues and improved the font/text rendering a bunch. Workflow was
basically to pick a website, share a screenshot of the website without
JavaScript, ask Codex to replicate it following our instructions. Most
of the time was the agent doing work by itself, and me checking in when
it notifies me it was done.&lt;/p&gt;
&lt;h2 id=&quot;day-3---polish--cross-platform--day-4&quot;&gt;Day 3 - Polish &amp;amp;
Cross-platform (+ day 4)&lt;/h2&gt;
&lt;p&gt;Third day we made large changes, lots of new features and a bunch of
new features supported. More regression tests, fixing performance
issues, fixing crashes and whatnot. Also added scrolling because this is
a mother fucking browser, it has to be able to scroll. Added some debug
logs too because that&apos;ll look cool in the demonstration video above, and
also added support for the back button because it was annoying to start
from scratch if I clicked the wrong link while testing.&lt;/p&gt;
&lt;p&gt;At the end of the third day we also added starting support for macOS,
and managed to get a window to open, and the tests to pass. Seems to
work OK :) Once we had that working, we also added Windows support,
basically the same process, just another platform after all.&lt;/p&gt;
&lt;p&gt;Then the fourth day (whaaaat?) was basically polish, fixing CI for
all three platforms, making it pass and finally cutting a release based
on what got built in CI. Still all within 72 hours (3 days * 24 hours,
which obviously this is how you count days).&lt;/p&gt;
&lt;h2 id=&quot;the-results-after-3-days-70-hours&quot;&gt;The results after ~3 days
(~70 hours)&lt;/h2&gt;
&lt;p&gt;And here it is, in all its glory, made in ~20K lines of code and
under 72 hours of total elapsed time from first commit to last:&lt;/p&gt;
&lt;p&gt;&lt;a
href=&quot;https://github.com/embedding-shapes/one-agent-one-browser&quot;&gt;&lt;img
src=&quot;/content/one-agent-one-browser-hn.png&quot;
alt=&quot;Screenshot of one-agent-one-browser running on X11&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You could try compiling it yourself (zero Rust dependencies, so it&apos;s
really fast :) ), or you can find binaries built on CI
here:&lt;br/&gt;&lt;small&gt;&lt;a
href=&quot;https://github.com/embedding-shapes/one-agent-one-browser/releases&quot;&gt;&lt;span&gt;https://github.com/embedding-shapes/one-agent-one-browser/releases&lt;/span&gt;&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can clone the repository, build it and try it out for yourself.
It&apos;s not great, I wouldn&apos;t even say it&apos;s good, but it works, and
demonstrates that one person with one agent can build a browser from
scratch.&lt;/p&gt;
&lt;p&gt;This is what the &quot;lines of code&quot; count ended up being after all was
said and done, including support for three OSes:&lt;/p&gt;
&lt;pre class=&quot;shell&quot;&gt;&lt;code&gt;$ git rev-parse HEAD
e2556016a5aa504ecafd5577c1366854ffd0e280

$ cloc src --by-file
      72 text files.
      72 unique files.
       0 files ignored.

github.com/AlDanial/cloc v 2.06  T=0.06 s (1172.5 files/s, 373824.0 lines/s)
-----------------------------------------------------------------------------------
File                                            blank        comment           code
-----------------------------------------------------------------------------------
src/layout/flex.rs                                 96              0            994
src/layout/inline.rs                               85              0            933
src/layout/mod.rs                                  82              0            910
src/browser.rs                                     78              0            867
src/platform/macos/painter.rs                      96              0            765
src/platform/x11/cairo.rs                          77              0            713
src/platform/windows/painter.rs                    88              0            689
src/bin/render-test.rs                             87              0            666
src/style/builder.rs                               83              0            663
src/platform/windows/d2d.rs                        53              0            595
src/platform/windows/windowed.rs                   72              0            591
src/style/declarations.rs                          18              0            547
src/image.rs                                       81              0            533
src/platform/macos/windowed.rs                     80              2            519
src/net/winhttp.rs                                 61              2            500
src/platform/x11/mod.rs                            56              2            487
src/css.rs                                        103            346            423
src/html.rs                                        58              0            413
src/platform/x11/painter.rs                        48              0            407
src/platform/x11/scale.rs                          57              3            346
src/layout/table.rs                                39              1            340
src/platform/x11/xft.rs                            35              0            338
src/style/parse.rs                                 34              0            311
src/win/wic.rs                                     39              8            305
src/style/mod.rs                                   26              0            292
src/style/computer.rs                              35              0            279
src/platform/x11/xlib.rs                           32              0            278
src/layout/floats.rs                               31              0            265
src/resources.rs                                   36              0            238
src/css_media.rs                                   36              1            232
src/debug.rs                                       32              0            227
src/platform/windows/dwrite.rs                     20              0            222
src/render.rs                                      18              0            196
src/style/custom_properties.rs                     34              0            186
src/platform/windows/scale.rs                      28              0            184
src/url.rs                                         32              0            173
src/layout/helpers.rs                              12              0            172
src/net/curl.rs                                    31              0            171
src/platform/macos/svg.rs                          35              0            171
src/browser/url_loader.rs                          17              0            166
src/platform/windows/gdi.rs                        17              0            165
src/platform/windows/scaled.rs                     16              0            159
src/platform/macos/scaled.rs                       16              0            158
src/layout/svg_xml.rs                               9              0            152
src/win/com.rs                                     26              0            152
src/png.rs                                         27              0            146
src/layout/replaced.rs                             15              0            131
src/net/pool.rs                                    18              0            129
src/platform/macos/scale.rs                        17              0            124
src/style/selectors.rs                             18              0            123
src/style/length.rs                                17              0            121
src/cli.rs                                         15              0            112
src/platform/windows/headless.rs                   20              0            112
src/platform/macos/headless.rs                     19              0            109
src/bin/fetch-resource.rs                          14              0            101
src/geom.rs                                        10              0            101
src/browser/render_helpers.rs                      11              0            100
src/dom.rs                                         11              0            100
src/style/background.rs                            15              0            100
src/layout/tests.rs                                 7              0             85
src/platform/windows/d3d11.rs                      14              0             83
src/win/stream.rs                                  10              0             63
src/platform/windows/svg.rs                        13              0             54
src/main.rs                                         4              0             33
src/platform/mod.rs                                 6              0             28
src/app.rs                                          5              0             25
src/lib.rs                                          1              0             20
src/platform/windows/mod.rs                         2              0             19
src/net/mod.rs                                      4              0             16
src/platform/macos/mod.rs                           2              0             14
src/platform/windows/wstr.rs                        0              0              5
src/win/mod.rs                                      0              0              3
-----------------------------------------------------------------------------------
SUM:                                             2440            365          20150
-----------------------------------------------------------------------------------&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;takeaways&quot;&gt;Takeaways&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;One human using one agent seems far more effective than one human
using thousands of agents&lt;/li&gt;
&lt;li&gt;One agent can work on a single codebase for hours, making real
progress on ambitious projects&lt;/li&gt;
&lt;li&gt;This could probably scale to multiple humans too, each equipped with
their own agent, imagine what we could achieve!&lt;/li&gt;
&lt;li&gt;Sometimes slower is faster and also better&lt;/li&gt;
&lt;li&gt;The human who drives the agent might matter more than how the agents
work and are set up, the judge is still out on this one&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If one person with one agent can produce equal or better results than
&quot;hundreds of agents for weeks&quot;, then the answer to the question: &quot;Can we
scale autonomous coding by throwing more agents at a problem?&quot;, probably
has a more pessimistic answer than some expected.&lt;/p&gt;
</description></item><item><title>Good Taste</title><link>https://emsh.cat/good-taste/</link>
<guid isPermaLink="true">https://emsh.cat/good-taste/</guid><pubDate>25 Jan 2026 00:00:00 +0000</pubDate><description>&lt;p&gt;There is a lot of doom going around, how all developers, creatives
and others are losing our jobs because AI is coming to take them. Some
of that doom is real; there&apos;s work that&apos;s basically throughput and spec
compliance, and AI can replace big chunks of it. But what I’m talking
about here is the other kind of work, where you have authorship and
stake in what you&apos;re producing, as a human creator.&lt;/p&gt;
&lt;p&gt;I get why many of us fear AI and carry this sense of doom with us,
but at the same time, as a creative I&apos;m not super worried about it, and
not because I think AI cannot generate things, or even generate good
things, because I think it can. But because the hard thing has never
been to just produce &lt;em&gt;something&lt;/em&gt;. What&apos;s always been hard, is
producing &lt;em&gt;something good&lt;/em&gt;, something made by someone with Good
Taste. A real human being that stand in front of hundreds of choices and
knows (or feels) what gets to stay, what gets cut, what to push and what
should be refused, and finally sharing the choices they made with you,
through the medium.&lt;/p&gt;
&lt;p&gt;Just to be clear, I&apos;m mainly talking about &quot;creating&quot; and authorship
here, not consumption, since you as a consumer are the only judge if
something is good or not for you. But when you&apos;re making something, if
your goal is for others to enjoy it, Good Taste becomes a huge part of
what you actually excel at. Not taste as in what the snobby critics do
(who ultimately are consumers), but the creator&apos;s taste; direction,
restraint, pacing, taking risks and sometimes causing offense.&lt;/p&gt;
&lt;p&gt;The AI models and the platforms seems to tend to regress to the mean.
They optimize for something that &quot;sounds right&quot; and &quot;doesn&apos;t upset
anyone&quot;, creating something that is &quot;acceptable&quot; or even &quot;plausible&quot;.
This default voice is the opposite of authorship, where you explicitly
&lt;em&gt;don&apos;t&lt;/em&gt; want to just average thing out. You want to hit a
specific emotional effect with specific rhythm, and you&apos;re sometimes
willing to risk choices that look wrong until the entire thing is put
together. I think this is why most AI output feels so bland and
emotionless.&lt;/p&gt;
&lt;p&gt;I&apos;m not trying to say that &quot;AI can&apos;t make you feel anything&quot; because
I don&apos;t think it&apos;s true, I think AI can generate something that hits,
you can even explicitly train and/or steer AI to produce more
&quot;emotional&quot; outputs. But what&apos;s actually happening, even there, is that
there is a human deciding and curating what counts as a &quot;hit&quot;, and the
model is learning the shape of that. Ultimately you&apos;re bottling taste,
not replacing it. There is no stake at the other side, no point of view
that is getting committed to, no moment where it suddenly goes &quot;No, I&apos;m
not saying that&quot; or &quot;Yes, that&apos;s amazing, completely new direction
now&quot;.&lt;/p&gt;
&lt;p&gt;Without any human steering and editing, the AI will just keep handing
you infinite plausible takes until one of them happen to work. Infinite
&quot;fine&quot;, but not much more.&lt;/p&gt;
&lt;p&gt;Ultimately, I think AI is an accelerant. If you already have Good
Taste, it&apos;ll help you move faster, which feels like a good thing: great
people continue to produce great things.&lt;/p&gt;
&lt;p&gt;But on the other hand, it also makes it easier for people without
taste to generate a lot of output that&apos;s either bland or sometimes
straight up nonsense. Without the ecosystem rewarding high quality and
good things, it instead rewards volume and speed, everything slightly
tilting to noise.&lt;/p&gt;
&lt;p&gt;I feel like we&apos;re building the wrong things. The whole vibe right now
is &quot;replace the human part&quot; instead of &quot;make better tools for the human
part&quot;. I don&apos;t want a machine that replaces my taste, I want tools that
help me use my taste better; see the cut faster, compare directions,
compare architectural choices, find where I&apos;ve missed things, catch when
we&apos;re going into generics, and help me make sharper intentional
choices.&lt;/p&gt;
</description></item><item><title>Cursor&apos;s latest &quot;browser experiment&quot; implied success without evidence</title><link>https://emsh.cat/cursor-implied-success-without-evidence/</link>
<guid isPermaLink="true">https://emsh.cat/cursor-implied-success-without-evidence/</guid><pubDate>16 Jan 2026 00:00:00 +0000</pubDate><description>&lt;p&gt;On January 14th 2026, Cursor published a blog post titled &quot;Scaling
long-running autonomous coding&quot; (&lt;a
href=&quot;https://cursor.com/blog/scaling-agents&quot;&gt;https://cursor.com/blog/scaling-agents&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;In the blog post, they talk about their experiments with running
&quot;coding agents autonomously for weeks&quot; with the explicit goal of&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;understand[ing] how far we can push the frontier of agentic coding
for projects that typically take human teams months to complete&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;They talk about some approaches they tried, why they think those
failed, and how to address the difficulties.&lt;/p&gt;
&lt;p&gt;Finally they arrived at a point where something &quot;solved most of our
coordination problems and let us scale to very large projects without
any single agent&quot;, which then led to this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;To test this system, we pointed it at an ambitious goal: building a
web browser from scratch. The agents ran for close to a week, writing
over 1 million lines of code across 1,000 files. You can explore the
source code on GitHub (&lt;a
href=&quot;https://github.com/wilsonzlin/fastrender&quot;&gt;https://github.com/wilsonzlin/fastrender&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is where things get a bit murky and unclear. They claim &quot;Despite
the codebase size, new agents can still understand it and make
meaningful progress&quot; and &quot;Hundreds of workers run concurrently, pushing
to the same branch with minimal conflicts&quot;, but they never actually say
if this is successful or not, is it actually working? Can you run this
browser yourself? We don&apos;t know and they never say explicitly.&lt;/p&gt;
&lt;p&gt;After this, they embed the following video:&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;/content/cursor-screenshots.webm&quot; controls=&quot;&quot;&gt;&lt;a
href=&quot;/content/cursor-screenshots.webm&quot;&gt;Video&lt;/a&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;And below it, they say &quot;While it might seem like a simple screenshot,
building a browser from scratch is extremely difficult.&quot;.&lt;/p&gt;
&lt;h3
id=&quot;they-never-actually-claim-this-browser-is-working-and-functional&quot;&gt;They
never actually claim this browser is working and functional&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;error: could not compile &apos;fastrender&apos; (lib) due to 34 previous
errors; 94 warnings emitted&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And if you try to compile it yourself, you&apos;ll see that it&apos;s very far
away from being a functional browser at all, and seemingly, it never
actually was able to build.&lt;/p&gt;
&lt;p&gt;Multiple recent GitHub Actions runs on &lt;code&gt;main&lt;/code&gt; show
failures (including workflow-file errors), and independent build
attempts report dozens of compiler errors, recent PRs were all merged
with failing CI, and going back in the Git history from most recent
commit back 100 commits,&lt;br/&gt;&lt;a
href=&quot;https://gist.github.com/embedding-shapes/f5d096dd10be44ff82b6e5ccdaf00b29&quot;&gt;I
couldn&apos;t find a single commit that compiled cleanly&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&apos;m not sure what the &quot;agents&quot; they unleashed on this codebase
actually did, but they seemingly never ran &quot;cargo build&quot; or even less
&quot;cargo check&quot;, because both of those commands surface 10s of errors
(which surely would balloon should we solve them) and about 100
warnings. There is an open GitHub issue in their repository about this
right now: &lt;a
href=&quot;https://github.com/wilsonzlin/fastrender/issues/98&quot;&gt;https://github.com/wilsonzlin/fastrender/issues/98&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And diving into the codebase, if the compilation errors didn&apos;t make
that clear already, makes it very clear to any software developer that
none of this is actually engineered code. It is what is typically known
as &quot;AI slop&quot;, low quality &lt;em&gt;something&lt;/em&gt; that surely represents
&lt;em&gt;something&lt;/em&gt;, but it doesn&apos;t have intention behind it, and it
doesn&apos;t even compile at this point.&lt;/p&gt;
&lt;p&gt;They later start to talk about what&apos;s next, but not a single word
about how to run it, what to expect, how it&apos;s working or anything else.
Cursor&apos;s blog post provides no reproducible demo and no known-good
revision (tag/release/commit) to verify the screenshots, beyond linking
the repo.&lt;/p&gt;
&lt;p&gt;Regardless of intent, Cursor&apos;s blog post creates the impression of a
functioning prototype while leaving out the basic reproducibility
markers one would expect from such claim. They never explicitly claim
it&apos;s actually working, so no one can say they lied at least.&lt;/p&gt;
&lt;p&gt;They finish off the article saying:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But the core question, can we scale autonomous coding by throwing
more agents at a problem, has a more optimistic answer than we
expected.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Which seems like a really strange conclusion to arrive at, when all
they&apos;ve proved so far, is that agents can output millions of tokens and
still not end up with something that actually works.&lt;/p&gt;
&lt;p&gt;A &quot;browser experiment&quot; doesn&apos;t need to rival Chrome. A reasonable
minimum bar is: it compiles on a supported toolchain and can render a
trivial HTML file. Cursor&apos;s post doesn’t demonstrate that bar, and
current public build attempts fail at this too.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Cursor never says &quot;this browser is production-ready&quot;, but they do
frame it as &quot;building a web browser from scratch&quot; and &quot;meaningful
progress&quot; and then use a screenshot and &quot;extremely difficult&quot; language,
wanting to give the impression that this experiment actually was a
success.&lt;/p&gt;
&lt;p&gt;The closest they get to implying that this was a success, is this
part:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hundreds of agents can work together on a single codebase for weeks,
making real progress on ambitious projects.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But this extraordinary claim isn&apos;t backed up by any evidence. In the
blog post they never provide a working commit, build instructions or
even a demo that can be reproduced.&lt;/p&gt;
&lt;p&gt;I don&apos;t think anyone expects this browser to be the next Chrome, but
I do think that if you claim you&apos;ve built a browser, it should at least
be able to demonstrate being able to be compiled + loading a basic HTML
file at the very least.&lt;/p&gt;
</description></item><item><title>Niccup: Hiccup-like HTML Generation in ~120 Lines of Pure Nix</title><link>https://emsh.cat/introducing-niccup/</link>
<guid isPermaLink="true">https://emsh.cat/introducing-niccup/</guid><pubDate>03 Dec 2025 00:00:00 +0000</pubDate><description>&lt;p&gt;Ever wish it was really simple to create HTML from just Nix
expressions, not even having to deal with function calls or other
complexities? With niccup, now there is!&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb1&quot;&gt;&lt;pre
class=&quot;sourceCode nix&quot;&gt;&lt;code class=&quot;sourceCode nix&quot;&gt;&lt;span id=&quot;cb1-1&quot;&gt;&lt;a href=&quot;#cb1-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;div#main.container&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-2&quot;&gt;&lt;a href=&quot;#cb1-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;lang&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;en&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-3&quot;&gt;&lt;a href=&quot;#cb1-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;h1&amp;quot;&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;Hello&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb2&quot;&gt;&lt;pre
class=&quot;sourceCode html&quot;&gt;&lt;code class=&quot;sourceCode html&quot;&gt;&lt;span id=&quot;cb2-1&quot;&gt;&lt;a href=&quot;#cb2-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;ot&quot;&gt; class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;container&amp;quot;&lt;/span&gt;&lt;span class=&quot;ot&quot;&gt; id&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;main&amp;quot;&lt;/span&gt;&lt;span class=&quot;ot&quot;&gt; lang&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;en&amp;quot;&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-2&quot;&gt;&lt;a href=&quot;#cb2-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;dt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;h1&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;&amp;gt;&lt;/span&gt;Hello&lt;span class=&quot;dt&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;h1&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-3&quot;&gt;&lt;a href=&quot;#cb2-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That&apos;s it. Nix data structures in, HTML out. Zero dependencies. Works
with flakes or without.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/embedding-shapes/niccup&quot;&gt;Source Code&lt;/a&gt;
| &lt;a href=&quot;https://emsh.cat/niccup/&quot;&gt;Website/Docs&lt;/a&gt; | &lt;a
href=&quot;https://emsh.cat/introducing-niccup/&quot;&gt;Introduction Blog
Post&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;why-generate-html-from-nix&quot;&gt;Why Generate HTML from Nix?&lt;/h2&gt;
&lt;p&gt;If you&apos;re building static sites, documentation, or web artifacts as
part of a Nix derivation, you&apos;ve probably resorted to one of these:&lt;/p&gt;
&lt;ol type=&quot;1&quot;&gt;
&lt;li&gt;String interpolation
(&lt;code&gt;&apos;&apos;&amp;lt;div&amp;gt;${title}&amp;lt;/div&amp;gt;&apos;&apos;&lt;/code&gt;). Works until you need
escaping or composition&lt;/li&gt;
&lt;li&gt;External templating tools. Another dependency, another language,
another build step&lt;/li&gt;
&lt;li&gt;Importing HTML files, no programmatic generation&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Niccup takes a different approach: represent HTML as native Nix data
structures. This gives you &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;,
&lt;code&gt;builtins.concatStringsSep&lt;/code&gt;, and the entire Nix expression
language for free. No new syntax to learn. No dependencies to
manage.&lt;/p&gt;
&lt;h2 id=&quot;the-syntax&quot;&gt;The Syntax&lt;/h2&gt;
&lt;p&gt;An element is a list:
&lt;code&gt;[ tag-spec attrs? children... ]&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&quot;tag-specs-with-css-shorthand&quot;&gt;Tag Specs with CSS Shorthand&lt;/h3&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb3&quot;&gt;&lt;pre
class=&quot;sourceCode nix&quot;&gt;&lt;code class=&quot;sourceCode nix&quot;&gt;&lt;span id=&quot;cb3-1&quot;&gt;&lt;a href=&quot;#cb3-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;div&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-2&quot;&gt;&lt;a href=&quot;#cb3-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;# &amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-3&quot;&gt;&lt;a href=&quot;#cb3-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-4&quot;&gt;&lt;a href=&quot;#cb3-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;input#search&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-5&quot;&gt;&lt;a href=&quot;#cb3-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;# &amp;lt;input id=&amp;quot;search&amp;quot;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-6&quot;&gt;&lt;a href=&quot;#cb3-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-7&quot;&gt;&lt;a href=&quot;#cb3-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;button.btn.primary&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-8&quot;&gt;&lt;a href=&quot;#cb3-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;# &amp;lt;button class=&amp;quot;btn primary&amp;quot;&amp;gt;&amp;lt;/button&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-9&quot;&gt;&lt;a href=&quot;#cb3-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-10&quot;&gt;&lt;a href=&quot;#cb3-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;form#login.auth.dark&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-11&quot;&gt;&lt;a href=&quot;#cb3-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;# &amp;lt;form class=&amp;quot;auth dark&amp;quot; id=&amp;quot;login&amp;quot;&amp;gt;&amp;lt;/form&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;attributes&quot;&gt;Attributes&lt;/h3&gt;
&lt;p&gt;The optional second element can be an attribute set:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb4&quot;&gt;&lt;pre
class=&quot;sourceCode nix&quot;&gt;&lt;code class=&quot;sourceCode nix&quot;&gt;&lt;span id=&quot;cb4-1&quot;&gt;&lt;a href=&quot;#cb4-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-2&quot;&gt;&lt;a href=&quot;#cb4-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;href&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;/about&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;_blank&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-3&quot;&gt;&lt;a href=&quot;#cb4-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;st&quot;&gt;&amp;quot;About&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-4&quot;&gt;&lt;a href=&quot;#cb4-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;# &amp;lt;a href=&amp;quot;/about&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;About&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Classes from the shorthand and attribute set are merged:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb5&quot;&gt;&lt;pre
class=&quot;sourceCode nix&quot;&gt;&lt;code class=&quot;sourceCode nix&quot;&gt;&lt;span id=&quot;cb5-1&quot;&gt;&lt;a href=&quot;#cb5-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;div.base&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-2&quot;&gt;&lt;a href=&quot;#cb5-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;added&amp;quot;&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;another&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;];&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-3&quot;&gt;&lt;a href=&quot;#cb5-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;st&quot;&gt;&amp;quot;content&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-4&quot;&gt;&lt;a href=&quot;#cb5-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;# &amp;lt;div class=&amp;quot;base added another&amp;quot;&amp;gt;content&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Boolean handling:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb6&quot;&gt;&lt;pre
class=&quot;sourceCode nix&quot;&gt;&lt;code class=&quot;sourceCode nix&quot;&gt;&lt;span id=&quot;cb6-1&quot;&gt;&lt;a href=&quot;#cb6-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;input&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-2&quot;&gt;&lt;a href=&quot;#cb6-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;checkbox&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-3&quot;&gt;&lt;a href=&quot;#cb6-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;va&quot;&gt;checked&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;cn&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-4&quot;&gt;&lt;a href=&quot;#cb6-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;va&quot;&gt;disabled&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;cn&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-5&quot;&gt;&lt;a href=&quot;#cb6-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;# &amp;lt;input checked=&amp;quot;checked&amp;quot; type=&amp;quot;checkbox&amp;quot;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;true&lt;/code&gt; renders as &lt;code&gt;attr=&quot;attr&quot;&lt;/code&gt;.
&lt;code&gt;false&lt;/code&gt; and &lt;code&gt;null&lt;/code&gt; are omitted entirely.&lt;/p&gt;
&lt;h3 id=&quot;children-and-composition&quot;&gt;Children and Composition&lt;/h3&gt;
&lt;p&gt;Children can be strings, numbers, nested elements, or lists:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb7&quot;&gt;&lt;pre
class=&quot;sourceCode nix&quot;&gt;&lt;code class=&quot;sourceCode nix&quot;&gt;&lt;span id=&quot;cb7-1&quot;&gt;&lt;a href=&quot;#cb7-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;p&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-2&quot;&gt;&lt;a href=&quot;#cb7-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;st&quot;&gt;&amp;quot;Text with &amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-3&quot;&gt;&lt;a href=&quot;#cb7-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;strong&amp;quot;&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;emphasis&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-4&quot;&gt;&lt;a href=&quot;#cb7-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;st&quot;&gt;&amp;quot; and more.&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-5&quot;&gt;&lt;a href=&quot;#cb7-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;# &amp;lt;p&amp;gt;Text with &amp;lt;strong&amp;gt;emphasis&amp;lt;/strong&amp;gt; and more.&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Lists are flattened one level, which makes &lt;code&gt;map&lt;/code&gt; work
naturally:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb8&quot;&gt;&lt;pre
class=&quot;sourceCode nix&quot;&gt;&lt;code class=&quot;sourceCode nix&quot;&gt;&lt;span id=&quot;cb8-1&quot;&gt;&lt;a href=&quot;#cb8-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;ul&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-2&quot;&gt;&lt;a href=&quot;#cb8-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;va&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;li&amp;quot;&lt;/span&gt; item &lt;span class=&quot;op&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-3&quot;&gt;&lt;a href=&quot;#cb8-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;       &lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;One&amp;quot;&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;Two&amp;quot;&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;Three&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-4&quot;&gt;&lt;a href=&quot;#cb8-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;# &amp;lt;ul&amp;gt;&amp;lt;li&amp;gt;One&amp;lt;/li&amp;gt;&amp;lt;li&amp;gt;Two&amp;lt;/li&amp;gt;&amp;lt;li&amp;gt;Three&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Text content is automatically escaped:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb9&quot;&gt;&lt;pre
class=&quot;sourceCode nix&quot;&gt;&lt;code class=&quot;sourceCode nix&quot;&gt;&lt;span id=&quot;cb9-1&quot;&gt;&lt;a href=&quot;#cb9-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;p&amp;quot;&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;&amp;lt;script&amp;gt;alert(&amp;#39;xss&amp;#39;)&amp;lt;/script&amp;gt;&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-2&quot;&gt;&lt;a href=&quot;#cb9-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;# &amp;lt;p&amp;gt;&amp;amp;lt;script&amp;amp;gt;alert(&amp;#39;xss&amp;#39;)&amp;amp;lt;/script&amp;amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;raw-html-and-comments&quot;&gt;Raw HTML and Comments&lt;/h3&gt;
&lt;p&gt;For trusted HTML that shouldn&apos;t be escaped:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb10&quot;&gt;&lt;pre
class=&quot;sourceCode nix&quot;&gt;&lt;code class=&quot;sourceCode nix&quot;&gt;&lt;span id=&quot;cb10-1&quot;&gt;&lt;a href=&quot;#cb10-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;div&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;raw &lt;span class=&quot;st&quot;&gt;&amp;quot;&amp;lt;strong&amp;gt;Already formatted&amp;lt;/strong&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-2&quot;&gt;&lt;a href=&quot;#cb10-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;# &amp;lt;div&amp;gt;&amp;lt;strong&amp;gt;Already formatted&amp;lt;/strong&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For HTML comments:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb11&quot;&gt;&lt;pre
class=&quot;sourceCode nix&quot;&gt;&lt;code class=&quot;sourceCode nix&quot;&gt;&lt;span id=&quot;cb11-1&quot;&gt;&lt;a href=&quot;#cb11-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;div&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;comment &lt;span class=&quot;st&quot;&gt;&amp;quot;TODO: refactor&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-2&quot;&gt;&lt;a href=&quot;#cb11-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;p&amp;quot;&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;Content&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-3&quot;&gt;&lt;a href=&quot;#cb11-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;# &amp;lt;div&amp;gt;&amp;lt;!-- &lt;/span&gt;&lt;span class=&quot;al&quot;&gt;TODO&lt;/span&gt;&lt;span class=&quot;co&quot;&gt;: refactor --&amp;gt;&amp;lt;p&amp;gt;Content&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;void-elements&quot;&gt;Void Elements&lt;/h3&gt;
&lt;p&gt;Self-closing tags work as expected:&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb12&quot;&gt;&lt;pre
class=&quot;sourceCode nix&quot;&gt;&lt;code class=&quot;sourceCode nix&quot;&gt;&lt;span id=&quot;cb12-1&quot;&gt;&lt;a href=&quot;#cb12-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;img&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;photo.jpg&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;alt&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;A photo&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-2&quot;&gt;&lt;a href=&quot;#cb12-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;# &amp;lt;img alt=&amp;quot;A photo&amp;quot; src=&amp;quot;photo.jpg&amp;quot;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-3&quot;&gt;&lt;a href=&quot;#cb12-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-4&quot;&gt;&lt;a href=&quot;#cb12-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;meta&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;charset&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;utf-8&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-5&quot;&gt;&lt;a href=&quot;#cb12-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;# &amp;lt;meta charset=&amp;quot;utf-8&amp;quot;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;api&quot;&gt;API&lt;/h2&gt;
&lt;p&gt;Four functions. That&apos;s the entire public interface.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;render&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Render to minified HTML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;renderPretty&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Render to indented HTML (2-space indent)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;raw&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mark a string as trusted, unescaped HTML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;comment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create an HTML comment node&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;a-real-example-blog-generator&quot;&gt;A Real Example: Blog
Generator&lt;/h2&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb13&quot;&gt;&lt;pre
class=&quot;sourceCode nix&quot;&gt;&lt;code class=&quot;sourceCode nix&quot;&gt;&lt;span id=&quot;cb13-1&quot;&gt;&lt;a href=&quot;#cb13-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;niccup&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;:&lt;/span&gt;
&lt;span id=&quot;cb13-2&quot;&gt;&lt;a href=&quot;#cb13-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;let&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-3&quot;&gt;&lt;a href=&quot;#cb13-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;va&quot;&gt;h&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; niccup&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;lib&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-4&quot;&gt;&lt;a href=&quot;#cb13-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-5&quot;&gt;&lt;a href=&quot;#cb13-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;va&quot;&gt;posts&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-6&quot;&gt;&lt;a href=&quot;#cb13-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;slug&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;hello&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;Hello World&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;Welcome!&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-7&quot;&gt;&lt;a href=&quot;#cb13-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;slug&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;update&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;An Update&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;More content here.&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-8&quot;&gt;&lt;a href=&quot;#cb13-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;op&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-9&quot;&gt;&lt;a href=&quot;#cb13-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-10&quot;&gt;&lt;a href=&quot;#cb13-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;va&quot;&gt;layout&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;: h&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;renderPretty &lt;span class=&quot;op&quot;&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-11&quot;&gt;&lt;a href=&quot;#cb13-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;st&quot;&gt;&amp;quot;html&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;lang&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;en&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-12&quot;&gt;&lt;a href=&quot;#cb13-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;head&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-13&quot;&gt;&lt;a href=&quot;#cb13-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;      &lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;meta&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;charset&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;utf-8&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-14&quot;&gt;&lt;a href=&quot;#cb13-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;      &lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;meta&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;viewport&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;width=device-width&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-15&quot;&gt;&lt;a href=&quot;#cb13-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;      &lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;title&amp;quot;&lt;/span&gt; title &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-16&quot;&gt;&lt;a href=&quot;#cb13-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-17&quot;&gt;&lt;a href=&quot;#cb13-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;body&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-18&quot;&gt;&lt;a href=&quot;#cb13-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;      &lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;nav&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;va&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;a&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;href&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;/&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;${&lt;/span&gt;p&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;slug&lt;span class=&quot;sc&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;.html&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt; p&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;title &lt;span class=&quot;op&quot;&gt;])&lt;/span&gt; posts&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-19&quot;&gt;&lt;a href=&quot;#cb13-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;      &lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;main&amp;quot;&lt;/span&gt; content &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-20&quot;&gt;&lt;a href=&quot;#cb13-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;      &lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;footer&amp;quot;&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;Generated with niccup&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-21&quot;&gt;&lt;a href=&quot;#cb13-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-22&quot;&gt;&lt;a href=&quot;#cb13-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;op&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-23&quot;&gt;&lt;a href=&quot;#cb13-23&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-24&quot;&gt;&lt;a href=&quot;#cb13-24&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;va&quot;&gt;renderPost&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;va&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; layout &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-25&quot;&gt;&lt;a href=&quot;#cb13-25&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;va&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; post&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-26&quot;&gt;&lt;a href=&quot;#cb13-26&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;va&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;article&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;h1&amp;quot;&lt;/span&gt; post&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;title &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;p&amp;quot;&lt;/span&gt; post&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;body &lt;span class=&quot;op&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-27&quot;&gt;&lt;a href=&quot;#cb13-27&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;op&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-28&quot;&gt;&lt;a href=&quot;#cb13-28&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-29&quot;&gt;&lt;a href=&quot;#cb13-29&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;in&lt;/span&gt; pkgs&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;runCommand &lt;span class=&quot;st&quot;&gt;&amp;quot;blog&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{}&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-30&quot;&gt;&lt;a href=&quot;#cb13-30&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;  mkdir -p $out&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-31&quot;&gt;&lt;a href=&quot;#cb13-31&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;builtins&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;concatStringsSep &lt;span class=&quot;st&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;va&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-32&quot;&gt;&lt;a href=&quot;#cb13-32&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;    cat &amp;gt; $out/&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;${&lt;/span&gt;p&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;slug&lt;span class=&quot;sc&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;.html &amp;lt;&amp;lt; &amp;#39;EOF&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-33&quot;&gt;&lt;a href=&quot;#cb13-33&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;${&lt;/span&gt;renderPost p&lt;span class=&quot;sc&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-34&quot;&gt;&lt;a href=&quot;#cb13-34&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;    EOF&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-35&quot;&gt;&lt;a href=&quot;#cb13-35&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;  &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; posts&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-36&quot;&gt;&lt;a href=&quot;#cb13-36&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This produces a complete static site as a Nix derivation. Add a post
to the list, rebuild, done.&lt;/p&gt;
&lt;h2 id=&quot;limitations&quot;&gt;Limitations&lt;/h2&gt;
&lt;p&gt;Being upfront about what niccup doesn&apos;t do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Attribute order is alphabetical.&lt;/strong&gt; Nix attribute
sets have no insertion order; &lt;code&gt;builtins.attrNames&lt;/code&gt; returns
keys sorted lexicographically. You cannot control attribute order in the
output.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;One-level flattening only.&lt;/strong&gt;
&lt;code&gt;[ &quot;ul&quot; (map ...) ]&lt;/code&gt; works because &lt;code&gt;map&lt;/code&gt; returns a
list that gets flattened. Deeper nesting like
&lt;code&gt;[ &quot;ul&quot; [ [ [ &quot;li&quot; &quot;x&quot; ] ] ] ]&lt;/code&gt; won&apos;t flatten further, you&apos;ll
get nested elements, not flattened children.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Eager evaluation.&lt;/strong&gt; The entire tree is evaluated
before rendering. For the static site generation use case, this is fine.
If you&apos;re generating gigabytes of HTML, this isn&apos;t your tool.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No streaming.&lt;/strong&gt; Output is a single string. Again,
fine for static sites; not designed for chunked HTTP responses.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;why-hiccup&quot;&gt;Why Hiccup?&lt;/h2&gt;
&lt;p&gt;The Hiccup format originated in Clojure and has been battle-tested
for over a decade. It maps naturally to Nix because both languages treat
data structures as first-class citizens. The syntax is minimal, just
lists and attribute sets, and composes with existing Nix idioms without
friction.&lt;/p&gt;
&lt;p&gt;The name &quot;niccup&quot; is a portmanteau: &lt;strong&gt;Ni&lt;/strong&gt;x +
Hic&lt;strong&gt;cup&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;source&quot;&gt;Source&lt;/h2&gt;
&lt;p&gt;The entire implementation is ~120 lines of pure Nix with no external
dependencies. The code, tests, and additional examples are available
at:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a
href=&quot;https://github.com/embedding-shapes/niccup&quot;&gt;github.com/embedding-shapes/niccup&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;MIT licensed.&lt;/p&gt;
</description></item></channel></rss>