embedding-shapes/niccup
{ "createdAt": "2025-12-03T14:27:13Z", "defaultBranch": "master", "description": "Hiccup-style HTML generation for Nix. Describe HTML as Nix lists/attrsets, render to strings.", "fullName": "embedding-shapes/niccup", "homepage": "https://embedding-shapes.github.io/niccup/", "language": "Nix", "name": "niccup", "pushedAt": "2025-12-04T01:56:25Z", "stargazersCount": 2, "topics": [ "html", "html-generator", "library", "nix", "nix-flake", "nixos", "nixpkgs", "static-site-generator", "website" ], "updatedAt": "2025-12-04T01:56:29Z", "url": "https://github.com/embedding-shapes/niccup"}niccup
Section titled “niccup”Hiccup-style HTML generation for Nix. Describe HTML as Nix lists/attrsets, render to strings. Heavily inspired by hiccup (Fast library for rendering HTML in Clojure) by @weavejester
View on GitHub | Website/Docs | Introduction Blog Post
Installation
Section titled “Installation”Flakes:
inputs.niccup.url = "github:embedding-shapes/niccup";# use inputs.niccup.libNon-flakes:
let niccup = builtins.fetchTarball "https://github.com/embedding-shapes/niccup/archive/master.tar.gz"; h = import (niccup + "/src/lib.nix") { };in h.render [ "p" "Hello" ]Security Note
Section titled “Security Note”This library assumes trusted inputs. Like server-side templates, the expressions should be developer-authored code, not user-generated content. Do not pass untrusted input directly to raw or comment, or do so at your own risk. Don’t say you weren’t warned tho.
Examples
Section titled “Examples”let h = inputs.niccup.lib; inh.render [ "div#main.container" { lang = "en"; class = [ "app" "dark" ]; } [ "h1" "Hello from Nix" ] [ "p" "Hiccup-style HTML in Nix." ] (h.comment "List example") [ "ul" (map (x: [ "li.item" x ]) [ "one" "two" "three" ]) ]]Write to file (use nixpkgs writeText):
{ pkgs, inputs, ... }:pkgs.writeText "index.html" (inputs.niccup.lib.render [ "p" "Hello" ])Some more involved examples:
- [art]!(examples/art/build.nix) - Generative SVG (Sierpinski triangle)
- [blog]!(examples/blog/build.nix) - Multi-page blog with navigation
- [docs]!(examples/docs/build.nix) - NixOS module documentation generator
- [quine]!(examples/quine/build.nix) - Self-rendering page
The website for niccup is generated dynamically with niccup too, the whole source is ~120 lines of Nix as well
Data Model
Section titled “Data Model”Element: [ tag-spec attrs? children... ]
Tag spec: string with optional CSS shorthand: "div#id.class1.class2". ID must precede classes.
Attributes (optional attrset, second position):
- Merged with shorthand; classes combine (shorthand first), ID from map overwrites shorthand.
class: string or list of strings.- Boolean
truerenders asattr="attr";false/nullomits the attribute.
Children:
- Strings/numbers: escaped, numbers via
toString. - Elements: nested
[ tag ... ]lists. - Lists: flattened one level (enables
mappatterns). rawnodes: unescaped HTML.commentnodes:<!-- ... -->.
Void tags (img, br, hr, input, meta, link, area, base, col, embed, source, track, wbr): no closing tag.
render : expr -> string- Render to minified HTML string.renderPretty : expr -> string- Render to indented, human-readable HTML.raw : string -> node- Mark content as unescaped HTML.comment : string -> node- Emit HTML comment.
Exported as lib from the flake.
Development
Section titled “Development”just build # checks syntax, builds the library
just test # runs a bunch of tests
just build-website # builds the project website to result/
just all-examples # builds all of the examples, currently `blog`, `quine` and `art`
just example blog # builds only the `blog` exampleLicense
Section titled “License”MIT 2025 - @embedding-shapes