Skip to content

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.

typescript
import { useInputboxElementContent, useTextElementDesign } from '@lightspeed/crane-api';

Content Composables

Content composables provide access to user-editable content defined in settings/content.ts.

Available Composables

ComposableEditor TypeReturn TypePurpose
useInputboxElementContentINPUTBOXReactive<InputBoxContent>Single-line text input
useTextareaElementContentTEXTAREAReactive<TextAreaContent>Multi-line text input
useButtonElementContentBUTTONReactive<ButtonContentData>Button with text and link
useImageElementContentIMAGEReactive<ImageContent>Image upload and settings
useToggleElementContentTOGGLEReactive<ToggleContent>Boolean toggle switch
useSelectboxElementContentSELECTBOXReactive<SelectBoxContent>Dropdown selection
useDeckElementContentDECKReactive<DeckContent>Collection of cards
useCategorySelectorElementContentCATEGORY_SELECTORReactive<CategorySelector>Category picker
useProductSelectorElementContentPRODUCT_SELECTORReactive<ProductSelector>Product picker
useLogoElementContentLOGOReactive<LogoContent>Logo image
useMenuElementContentMENUReactive<MenuContent>Menu items
useNavigationMenuElementContentNAVIGATION_MENUReactive<MenuContent>Navigation menu
useTranslationTranslation helperMulti-language support

Return Properties

Text Composables (useInputboxElementContent, useTextareaElementContent):

  • hasContenttrue if field has non-empty text
  • value — the text string

Button Composable (useButtonElementContent):

  • title — button text label
  • hasTitletrue if button has text
  • hasLinktrue if button has a link configured
  • performAction() — 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):

  • hasContenttrue if an image is uploaded
  • lowResolutionMobileImage — 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

vue
<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

ComposableEditor TypeReturn TypePurpose
useTextElementDesignTEXTReactive<TextDesignData>Text styling (font, size, color)
useTextareaElementDesignTEXTAREAReactive<TextareaDesignData>Textarea styling
useButtonElementDesignBUTTONReactive<ButtonDesignData>Button styling
useBackgroundElementDesignBACKGROUNDReactive<BackgroundDesignData>Background color/image
useImageElementDesignIMAGEReactive<ImageDesignData>Image styling
useToggleElementDesignTOGGLEReactive<ToggleDesignData>Toggle styling
useSelectboxElementDesignSELECTBOXReactive<SelectboxDesignData>Selectbox styling
useLayoutElementDesignReactive<LayoutDesignData>Layout settings
useLogoElementDesignLOGOComputedRef<LogoDesignData>Logo styling
useAccordionElementDesignACCORDIONReactive<AccordionDesignData>Accordion nested editors

Return Properties

Text Composables (useTextElementDesign, useTextareaElementDesign):

  • visibletrue if element should be displayed
  • size — font size as number (append 'px' for CSS)
  • font — font family string
  • color — Color object with .hex, .rgba, .hsl properties
  • boldtrue if text should be bold
  • italictrue if text should be italic
  • whiteSpace — (textarea only) white-space CSS value

Background Composable (useBackgroundElementDesign):

  • background.type'solid' or 'gradient'
  • background.solid.color — Color object for solid backgrounds
  • background.gradient.fromColor / toColor — Color objects for gradients

Button Composable (useButtonElementDesign):

  • visibletrue if button should be displayed
  • appearance'solid-button', 'outline-button', or 'text-link'
  • size'small', 'medium', or 'large'
  • style'pill', 'rectangle', or 'round-corner'
  • color — Color object
  • font — font family string

Example

vue
<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

vue
<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:

vue
<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:

vue
<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.

vue
<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:

vue
<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'.

typescript
const { currentLanguage } = useCurrentLanguage();
// currentLanguage.value === 'en' | 'nl' | 'fr' | ...

useBackgroundStyle(backgroundDesign, options?)

Creates reactive CSS background styles from BackgroundDesignData. Supports solid colors and gradients.

typescript
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.

typescript
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 information
  • content — raw content data
  • design — raw design data
  • defaults — default values
  • site — site configuration (languages, currency, etc.)
  • category — current category data (on category pages)
  • storeData — store metadata
  • globalDesign — 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).

typescript
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.