Editor Experience
The Instant Site Editor is the main interface merchants use to customize a theme. Structure settings so each section works immediately, uses clear merchant-facing labels, and only exposes 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.
// 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',
},
}),
};// 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.
// 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',
}),
};// 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.
// 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.
// 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
maxCardsfor the number of cards the design supports. - Use
maxProductsfor the number of products the grid, carousel, or list can display well. - Use
maxCategoriesfor the number of category links or cards the layout can handle.
// 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.
<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.
// 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',
}),
};// 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,
},
}),
};