> For the complete documentation index, see [llms.txt](https://docs.ecwid.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ecwid.com/site-themes/develop-site-themes/dev-recommendations/editor-ux.md).

# Editor UX

The Instant Site Editor is the main interface merchants use to customize a theme.&#x20;

Choose and structure settings in a way for each section to work immediately, use clear merchant-facing labels, and only expose choices that the storefront can render well.

### Use Translation Keys for Merchant-Facing Strings

Use `$label.*` keys for every `label`, `placeholder`, `description`, option label, default text value, and editor help string. Do not hardcode English text in `content.ts` or `design.ts`.

This keeps editor text localizable and consistent with the rest of the theme. Define the strings in `settings/translations.ts`.

```typescript
// settings/content.ts
import { content } from '@lightspeed/crane-api';

export default {
  headline: content.inputbox({
    label: '$label.content.headline',
    placeholder: '$label.content.headline_placeholder',
    defaults: {
      text: '$label.defaults.headline',
    },
  }),
  layout: content.selectbox({
    label: '$label.content.layout',
    placeholder: '$label.content.layout_placeholder',
    description: '$label.content.layout_description',
    options: [
      { value: 'image_left', label: '$label.options.image_left' },
      { value: 'image_right', label: '$label.options.image_right' },
    ],
    defaults: {
      value: 'image_left',
    },
  }),
};
```

```typescript
// settings/translations.ts
import { translation } from '@lightspeed/crane-api';

export default translation.init({
  en: {
    '$label.content.headline': 'Headline',
    '$label.content.headline_placeholder': 'Add a short headline',
    '$label.content.layout': 'Layout',
    '$label.content.layout_placeholder': 'Choose a layout',
    '$label.content.layout_description': 'Pick the layout that best fits this section.',
    '$label.defaults.headline': 'New arrivals',
    '$label.options.image_left': 'Image left',
    '$label.options.image_right': 'Image right',
  },
});
```

### Group Related Settings

Long flat settings lists are hard to scan. Use `content.divider()` and `design.divider()` to separate groups. Use `content.info()` and `design.info()` when a setting needs context the label cannot provide.

Keep the most common settings first. Put advanced or secondary styling in `design.accordion()` so merchants can ignore it until they need it.

```typescript
// settings/content.ts
import { content } from '@lightspeed/crane-api';

export default {
  copy_group: content.divider({
    label: '$label.content.copy_group',
  }),
  title: content.inputbox({
    label: '$label.content.title',
    placeholder: '$label.content.title_placeholder',
    defaults: {
      text: '$label.defaults.title',
    },
  }),
  media_group: content.divider({
    label: '$label.content.media_group',
  }),
  media_help: content.info({
    label: '$label.content.media_help',
    description: '$label.content.media_help_description',
  }),
  image: content.image({
    label: '$label.content.image',
  }),
};
```

```typescript
// settings/design.ts
import { design } from '@lightspeed/crane-api';

export default {
  text_style: design.text({
    label: '$label.design.text_style',
    hideVisibleToggle: true,
    defaults: {
      font: 'global.fontFamily.title',
      size: 'global.textSize.title',
      color: 'global.color.title',
      visible: true,
    },
  }),
  advanced_style: design.accordion({
    items: {
      card: {
        label: '$label.design.card_style',
        editors: {
          background: design.background({
            label: '$label.design.card_background',
            enableAutoColor: true,
            defaults: {
              style: 'COLOR',
              color: 'global.color.background',
            },
          }),
          border_color: design.colorPicker({
            label: '$label.design.border_color',
            enableAlphaColor: true,
            defaults: {
              color: '#0000001A',
            },
          }),
        },
      },
    },
  }),
};
```

### Constrain Choices to Supported Layouts

Use `content.selectbox()` or `design.selectbox()` when the section only supports a fixed set of values. Free-text settings are useful for content, but they create invalid states when the value controls layout, behavior, or visual variants.

Restrict design controls when the section cannot handle every editor option:

| Option              | Use when                                                       |
| ------------------- | -------------------------------------------------------------- |
| `enableAlphaColor`  | Transparent colors are supported and readable in the layout    |
| `enableAutoColor`   | The element can safely inherit global theme colors             |
| `hideVisibleToggle` | The element is required for the section to make sense          |
| `hideSize`          | The layout has one intended size or computes size responsively |

See the `COLOR_PICKER`, `TEXT`, `BUTTON`, and `BACKGROUND` editor docs for supported color options.

```typescript
// settings/design.ts
import { design } from '@lightspeed/crane-api';

export default {
  card_layout: design.selectbox({
    label: '$label.design.card_layout',
    description: '$label.design.card_layout_description',
    options: [
      { value: 'grid', label: '$label.options.grid' },
      { value: 'carousel', label: '$label.options.carousel' },
    ],
    defaults: {
      value: 'grid',
    },
  }),
  title_style: design.text({
    label: '$label.design.title_style',
    hideVisibleToggle: true,
    hideSize: true,
    enableAutoColor: true,
    defaults: {
      font: 'global.fontFamily.title',
      color: 'global.color.title',
      visible: true,
    },
  }),
};
```

### Provide Complete Defaults

Every section should look complete as soon as the merchant adds it. Set defaults for required copy, buttons, layout choices, and design values. A merchant should be able to publish a newly added section without filling empty placeholders first.

Use realistic sample content for text defaults. For design defaults, prefer global theme tokens such as `global.color.title`, `global.color.background`, `global.fontFamily.title`, and `global.textSize.body` when the section should follow the merchant's global style.

```typescript
// settings/content.ts
import { content } from '@lightspeed/crane-api';

export default {
  title: content.inputbox({
    label: '$label.content.title',
    placeholder: '$label.content.title_placeholder',
    defaults: {
      text: '$label.defaults.title',
    },
  }),
  cta: content.button({
    label: '$label.content.cta',
    defaults: {
      title: '$label.defaults.cta',
      buttonType: 'GO_TO_STORE',
    },
  }),
};
```

### Set Realistic Limits

Choose limits that the layout can render without wrapping poorly, clipping content, or slowing down the storefront. Do not set high caps just because the API allows them.

* Use `maxCards` for the number of cards the design supports.
* Use `maxProducts` for the number of products the grid, carousel, or list can display well.
* Use `maxCategories` for the number of category links or cards the layout can handle.

```typescript
// settings/content.ts
import { content } from '@lightspeed/crane-api';

export default {
  featured_products: content.productSelector({
    label: '$label.content.featured_products',
    maxProducts: 4,
  }),
  category_links: content.categorySelector({
    label: '$label.content.category_links',
    maxCategories: 6,
  }),
  testimonials: content.deck({
    label: '$label.content.testimonials',
    addButtonLabel: '$label.content.add_testimonial',
    maxCards: 3,
    cards: {
      defaultCardContent: {
        label: '$label.content.testimonial',
        settings: {
          quote: content.textarea({
            label: '$label.content.quote',
            placeholder: '$label.content.quote_placeholder',
          }),
        },
      },
    },
  }),
};
```

### Use the Right Source for Text in Components

Use content composables for merchant-editable text. Use `useTranslation()` only for static text that developers define and merchants do not edit.

This keeps editable content in the editor and avoids hardcoded strings in Vue components.

```vue
<script setup lang="ts">
import {
  useInputboxElementContent,
  useTranslation,
} from '@lightspeed/crane-api';

import type { Content } from './type';

const title = useInputboxElementContent<Content>('title');
const { t } = useTranslation();
</script>

<template>
  <section>
    <p class="eyebrow">{{ t('$label.shared.featured_collection') }}</p>
    <h2 v-if="title.hasContent">{{ title.value }}</h2>
  </section>
</template>
```

### Keep Header Keys Mandatory and Exact

Header sections have required editor keys. Define content `menu` and `logo`, and design `logo`, with those exact names. The build validates this and fails when the keys are missing or renamed.

See Headers for the full header rules.

```typescript
// headers/custom-header/settings/content.ts
import { content } from '@lightspeed/crane-api';

export default {
  menu: content.navigationMenu({
    label: '$label.content.menu',
  }),
  logo: content.logo({
    label: '$label.content.logo',
  }),
};
```

```typescript
// headers/custom-header/settings/design.ts
import { design } from '@lightspeed/crane-api';

export default {
  logo: design.logo({
    label: '$label.design.logo',
    defaults: {
      font: 'global.fontFamily.title',
      size: 24,
      color: 'global.color.title',
      visible: true,
    },
  }),
};
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.ecwid.com/site-themes/develop-site-themes/dev-recommendations/editor-ux.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
