---
title: "Visual Editor"
description: "Compose pages visually — add sections, arrange blocks, bind data, and publish."
canonical_url: "https://www.nyxel.dev/docs/editor"
markdown_url: "https://www.nyxel.dev/docs/editor.md"
---

# Visual Editor
URL: /docs/editor
LLM index: /llms.txt
Description: Compose pages visually — add sections, arrange blocks, bind data, and publish.

# Visual Editor

<div class="nyxel-status-callout" data-status="built-with-limitations">
<p><strong>Built, with limitations</strong> The editor is functional and public-demoable. Publishing, durable hosted projects, and production permissions are not built yet.</p>
</div>

The Nyxel visual editor is the primary surface for composing storefront pages — the equivalent of Shopify's theme editor, but for your headless Shopify storefront built with SvelteKit.

The editor runs as its own app on a separate origin (an admin subdomain in production), the same way Sanity Studio is a standalone app rather than part of the site it edits. It connects to a storefront over HTTP — reading the component registry, theme config, and templates, and embedding the storefront's live preview in an iframe.

## What the editor does

The editor lets you compose pages from registered sections and blocks:

- **Composition tree** — the ordered tree of sections and blocks on the current template, with add, remove, duplicate, hide, rename, and reorder
- **Drag-and-drop** — reorder sections and blocks (and move blocks between containers that accept them) with grip-handle drag, drop indicators, and stable client IDs
- **Setting controls** — each schema field renders its corresponding editor control (text inputs, color pickers, image selectors, dropdowns)
- **Inspector** — hover the page preview to highlight the section or block under the cursor; click to jump straight to its settings
- **Live preview** — a real render of the current draft in an isolated iframe, updated as you edit
- **Save** — persists the active page template and any dirty group templates
- **Saved sections** — capture a composed section as a reusable preset, distinct from dev-authored sections
- **Dynamic sources** — bind compatible setting fields to Shopify data and add repeaters on block containers (see [Dynamic Sources](/docs/dynamic-sources))

Publishing is planned, but intentionally disabled while the draft/publish workflow is still being designed.

## Editor surfaces

The editor exposes three areas, switched from the top bar:

| Surface | What it is |
|---------|-----------|
| **Composer** | The page composition tree for the current template — add, remove, reorder, and nest sections and blocks. The Shopify-theme-editor equivalent. |
| **Components** | A Nyxel-only surface, in the spirit of Storybook. "Components" is the umbrella term for everything you can add — both **sections** (top-level) and **blocks** (children). The **Navigator** lists **SAVED** presets (with an expandable block tree per preset), then **SECTIONS** and **BLOCKS** grouped in collapsible categories — the same drawer layout used by the add-section and add-block pickers. Select a row to preview and configure it in isolation; hover a row for a quick peek popout. Section and saved-section previews use a dedicated iframe route with live draft sync and a resizable center frame. Block previews shrink-wrap to content without a device frame. Links in component previews are inert so you can click through the UI without navigating away. |
| **Configuration** | Global **schema-derived** theme configuration — colors and color schemes, typography, layout, buttons, cards, media, and inputs. Schema in `src/lib/config/settings_schema.json`; merchant overrides in `settings_data.json` (sparse `current` object, including `color_schemes` palettes). Theme identity (name, author) lives in the schema and drives the editor chrome. Settings groups expand inline in the **Navigator**. Color schemes use Horizon-style palettes — preview cards in the Navigator; **Edit** opens a dedicated scheme editor in the **Inspector** where you rename, remove, and tune each color field with the same pickers used elsewhere. Section- and block-level **Color scheme** pickers include **Inherit** (empty value) and resolve the active scheme from the parent section or group; **Edit** jumps to Configuration with that scheme open. Search filters groups and fields by label or id; hover the panel header to expand or collapse every group (or auto-expands matching groups while you search). |

Composer and Components use a **Navigator**, center preview, and **Inspector** panel. Configuration uses the Navigator and center preview by default; editing a color scheme also opens the Inspector (same stacked layout on narrow viewports). Use the **Preview layout** menu in the top bar to switch **Mobile**, **Desktop**, **Resizable** (Components sections and saved presets — drag the corner handle to set width), or **Full screen** (hides side panels; preview expands to full desktop width). Composer and Configuration both include a search field at the top of the Navigator. If you hide the panels, clicking **Composer** or **Configuration** in the area switcher brings them back.

## Components view

The Components view is one of the places Nyxel deliberately moves beyond a direct clone of the Shopify theme editor.

