How to Create Custom Visual Presets in Archify: A Step-by-Step Guide
Extend the JSON schema, add scoped CSS rules, and expose the new preset in the UI picker to create custom visual presets in Archify.
Archify uses visual presets to control the look and feel of rendered architecture diagrams without affecting their underlying geometry. The meta.visual_preset field in your diagram JSON selects from built-in themes like classic, signal-flow, blueprint, and editorial. This guide shows you how to create custom visual presets by extending the schema, adding CSS, and updating the UI—based on the actual implementation in tt-a1i/archify.
How Archify Visual Presets Work
The preset system is client-side only. When a diagram loads, the renderer sets a data-preset attribute on both the root <html> element and the <svg> container. CSS rules scoped to [data-preset="..."] then apply the visual styling.
As implemented in archify/renderers/architecture/render-architecture.mjs, the renderer never recomputes layout based on the preset. This guarantees stable semantic IDs and geometry regardless of which visual preset is active.
The preset value is validated against an enum defined in archify/schemas/architecture.schema.json at line 20 [source]. The UI picker enumerates available presets and synchronizes the data-preset attribute across elements, as confirmed by the test suite in archify/test/preset-tryon.test.mjs lines 40-61 [source].
Step 1: Extend the Schema
First, add your custom preset name to the allowed enum values so JSON validation passes.
Edit archify/schemas/architecture.schema.json:
{
"visual_preset": {
"enum": ["classic", "signal-flow", "blueprint", "editorial", "my-preset"]
}
}
- The enum is located on line 20 of the schema file.
- Choose a lowercase, hyphenated name following the existing convention.
- Validation fails if the preset value is not in this list.
Step 2: Add CSS Rules for Your Preset
Create a scoped CSS block that targets [data-preset="your-preset-name"]. The existing presets in experiments/visual-evolution/prototype.html demonstrate this pattern on lines 129-170 [source].
Example CSS for a custom preset:
<style>
/* Dark mode variables for my-preset */
html[data-preset="my-preset"][data-theme="dark"] {
--bg-body: #1a1a2e;
--fg-primary: #e0e0ff;
--accent: #ff6f61;
}
/* Light mode variables */
html[data-preset="my-preset"][data-theme="light"] {
--bg-body: #f8f9fa;
--fg-primary: #212529;
--accent: #e85d04;
}
/* Component styling */
html[data-preset="my-preset"] .header {
padding-right: 2rem;
}
html[data-preset="my-preset"] .card {
background: var(--bg-body);
color: var(--fg-primary);
border: 1px solid var(--accent);
}
/* Decorative overlay */
html[data-preset="my-preset"] .diagram-container::before {
content: "";
position: absolute;
inset: 0;
background: radial-gradient(circle at 20% 20%, var(--accent) 0%, transparent 70%);
pointer-events: none;
}
</style>
Key CSS patterns to follow:
- Scope all rules under
html[data-preset="..."]to avoid leaking styles. - Support both light and dark themes using
[data-theme="light|dark"]qualifiers. - Use CSS custom properties for colors to enable easy theme variants.
- Style
.card,.header,.diagram-container, and toolbar elements as needed.
Step 3: Expose the Preset in the UI Picker
The preset picker is a menu-button that appears in every rendered Archify page. You must add your preset to three places: the runtime list, the DOM menu, and the click handler.
Update the Runtime Preset List
In archify/renderers/shared/utils.mjs (or your renderer's UI helper), extend the preset array:
// archify/renderers/shared/utils.mjs
const PRESET_VALUES = ['classic', 'signal-flow', 'blueprint', 'editorial', 'my-preset'];
// Expose to global Archify object for runtime access
globalThis.Archify = globalThis.Archify || {};
Archify.presets = PRESET_VALUES;
Generate the Menu Items
Build picker buttons with the required attributes:
function buildPresetMenu() {
const menu = document.getElementById('preset-menu');
PRESET_VALUES.forEach(val => {
const item = document.createElement('button');
item.dataset.presetValue = val;
item.setAttribute('role', 'menuitemradio');
item.setAttribute('aria-checked', 'false');
item.textContent = val.charAt(0).toUpperCase() + val.slice(1).replace(/-/g, ' ');
menu.appendChild(item);
});
}
Required attributes per the test suite [source]:
data-preset-value: the raw preset stringrole="menuitemradio": for accessibility and test targeting
Handle Selection Changes
The click handler must synchronize data-preset on both <html> and <svg>:
function handlePresetSelect(presetName) {
const html = document.documentElement;
const svg = document.querySelector('svg.archify-diagram');
html.setAttribute('data-preset', presetName);
if (svg) svg.setAttribute('data-preset', presetName);
// Update aria-checked for accessibility
document.querySelectorAll('[role="menuitemradio"]').forEach(btn => {
btn.setAttribute('aria-checked', btn.dataset.presetValue === presetName);
});
}
This dual-element update is required—the test suite asserts both elements receive the attribute [source].
Using Your Custom Visual Preset
Once the three steps are complete, reference your preset in any diagram JSON:
{
"schema_version": 1,
"diagram_type": "architecture",
"meta": {
"title": "Production Microservices",
"visual_preset": "my-preset",
"animation": "none"
},
"components": [
{ "id": "api-gateway", "type": "service", "name": "API Gateway" }
],
"connections": [
{ "from": "client", "to": "api-gateway" }
]
}
The renderer will:
- Validate
visual_presetagainst the extended schema - Set
data-preset="my-preset"on<html>and<svg> - Apply your scoped CSS without touching layout coordinates
Key Files for Custom Visual Presets
| File | Purpose |
|---|---|
archify/schemas/architecture.schema.json |
Defines valid visual_preset enum values (line 20) |
experiments/visual-evolution/prototype.html |
Reference implementation of preset CSS scoping (lines 129-170) |
archify/test/preset-tryon.test.mjs |
Test coverage for picker UI and attribute synchronization |
archify/renderers/shared/utils.mjs |
UI helper for preset menu generation |
archify/renderers/architecture/render-architecture.mjs |
Main renderer that consumes presets without layout changes |
Summary
- Custom visual presets in Archify require three coordinated changes: schema extension, CSS scoping, and UI picker updates.
- The preset system is purely presentational—geometry and semantic IDs remain stable across all themes.
- All rules must scope under
[data-preset="..."]selectors to isolate styles. - The
data-presetattribute must be set on both<html>and<svg>elements for consistent rendering. - Reference
experiments/visual-evolution/prototype.htmlfor CSS patterns andarchify/test/preset-tryon.test.mjsfor UI behavior expectations.
Frequently Asked Questions
Can I override a built-in preset instead of creating a new one?
No—Archify does not support preset overriding. The CSS cascade uses attribute selectors that match exact preset names. To modify a built-in look, create a new preset with a unique name and copy the base styles from experiments/visual-evolution/prototype.html as your starting point.
Do custom visual presets affect diagram export formats?
Exported SVGs preserve the data-preset attribute, so CSS-based styling remains active when the file is viewed in a browser. However, raster exports (PNG, PDF) render with whatever styles are computed at export time—ensure your export pipeline loads the same CSS files.
Can I animate transitions between visual presets?
Yes. Since presets toggle a data attribute on the same DOM elements, you can add CSS transitions on properties like background-color, border-color, and opacity. Add transition: all 0.3s ease to your scoped rules. The renderer does not interfere with CSS animations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →