Designing a logo is usually treated as a one-off, visual task.
Designing a logo system—with variants, themes, sizes, and guarantees of consistency—is a very different problem.
In this article I’ll show how I ended up generating real SVG logo files, fully parametric, using Pug, embedded and subsetted fonts, and a CLI-driven pipeline.
This approach has been used to create a new SmarkForm logo, which will replace the current one starting from upcoming version 0.13.0.
The logo is not just an asset anymore: it is generated, reproducible, and version-controlled.
SmarkForm is a free and open-source toolkit for declarative, markup-driven form generation — from simple inputs to complex nested forms and dynamic lists, with first-class JSON import/export. It is framework-agnostic and styling-agnostic by design, and that same philosophy now applies to its branding.
The Goal
I wanted:
- A single logo definition
-
Variants for:
- light / dark
- monochrome
- compact / full
Fonts embedded and subsetted
Output as real
.svgfilesGenerated via CLI, not a browser
Reproducible and scriptable
In short: branding as code.
Why Pug?
Pug gives you three things that are surprisingly powerful for SVG work:
- Logic (conditions, defaults, variants)
- Parametrization (options passed from CLI)
- Readable structure for complex SVG trees
SVG is XML.
Pug is extremely good at generating structured XML.
That combination is criminally underrated.
Turning SVG into a First-Class Template
The key decision was this:
Don’t generate HTML that contains SVG.
Generate SVG directly so that it can be linked everywhere.
That means:
-
doctype svginstead of (default in Pug)doctype html - The root node is
<svg> - Follow svg specs
A Pug mixin as the logo engine
Implementing the svg as a Pug mixin let me actually start with an HTML document showcasing different parameters combinations.
Even colors and fonts were originally parametyzed allowing to examine several configurations side by side until the winner was choosen.
At the end of the process only functional parameters have been kept.
mixin smarkformLogo(options = {})
-
const {
mode = 'light',
compact = false,
monochrome = false,
size = 100
} = options;
const height = size;
const width = compact
? Math.round(height * 1.105)
: Math.round(height * 4.1);
svg(
xmlns="http://www.w3.org/2000/svg"
width=width
height=height
viewBox=`0 0 ${width} ${height}`
role="img"
aria-label="SmarkForm logo"
)
// SVG content here
At this point we already have something valuable:
- Dimensions are computed
- Variants are driven by data
- The SVG is no longer static
Choosing and Subsetting Fonts
External fonts are fragile:
- Network-dependent
- Inconsistent
- Sometimes forbidden in branding assets
So the decision was to embed the font directly in the SVG.
Picking a font with the right license
For SmarkForm I used Work Sans, available from
👉 https://fonts.google.com
Google Fonts is particularly useful because you can filter by:
- Open-source licenses (SIL Open Font License, Apache 2.0, etc.)
- Font weights
- Variable fonts
This makes it easy to ensure your branding assets are legally safe to embed and redistribute.
Subsetting the font
Instead of embedding a full font file, I generated subsetted WOFF2 files containing only the glyphs actually used in the logo.
This keeps the SVG:
- Smaller
- Faster to load
- More intentional
Tools like pyftsubset (from fonttools) are perfect for this step.
Examnple:
pyftsubset static/WorkSans-Regular.ttf \
--text="<SmarkForm}" \
--flavor=woff2 \
--output-file=WorkSans-SmarkForm-Regular.woff2
In my case, for stylistic reasons, I used two different faces of the font so I had to do this twice (once for WorkSans-Regular and one for WorkSans-SemiBold).
You can stick with one or pick for even different fonts. I reckon keeping things simpler is better for a logo though.
Embedding Fonts via Base64
Once you have your subsetted .woff2, embedding it is straightforward.
From the shell:
base64 < WorkSans-SmarkForm-Regular.woff2
Then inline it into the template.
A small stylistic trick I used:
const WorkSans_regular_b64 = (`
base64 < WorkSans-SmarkForm-Regular.woff2
`).replace(/\s+/g, '');
Notes:
- Declaring binary (base64) data in a variable at the beginning keeps the actual code cleaner afterwards.
- The backticks sit outside the base64 block, so alignment stays clean.
- Newlines and spaces are stripped afterwards (avoiding css issues).
- Assuming the file is in the current directory, in Vim you can simply type
:vip!bashto insert in place the file contents encoded in base64.
A subtle but important detail
Base64 itself allows arbitrary whitespace and newlines.
CSS does not.
If you forget to strip whitespace, your font may silently fail to load.
This is one of those details that’s obvious after you hit it once.
Wiring Fonts to SVG Text via CSS
Fonts are referenced via CSS inside <defs>:
defs
style.
@font-face {
font-family: 'WS-Regular';
src: url('data:font/woff2;base64,#{WorkSans_regular_b64}') format('woff2');
}
@font-face {
font-family: 'WS-SemiBold';
src: url('data:font/woff2;base64,#{WorkSans_SemiBold_b64}') format('woff2');
}
text.regular {
font-family: 'WS-Regular';
dominant-baseline: middle;
}
text.semibold {
font-family: 'WS-SemiBold';
dominant-baseline: middle;
}
The .regular / .semibold classes don’t mean anything special by themselves —
they’re simply a clean way to bind specific font weights and behaviors to specific parts of the logo.
Rendering Text Safely in SVG
One small but important gotcha when generating SVG with Pug:
text.regular(
x=lmargin
y=height / 2
font-size=height * 0.6
fill=primaryColor
)='<'
The < must be passed as a string.
Otherwise it’s interpreted as markup and breaks the SVG.
Once you do this explicitly, Pug behaves exactly as expected
by replacing them with their correspondent HTML entities.
Compact vs Full Variants (Same Source)
With logic in place, variants are trivial:
if compact
text.semibold(...) 'S'
text.semibold(...) '}'
else
text.regular(...) '<'
text.semibold(...) 'Smark'
text.semibold(...) 'Form'
text.semibold(...) '}'
No duplication.
No parallel files.
One definition, many outcomes.
Generating Real SVG Files from the CLI
This is the payoff.
Once the template outputs only SVG, you can do this:
npx pug-cli \
-O '{ compact: true, mode: "dark" }' \
< smarkform_logo.pug \
> smarkform_compact_dark.svg
And that file is:
- A valid SVG
- Fully self-contained
- Embeddable anywhere
- Diff-friendly
- Reproducible
You can script all variants in seconds.
Integration into the SmarkForm project
Integrating the new logo into the SmarkForm codebase turned out to be refreshingly simple.
The existing assets already lived under /docs/assets, so I introduced a new logo directory for better organization, with a src subdirectory to hold the source files.
The Pug template that generates the logo variants now lives in /docs/assets/logo/src, alongside a small Bash script responsible for rendering the desired SVG outputs. The script is intentionally minimal and largely self-documenting, making it easy to adjust or extend if new variants are needed in the future.
Importantly, this script is not part of the build process. The generated SVG files are stable, self-contained assets and remain valid until the logo source itself changes. In practice, this means the script only needs to be executed when the Pug template is modified—keeping the build pipeline clean and avoiding unnecessary regeneration work.
You can find both the generator template and the accompanying script in the aforementioned /docs/assets/logo/src directory in the SmarkForm GitHub repository.
Why This Scales
This approach scales because:
- Logos become data-driven
- Branding lives inside the repository
- Changes are auditable
- Variants never drift out of sync
Just as importantly, it respects SmarkForm’s philosophy:
SmarkForm is markup-driven and styling-agnostic.
The branding system does not impose a visual identity on user forms.
Instead, it provides optional, lightweight visual cues — icons or small banners — so end users may recognize a form as being powered by SmarkForm and know what kind of experience to expect.
Developers and designers remain fully in control.
Final Thoughts
This started as “I just want a logo SVG”.
It ended as:
- A parametric branding system
- A CLI-driven asset pipeline
- A single source of truth
If you already treat UI as code,
there’s no good reason your branding assets shouldn’t be treated the same way.
Once you do it like this, going back feels… unnecessary 😉


Top comments (0)