In Shopify themes, developers can ship multiple presets or variations of a section. Nyxel keeps that option, but also lets an author build a composed section from existing sections and blocks, save it, and reuse it later. It is closer to a mix of Storybook, Figma components, and the Shopify add-section drawer:

- browse sections and blocks without editing a page
- preview a component in isolation
- tune schema defaults
- expand saved components to inspect their block tree
- duplicate, rename, restore, and delete saved components
- instantiate a saved component into a page with fresh ids

This is useful because not every useful section variation needs to become new code. A strong set of primitives can create many reusable patterns.

Configuration also includes a global **Color mode** strategy (`System`, `Light`, `Dark`). Storefront and editor previews apply this mode through native CSS (`color-scheme` and `prefers-color-scheme`) while section/block `color_scheme` pickers continue to work the same way. By default, each scheme auto-mirrors for dark mode; advanced themes can set an explicit pair in `settings_data.json` via `current.color_schemes.<scheme-id>.dark`.

## View menu

The **View menu** at the top-left (Nyxel mark + current area label) is the unified area switcher. It lists every editor surface with keyboard hints:

| Area | What it opens |
|------|----------------|
| **Composer** | Page composition tree (Header / Template / Footer) |
| **Theme Settings** | Global theme configuration (colors, typography, layout) |
| **Components** | Theme and saved component browser |
| **Preferences** | Editor chrome personalization and project connection |

Each row shows its shortcut (for example **⌘ ⌃ 1** for Composer on Mac). The current area is disabled in the menu so you always know where you are.

## Preferences

**Preferences** is a dedicated editor view — not your storefront theme. The **Navigator** lists collapsible sections; the center column stays on the workspace canvas (no preview iframe).

- **Editor** — accent hue (OKLCH wheel with guardrailed steps), **Revert accent**, editor **Appearance** (Light / Dark / System), panel stacking (**Auto** vs **Always stacked**), and **Auto-fit inspector** when stacked. Accent and appearance persist across sessions; accent tint syncs into preview iframes.
- **Project** — read-only Storefront connection fields so the editor can show what shop data it is previewing.
- **Theme Information** — read-only theme name and author.
- **Keybinds** — category-grouped shortcut reference (same actions as **More → Keyboard shortcuts**).

The editor workspace uses a recessed `control-track` background behind the side panels and preview frame.

## Markets

The top bar includes a **market switcher** when you are in **Theme Settings** (Composer uses the navigator header for template switching instead). Markets come from the Shopify Storefront API localization list. Picking one applies `@inContext(country, language)` to storefront data requests so prices and localized content on supported routes match that market in the preview. **Store default** clears the override and falls back to optional env defaults (`PUBLIC_SHOPIFY_STOREFRONT_COUNTRY` / `LANGUAGE`).

## Top bar actions

In **Composer**, the **template navigator** lives in the **Navigator** panel header (the title row above the composition tree). The top bar context row shows an optional **market switcher** and a read-only **address bar** with the merchant path the preview is showing (for example `/products/example`). Following same-origin links inside the preview updates the address bar; the **Open in storefront** control next to it opens that path on your live storefront origin in a new tab.

In **Theme Settings**, the template navigator stays in the top bar next to the view menu.

Between the market/address context and the **More** menu, the top bar exposes preview tools:

