# How to Create Custom Visual Presets in Archify: A Step-by-Step Guide

> Learn to create custom visual presets in Archify with this step-by-step guide. Extend the JSON schema, add CSS rules, and expose your preset in the UI picker.

- Repository: [tt-a1i/archify](https://github.com/tt-a1i/archify)
- Tags: how-to-guide
- Published: 2026-08-10

---

**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`](https://github.com/tt-a1i/archify/blob/main/archify/schemas/architecture.schema.json) at line 20 [[source]](archify/schemas/architecture.schema.json#L20-L21). 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]](archify/test/preset-tryon.test.mjs#L40-L61).

## 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`](https://github.com/tt-a1i/archify/blob/main/archify/schemas/architecture.schema.json):

```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`](https://github.com/tt-a1i/archify/blob/main/experiments/visual-evolution/prototype.html) demonstrate this pattern on lines 129-170 [[source]](experiments/visual-evolution/prototype.html#L129-L170).

Example CSS for a custom preset:

```html
<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:

```javascript
// 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:

```javascript
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]](archify/test/preset-tryon.test.mjs#L40-L49):
- `data-preset-value`: the raw preset string
- `role="menuitemradio"`: for accessibility and test targeting

### Handle Selection Changes

The click handler must synchronize `data-preset` on both `<html>` and `<svg>`:

```javascript
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]](archify/test/preset-tryon.test.mjs#L58-L61).

## Using Your Custom Visual Preset

Once the three steps are complete, reference your preset in any diagram JSON:

```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:
1. Validate `visual_preset` against the extended schema
2. Set `data-preset="my-preset"` on `<html>` and `<svg>`
3. Apply your scoped CSS without touching layout coordinates

## Key Files for Custom Visual Presets

| File | Purpose |
|------|---------|
| [`archify/schemas/architecture.schema.json`](https://github.com/tt-a1i/archify/blob/main/archify/schemas/architecture.schema.json) | Defines valid `visual_preset` enum values (line 20) |
| [`experiments/visual-evolution/prototype.html`](https://github.com/tt-a1i/archify/blob/main/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-preset` attribute must be set on **both `<html>` and `<svg>`** elements for consistent rendering.
- Reference [`experiments/visual-evolution/prototype.html`](https://github.com/tt-a1i/archify/blob/main/experiments/visual-evolution/prototype.html) for CSS patterns and `archify/test/preset-tryon.test.mjs` for 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`](https://github.com/tt-a1i/archify/blob/main/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.