Composables
The Crane API provides Vue 3 composables for accessing content and design data inside your section components. These composables return reactive objects that update automatically when merchants edit values in the Instant Site Editor.
import { useInputboxElementContent, useTextElementDesign } from '@lightspeed/crane-api';Content Composables
Content composables provide access to user-editable content defined in settings/content.ts.
Available Composables
| Composable | Editor Type | Return Type | Purpose |
|---|---|---|---|
useInputboxElementContent | INPUTBOX | Reactive<InputBoxContent> | Single-line text input |
useTextareaElementContent | TEXTAREA | Reactive<TextAreaContent> | Multi-line text input |
useButtonElementContent | BUTTON | Reactive<ButtonContentData> | Button with text and link |
useImageElementContent | IMAGE | Reactive<ImageContent> | Image upload and settings |
useToggleElementContent | TOGGLE | Reactive<ToggleContent> | Boolean toggle switch |
useSelectboxElementContent | SELECTBOX | Reactive<SelectBoxContent> | Dropdown selection |
useDeckElementContent | DECK | Reactive<DeckContent> | Collection of cards |
useCategorySelectorElementContent | CATEGORY_SELECTOR | Reactive<CategorySelector> | Category picker |
useProductSelectorElementContent | PRODUCT_SELECTOR | Reactive<ProductSelector> | Product picker |
useLogoElementContent | LOGO | Reactive<LogoContent> | Logo image |
useMenuElementContent | MENU | Reactive<MenuContent> | Menu items |
useNavigationMenuElementContent | NAVIGATION_MENU | Reactive<MenuContent> | Navigation menu |
useTranslation | — | Translation helper | Multi-language support |
Return Properties
Text Composables (useInputboxElementContent, useTextareaElementContent):
hasContent—trueif field has non-empty textvalue— the text string
Button Composable (useButtonElementContent):
title— button text labelhasTitle—trueif button has texthasLink—trueif button has a link configuredperformAction()— triggers the button action (navigation, scroll, etc.)type— action type (HYPER_LINK,MAIL_LINK,TEL_LINK, etc.)link,email,phone— target values based on type
Image Composable (useImageElementContent):
hasContent—trueif an image is uploadedlowResolutionMobileImage— URL for mobile placeholder (100x200)highResolutionMobileImage— URL for mobile full quality (1000x2000)lowResolutionDesktopImage— URL for desktop placeholder (200x200)highResolutionDesktopImage— URL for desktop full quality (2000x2000)
Example
<script setup lang="ts">
import {
useInputboxElementContent,
useImageElementContent,
useButtonElementContent
} from '@lightspeed/crane-api';
import type { Content } from './type';
const title = useInputboxElementContent<Content>('title');
const heroImage = useImageElementContent<Content>('hero_image');
const ctaButton = useButtonElementContent<Content>('call_to_action');
</script>
<template>
<section class="hero">
<img
v-if="heroImage.hasContent"
:src="heroImage.highResolutionDesktopImage"
/>
<h1 v-if="title.hasContent">{{ title.value }}</h1>
<button
v-if="ctaButton?.hasTitle && ctaButton?.hasLink"
@click="ctaButton.performAction"
>
{{ ctaButton.title }}
</button>
</section>
</template>💡 Key Mapping
The string passed to each composable (e.g., 'title', 'hero_image') must match a key in your content.ts settings file.
Design Composables
Design composables provide access to styling settings defined in settings/design.ts.
Available Composables
| Composable | Editor Type | Return Type | Purpose |
|---|---|---|---|
useTextElementDesign | TEXT | Reactive<TextDesignData> | Text styling (font, size, color) |
useTextareaElementDesign | TEXTAREA | Reactive<TextareaDesignData> | Textarea styling |
useButtonElementDesign | BUTTON | Reactive<ButtonDesignData> | Button styling |
useBackgroundElementDesign | BACKGROUND | Reactive<BackgroundDesignData> | Background color/image |
useImageElementDesign | IMAGE | Reactive<ImageDesignData> | Image styling |
useToggleElementDesign | TOGGLE | Reactive<ToggleDesignData> | Toggle styling |
useSelectboxElementDesign | SELECTBOX | Reactive<SelectboxDesignData> | Selectbox styling |
useLayoutElementDesign | — | Reactive<LayoutDesignData> | Layout settings |
useLogoElementDesign | LOGO | ComputedRef<LogoDesignData> | Logo styling |
useAccordionElementDesign | ACCORDION | Reactive<AccordionDesignData> | Accordion nested editors |
Return Properties
Text Composables (useTextElementDesign, useTextareaElementDesign):
visible—trueif element should be displayedsize— font size as number (append'px'for CSS)font— font family stringcolor— Color object with.hex,.rgba,.hslpropertiesbold—trueif text should be bolditalic—trueif text should be italicwhiteSpace— (textarea only) white-space CSS value
Background Composable (useBackgroundElementDesign):
background.type—'solid'or'gradient'background.solid.color— Color object for solid backgroundsbackground.gradient.fromColor/toColor— Color objects for gradients
Button Composable (useButtonElementDesign):
visible—trueif button should be displayedappearance—'solid-button','outline-button', or'text-link'size—'small','medium', or'large'style—'pill','rectangle', or'round-corner'color— Color objectfont— font family string
Example
<script setup lang="ts">
import { computed } from 'vue';
import {
useTextElementDesign,
useBackgroundElementDesign,
} from '@lightspeed/crane-api';
import type { Design } from './type';
const titleDesign = useTextElementDesign<Design>('title_style');
const backgroundDesign = useBackgroundElementDesign<Design>('section_background');
const backgroundStyle = computed(() => {
if (backgroundDesign.background?.type === 'gradient') {
return {
backgroundImage: `linear-gradient(to right, ${backgroundDesign.background.gradient?.fromColor.hex}, ${backgroundDesign.background.gradient?.toColor.hex})`
};
}
return { backgroundColor: backgroundDesign.background?.solid?.color?.hex };
});
const titleStyle = computed(() => ({
fontSize: `${titleDesign.size}px`,
fontFamily: titleDesign.font,
color: (titleDesign.color as Color).hex,
fontStyle: titleDesign.italic ? 'italic' : 'normal',
fontWeight: titleDesign.bold ? 'bold' : 'normal'
}));
</script>
<template>
<section :style="backgroundStyle">
<h1 v-if="titleDesign.visible" :style="titleStyle">
Welcome
</h1>
</section>
</template>Accordion Element
The useAccordionElementDesign composable provides access to design editors nested inside an ACCORDION design editor. It returns a reactive object containing items, where each item holds a label and an editors map of nested design editor values.
Usage
<script setup lang="ts">
import {
useAccordionElementDesign,
SelectboxDesignData,
ToggleDesignData,
} from '@lightspeed/crane-api';
import type { Design } from './type';
const styling = useAccordionElementDesign<Design>('styling');
const layout = styling.items?.['page_settings']?.editors?.layout as SelectboxDesignData | undefined;
const showFilters = styling.items?.['page_settings']?.editors?.showFilters as ToggleDesignData | undefined;
const cardSize = styling.items?.['card_settings']?.editors?.cardSize as SelectboxDesignData | undefined;
</script>
<template>
<div :class="['catalog', `layout-${layout?.value}`]">
<aside v-if="showFilters?.enabled" class="filters">
<!-- filter panel -->
</aside>
<div :class="['products', `card-${cardSize?.value}`]">
<!-- product grid -->
</div>
</div>
</template>💡 Key Mapping
The first argument to useAccordionElementDesign (e.g., 'styling') must match the key in your design.ts settings file. Access nested editors via items?.['itemKey']?.editors?.editorKey.
Responsive Images
The image composable provides multiple resolution variants for responsive designs:
<template>
<picture v-if="image.hasContent">
<source media="(max-width: 768px)" :srcset="image.highResolutionMobileImage" />
<img :src="image.highResolutionDesktopImage" alt="Hero" />
</picture>
</template>For programmatic control, use a reactive width check:
<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
import { useImageElementContent } from '@lightspeed/crane-api';
import type { Content } from './type';
const image = useImageElementContent<Content>('hero_image');
const windowWidth = ref<number>(typeof window !== 'undefined' ? window.innerWidth : 0);
function onResize() { windowWidth.value = window.innerWidth; }
onMounted(() => { window.addEventListener('resize', onResize); });
onBeforeUnmount(() => { window.removeEventListener('resize', onResize); });
const isMobile = computed(() => windowWidth.value <= 768);
const imageUrl = computed(() =>
isMobile.value ? image.highResolutionMobileImage : image.highResolutionDesktopImage
);
</script>Working with DECK
The DECK composable returns a collection of cards. Use getReactiveRef to access individual card fields. When a card’s settings include a nested DECK (e.g. slides with link decks), pass EditorTypes.DECK as the editor type to get a reactive deck content object with the same shape (hasContent, cards, getReactiveRef), subject to the max nesting depth of one level.
<script setup lang="ts">
import { computed } from 'vue';
import {
useDeckElementContent,
Card,
EditorTypes,
ImageContent,
TextAreaContent,
InputBoxContent
} from '@lightspeed/crane-api';
import type { Content } from './type';
const imagesRaw = useDeckElementContent<Content>('images');
const images = computed(() => (
imagesRaw?.cards?.map((card: Card) => ({
text: imagesRaw.getReactiveRef(card, EditorTypes.TEXTAREA, 'image_text') as unknown as TextAreaContent | undefined,
content: imagesRaw.getReactiveRef(card, EditorTypes.IMAGE, 'image_content') as unknown as ImageContent | undefined,
link: imagesRaw.getReactiveRef(card, EditorTypes.INPUTBOX, 'image_link') as unknown as InputBoxContent | undefined,
})).filter((image) => (image.content !== undefined && image.content.hasContent))
));
</script>
<template>
<div class="gallery">
<div v-for="(image, index) in images" :key="index" class="gallery-item">
<img :src="image.content?.highResolutionDesktopImage" />
<p v-if="image.text?.hasContent">{{ image.text.value }}</p>
</div>
</div>
</template>💡 Type Casting
Use the as unknown as Type casting pattern for getReactiveRef results, as shown above.
Translation
Use useTranslation for static multi-language text defined in your translations file:
<script setup lang="ts">
import { useTranslation } from '@lightspeed/crane-api';
const { t } = useTranslation();
</script>
<template>
<h1>{{ t('$label.shared.title') }}</h1>
<p>{{ t('$label.shared.description') }}</p>
</template>ℹ️ Static Text Only
Use useTranslation for static text only — text that is the same across all instances of the section. For merchant-editable text, use content composables like useInputboxElementContent.
UI Utility Composables
useCurrentLanguage()
Returns the current language code from site context with fallback chain: selected → main → 'en'.
const { currentLanguage } = useCurrentLanguage();
// currentLanguage.value === 'en' | 'nl' | 'fr' | ...useBackgroundStyle(backgroundDesign, options?)
Creates reactive CSS background styles from BackgroundDesignData. Supports solid colors and gradients.
const bg = useBackgroundElementDesign<DesignType>('section_bg');
const bgStyle = useBackgroundStyle(bg, { direction: 'to bottom' });
// <div :style="bgStyle">useButtonStyles(buttonDesign, options?)
Generates button CSS styles from ButtonDesignData. Handles size, appearance, shape, and auto contrast text color for solid buttons.
const btn = useButtonElementDesign<DesignType>('cta_button');
const btnStyle = useButtonStyles(btn, {
sizes: { large: { fontSize: '20px', padding: '16px 32px' } },
});
// <button :style="btnStyle">Advanced Composables
These composables are exported from @lightspeed/crane-api for advanced use cases. Most sections won't need them.
useVueBaseProps
Low-level composable providing reactive access to the full section context:
context— store and environment informationcontent— raw content datadesign— raw design datadefaults— default valuessite— site configuration (languages, currency, etc.)category— current category data (on category pages)storeData— store metadataglobalDesign— global design settings (colors, fonts, text sizes)tileId— the section's tile identifier
Use this when the typed content/design composables are not sufficient — for example, when accessing global design tokens directly or reading site-level configuration.
useInstantsiteJsApi
Returns window.instantsite — the platform's JavaScript API for interacting with the storefront runtime (e.g., triggering cart updates, navigation events).
import { useInstantsiteJsApi } from '@lightspeed/crane-api';
const api = useInstantsiteJsApi();⚠️ Client-Only
This is a client-only API. Guard usage with typeof window !== 'undefined' or call it inside onMounted. See SSR vs Client-Only Rendering.