- **Design Mode** — toggles click-to-inspect in the preview (hover highlights, click selects the node and opens its settings in the **Inspector**). Unavailable in **Preferences** and **Theme Settings**. Shortcut: **⌘ ⇧ I** (Mac) / **Ctrl Shift I** (Windows/Linux).
- **Agent** — opens an AI chat panel in the **Inspector** for page-building tasks (see [Agent](#agent) below). Requires Google Chrome 138+ with the built-in Language Model (Prompt API). The toggle is disabled with an explanatory tooltip on unsupported browsers. Shortcut: **⌘ .** (Mac) / **Ctrl .** (Windows/Linux).
- **Preferences** — opens the Preferences panel (editor color scheme, project info, keybinds). Shortcut: **⌘ ,** (Mac) / **Ctrl ,** (Windows/Linux).
- **Preview layout** — dropdown for **Mobile** (phone-width frame), **Desktop** (full workspace width), **Resizable** (Components area — section/saved previews with a draggable corner handle; switches automatically when you resize), and **Full screen** (hides **Navigator** and **Inspector**; preview at full desktop width). **Design Mode** and viewport presets are unavailable for block previews. Shortcuts: **⌘ ⇧ M** / **Ctrl Shift M** (mobile), **⌘ ⇧ D** / **Ctrl Shift D** (desktop), **⌘ ⇧ B** / **Ctrl Shift B** (full screen).

The **More** menu (⋯) on the right opens:

- **Preview** — the live storefront origin in a new tab
- **View documentation** — the public docs site in a new tab
- **Keyboard shortcuts** — opens a cheatsheet of supported editor shortcuts (`⌘/` or **More → Keyboard shortcuts**)

Theme name and author appear at the bottom of the More menu.

## Agent

**Agent** is an AI page-building assistant in the **Inspector** panel. It can read the current template, selection, and preview context, then make the same edits you would in Composer — add or remove sections and blocks, update settings, switch templates, and set which product or collection the preview shows.

### Requirements

Agent uses **Chrome's on-device Language Model** (the Prompt API). You need **Google Chrome 138+** with language models enabled for your profile. Edge, Opera, Firefox, and Safari are not supported yet. The **Agent** button in the top bar stays disabled until Nyxel confirms the API is available; hover the button for the reason.

A cloud-hosted assistant that does not depend on Chrome is planned for a later release.

### What you can ask

Examples that work today:

- "What is selected right now?"
- "List sections on this template"
- "Preview a different product on this template"
- "Take me to the home template"
- "Change the heading on the selected section" (when a node is selected)

The agent can use the same structured editor state as manual editing. Shopify resource search is disabled in the public demo to avoid exposing connected-store data.

### How it works

Each message starts by reading live editor state (template, selection, dirty flags, preview handles). Tool calls mutate the in-memory draft — the same draft layer as manual editing — so undo, dirty tracking, and preview sync behave normally.

Tool traces appear in the chat as compact JSON rows so you can see what the agent read or changed.

### URL state

Agent navigation respects shareable URL params (`view`, `template`, `node`, `product`, etc.). Preview resource handles in the URL (for example `?product=my-handle`) hydrate on load through the admin search API.

## The editing surface

### Composition tree

The Composer Navigator is split into three labeled regions — **Header**, **Template**, and **Footer** — matching how Shopify themes separate section groups from the page body. Each region has its own section list, drag-and-drop scope, and **Add section** picker (header/footer sections are filtered by `enabled_on.groups`; the template region also lists saved sections).

Within each region, sections appear in order with child blocks nested beneath their parents. You can:

- **Add** — "Add section" opens the component picker scoped to sections (including your saved sections); "Add block" under an expanded container opens it scoped to the blocks that container accepts. Context menus on a selected row offer **Add before** and **Add after** — each opens the same scoped component browser so you pick the section or block type to insert (saved sections included for top-level siblings).
- **Remove / duplicate / hide / rename** — from the settings panel's actions menu; the eye toggle on each row hides a node without deleting it. **Rename** focuses the name field in the settings panel and selects the current text for quick editing.
- **Reorder** — drag rows by the grip handle; blocks can move into any container whose schema accepts their type, including empty containers (drop onto the "Add block" zone)
- **Configure** — click a row to open its settings panel
- **Search** — filter sections and blocks by name; results list matching nodes with section/block icons and select the node when you pick one

**Repeaters** appear as locked rows with a repeat icon. The body block nests beneath the repeater row; you configure it once and the preview renders one instance per iterable entry. Repeater rows cannot be reordered or duplicated independently — change the iterable source or body block instead.

The tree is the page JSON. Every change marks the draft dirty; **Save** writes the current template state in a deterministic shape.

### Design Mode (Inspector)

**Design Mode** in the top bar (on by default in Composer and Components) connects the preview to the tree — the Shopify theme editor pattern:

- **Hover** any section or block in the page preview to see a labeled highlight outline
- **Click** to select that node — the **Inspector** opens on its controls
- Repeater instances share one body block; Design Mode passes repeater metadata so highlights and selection target the correct iteration
- Selecting a row in the **Navigator** highlights and scrolls to the node in the preview

### Settings panel

When you select a section or block, its settings panel opens. Each setting declared in the schema renders with the appropriate control. Controls ship incrementally — stubs still round-trip values so nothing is lost.

| Schema type | Editor control | Status |
|-------------|---------------|--------|
| `header` | Section label | Shipped |
| `paragraph` | Help text | Shipped |
| `text` | Text input | Shipped |
| `textarea` | Multi-line textarea | Shipped |
| `number` | Numeric input (min/max clamped, tabular figures) | Shipped |
| `checkbox` | Toggle switch | Shipped |
| `range` | Slider + editable numeric readout | Shipped |
| `radio` / `select` | Segmented control (≤3 options) or dropdown | Shipped |
| `url` / `video_url` | URL input | Shipped |
| `text_alignment` | Segmented control with alignment icons | Shipped |
| `color` / `color_background` | Color picker | Shipped |
| `color_scheme` | Scheme swatches with **Inherit** and **Edit** (resolves inherited scheme in picker) | Shipped |
| `font_picker` | Curated font dropdown with live preview | Shipped |
| `icon` | Iconify searchable grid picker | Shipped — requires `@iconify/svelte` in your storefront |
| `richtext` / `inline_richtext` | TipTap rich-text editor | Shipped |
| `html` | CodeMirror HTML editor | Shipped |
| `css` | CodeMirror CSS editor | Shipped |
| `image_picker` / `video` | Media picker with Shopify Files search | Shipped when Shopify Admin access is connected |
| `product` / `collection` / `blog` / `article` / `page` / `*_list` / `link_list` / `metaobject` | Admin API resource picker with search | Shipped when Shopify Admin access is connected |

#### Conditional visibility (`visible_if`)

Any setting can declare `visible_if: { "setting_id": "other_setting", "value": "some_value" }` to show or hide controls in the Inspector when another setting equals a specific value. Hidden settings keep their stored values — toggling visibility back restores them. Example: icon size and color controls appear only when **Show icon** is enabled.

#### Capability gating

Some setting types depend on a package your storefront must provide (for example, `icon` renders with `@iconify/svelte`). At build time the registry compiler checks your storefront's `package.json` and exposes a `capabilities` map. If a dependency is missing, the editor shows an install notice instead of a broken control.

#### Saving schema defaults (Components area)

In the **Components** area you preview a component in isolation with its schema defaults. After tweaking values, the actions menu offers **Save as defaults** — Nyxel writes the current values back into the `default` fields of the component's `<script section lang="json">` block in its source `.svelte` file. This is the read-write loop between the editor and your code: the schema stays the single source of truth, and the editor can update it.

#### Icon picker

The `icon` setting type stores an Iconify reference (`lucide:star`). It exists because themes often need icon choices without hard-coding every SVG into the theme. Schema fields:

- `pack` — Iconify set prefix (defaults to `lucide`)
- `icons` — optional allowlist of icon names within the pack

The picker fetches icons from the Iconify API, supports search, and auto-selects the first available icon when no default is set.

### Binding picker

Fields that support dynamic sources show a dynamic-source button (the link icon). When a binding is active, the picker replaces the static value input:

1. You choose a source group (brand, current page context, product fields, and related entries)
2. The picker filters to **type-compatible** sources — a `text` field only shows sources that resolve to text
3. You select the specific path (e.g., `closest:product.title`, `shop.brand.slogan`)
4. A breadcrumb shows the binding path in the settings panel

Bound settings display differently from static values — with a source path breadcrumb, an **Edit value** button, and a **Remove dynamic source** action.

Block containers that accept children also expose a **Repeater source** dropdown. Pick an iterable (collection products, product variants, product images, blog articles, or collections) to add a repeater body row to the composition tree.

### Containers: Container and Stack

Two layout primitives make page building modular without writing new section code:

- **Container** — a generic top-level section (`container.svelte`) with layout (direction, alignment, gap), size (page/full width, height), and appearance (color scheme, padding, background media). It accepts **any** registered block. Whole pages can be composed from containers alone.
- **Stack** — a block-level flex stack (`stack.svelte`) — vertical or horizontal, alignment, gap, optional color scheme and background media. Stacks nest inside containers (or other stacks) to build columns and rows.

Both are reference implementations you can copy and constrain (e.g. restrict `blocks.accepted.types`).

**Insert presets are empty** — no demo text blocks on add. Reusable composed trees belong in **saved sections**, not default presets.

### Saved sections

Composed something worth reusing? Select the section and choose **Save section** from the actions menu. The captured tree (settings overrides + blocks) becomes a reusable saved section.

Saved sections appear in the add-section picker under a distinct **Saved components** group with a bookmark icon — clearly separate from dev-authored sections. In the **Components** area the same presets appear at the top of the browser; expanding a row reveals its block tree (Composer-style chevrons) and selecting a nested block syncs the preset preview iframe. Context menus support rename, duplicate, restore defaults, and delete. Adding a preset to a page instantiates a deep copy with fresh IDs. They reference registry types only; a preset never defines new components.

## Preview

The preview panel renders the **draft** — the current page document rendered through the same `<Sections>` container (from `@nyxel/sdk/svelte`) as production. As you edit:

- Setting changes update the preview instantly
- Reordering sections rearranges the preview in real time
- Hidden nodes drop out of the render

The preview iframe receives the current draft over `postMessage`. The renderer stamps each node with a stable editor id, which is what the inspector hit-tests against. When you customize editor accent hue in Settings, that tint syncs into the preview iframe as well.

## Templates

The editor works on **templates** — Shopify-shaped page types, each backed by one JSON file in `src/lib/templates/` and a matching storefront route (home, product, collection, collection list, page, blog, blog post, cart, search, password, 404).

Use the **template navigator** — a Shopify-admin-style picker in the **Navigator** header while you are in **Composer**, or in the top bar in **Theme Settings** — to switch which template you edit:

- **Search** — filter across all template types and alternates in one flat list
- **Categories** — drill into Products, Collections, Pages, Blogs, or Blog posts to see the default template plus any alternates for that type
- **Direct items** — Home page and Collections list open without a category drill-in
- **Create template** — copy a type's default into a new alternate (for example `product.holiday.json` from the Products category)
- **System templates** — Cart, Search, Password, and 404 are editable like other page types (matching routes under `(theme)/`)
- **Coming soon** — Gift card remains disabled until that route ships

Each category's **default** template is the base JSON (`product.json`). **Alternate** templates use a dotted filename (`product.summer.json`) and render on the storefront when the matching route includes Shopify's `?view=summer` query param. Assigning alternates to individual Shopify resources is planned, not built.

Nyxel loads the active template, points the preview at the matching route pattern, and **Save** persists the active template state. Your selection persists across sessions.

Each template is one JSON document. The editor reads it on open, edits a draft in memory, and Save writes it back. The storefront discovers every JSON file under `src/lib/templates/` at build time — adding an alternate from the editor needs no route or import changes.

### Template keys

The **key** is the filename stem (`product.summer.json` → `product.summer`) and is mirrored in the document's `key` field. Page keys follow the route type (`product`, `page`, `index`, …) with an optional suffix after a dot for alternates. Header and footer shell groups use `header` / `footer` (and `header.{suffix}` / `footer.{suffix}` for alternates) — not `header-group` — so multiple shell groups can coexist and be assigned per route later. Saved sections use `section.{slug}`; saved blocks use `block.{slug}`. See [Project structure](/docs/project-structure) for the full table.

## Save vs publish

Two distinct actions:

| Action | What it does | Status |
|--------|-------------|--------|
| **Save** | Persists the current draft/template state | Shipped |
| **Publish** | Turns a saved draft into a live storefront change | Planned — see [Publishing](/docs/publishing) |

Undo/redo is session-local history over the active context — Composer tracks page/header/footer edits; Configuration tracks theme settings and color schemes; Components tracks saved-section preset trees and per-component schema default edits. Use **⌘Z** / **⌘⇧Z** (Mac) or **Ctrl+Z** / **Ctrl+Y** (Windows/Linux).

### Keyboard shortcuts

Shortcuts are disabled while focus is in an input, textarea, select, or contenteditable field (including the rich-text editor). Open the full cheatsheet with **⌘ /** (Mac) or **Ctrl /** (Windows/Linux), or from **More → Keyboard shortcuts**.

| Category | Action | Mac | Windows / Linux |
|----------|--------|-----|-----------------|
| General | Undo | ⌘ Z | Ctrl Z |
| General | Redo | ⌘ ⇧ Z | Ctrl Y |
| General | Save | ⌘ S | Ctrl S |
| Tools | Agent | ⌘ . | Ctrl . |
| Tools | Select | ⌘ ⇧ I | Ctrl Shift I |
| Tools | Preferences | ⌘ , | Ctrl , |
| Tools | Full screen | ⌘ ⇧ B | Ctrl Shift B |
| Tools | Mobile preview | ⌘ ⇧ M | Ctrl Shift M |
| Tools | Desktop preview | ⌘ ⇧ D | Ctrl Shift D |
| Navigation | Composer | ⌘ ⌃ 1 | Ctrl Alt 1 |
| Navigation | Theme Settings | ⌘ ⌃ 2 | Ctrl Alt 2 |
| Navigation | Components | ⌘ ⌃ 3 | Ctrl Alt 3 |
| Sections & blocks | Hide & show | ⌘ ⇧ H | Ctrl Shift H |
| Sections & blocks | Remove | ⇧ ⌫ | Shift ⌫ |
| Sections & blocks | Select previous / next | ⇧ ↑ / ⇧ ↓ | Shift ↑ / Shift ↓ |
| Sections & blocks | Open selected element | ⇧ ↵ | Shift Enter |
| Sections & blocks | Expand / collapse all sections | ⌘ ⇧ O / ⌘ ⇧ P | Ctrl Shift O / Ctrl Shift P |

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
Docs-scoped sitemap: [/docs/sitemap.md](/docs/sitemap.md).
Well-known sitemap: [/.well-known/sitemap.md](/.well-known/sitemap.md).
