#svg #html #xml #tag #markup


Write SVG / HTML / XML programmatically

20 releases (2 stable)

1.0.1 Apr 13, 2021
0.7.1 Mar 8, 2021
0.6.2 Feb 26, 2021
0.5.0 Feb 26, 2021
0.1.0 Feb 17, 2021

#24 in Template engine

Download history 44/week @ 2021-02-13 175/week @ 2021-02-20 57/week @ 2021-02-27 66/week @ 2021-03-06 10/week @ 2021-03-13 40/week @ 2021-03-20 47/week @ 2021-03-27 42/week @ 2021-04-03 88/week @ 2021-04-10 71/week @ 2021-04-17 10/week @ 2021-04-24 28/week @ 2021-05-01

165 downloads per month
Used in poloto

MIT license

376 lines


Build xml / html / svg programatically using element building blocks. Instead of using a templating engine, write data/markup that 'looks like' rust.

Find it on github and crates.io.


Tagger aims to be memory efficient. Instead of constructing a nested structure of tags in memory and then have it be written out, Tagger will write out the elements to a fmt::Write object on the fly.

Tagger aims to guarantee correct writing of elements at compile time. At compile time, Tagger ensures that every tag has zero or more attributes followed by symbol to complete that tag, and that every tag that needs an ending tag, has one. This is achieved by nesting closures. That said this isn't 100% true. The user is allowed to write arbitrary data inside of any element, so it is possible that the user might insert tags that disrupt this guarantee. However, assuming the user doesn't manually write their own <tags> then there is this guarantee.

Tagger also provides functionality to build svg paths and polyline attribute data.

Sometimes, having to deal with all the borrowing and closures is difficult. For these cases a traditional ElementStack can be used. This allows you to move around the stack between functions and classes easily. The downside is that you lose compile time assurance that every push matches every pop.


use tagger::prelude::*;
fn main() -> core::fmt::Result {
    let width = 100.0;
    let height = 100.0;

    let mut root = tagger::Element::new(tagger::upgrade(std::io::stdout()));

    root.elem("svg", |header| {
        let (svg, ()) = header.write(|b| {
            b.attr("xmlns", "http://www.w3.org/2000/svg")?
                .with_attr("viewBox", wr!("0 0 {} {}", width, height))?

        svg.single("rect", |w| {
            w.attr("x1", 0)?
                .attr("y1", 0)?
                .attr("rx", 20)?
                .attr("ry", 20)?
                .attr("width", width)?
                .attr("height", height)?
                .attr("style", "fill:blue")?

        //Add styling for test class.
        svg.elem_no_attr("style", |style| {
            write_ret!(style, "{}", ".test{fill:none;stroke:white;stroke-width:3}")?.empty_ok()

        //Draw some circles
        svg.elem("g", |header| {
            let (g, ()) = header.write(|w| w.attr("class", "test")?.empty_ok())?;
            for r in (0..50).step_by(10) {
                g.single("circle", |w| {
                    w.attr("cx", 50.0)?
                        .attr("cy", 50.0)?
                        .attr("r", r)?





No runtime deps