@bloomreach/lit-sdk
v27.3.0
Published
Bloomreach SPA SDK for Lit web components
Readme
Bloomreach Lit SDK
Bloomreach Lit SDK integration for Lit web components. This package provides the same component set as the React, Angular, and Vue SDKs — built with Lit patterns: custom elements, decorators, and @lit/context for state propagation.
Features
- Lit custom elements for Bloomreach page rendering
- Dynamic component mapping (brXM
ctypeto Lit tag name) - Experience Manager preview integration with live editing
- Context-based state propagation via
@lit/context - Full TypeScript support with re-exported spa-sdk types
Installation
npm install @bloomreach/lit-sdk @bloomreach/spa-sdkQuick Start
1. Create the app shell
import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import type { Configuration } from '@bloomreach/spa-sdk';
import '@bloomreach/lit-sdk';
import './my-banner.js';
import './my-content.js';
const MAPPING: Record<string, string> = {
'Banner': 'my-banner',
'Content': 'my-content',
};
@customElement('my-app')
export class MyApp extends LitElement {
private get _config(): Configuration {
return {
endpoint: 'http://localhost:8080/site/resourceapi',
httpClient: async (config) => {
const response = await fetch(config.url, {
method: config.method,
headers: config.headers as Record<string, string>,
body: config.data,
});
return { data: await response.json() };
},
path: `${window.location.pathname}${window.location.search}`,
} as Configuration;
}
createRenderRoot() { return this; }
render() {
return html`
<br-page .configuration=${this._config} .mapping=${MAPPING}>
<header>
<br-component name="menu"></br-component>
</header>
<main>
<br-component name="main"></br-component>
</main>
<footer>
<br-component name="footer"></br-component>
</footer>
</br-page>
`;
}
}2. Create a mapped component
Every mapped component receives component (ContainerItem) and page (Page) properties from the SDK:
import { LitElement, html, nothing } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import type { ContainerItem, Page } from '@bloomreach/spa-sdk';
import { getDocumentData } from '@bloomreach/lit-sdk';
interface BannerData {
title: string;
content: { value: string };
}
@customElement('my-banner')
export class MyBanner extends LitElement {
@property({ type: Object }) component?: ContainerItem;
@property({ type: Object }) page?: Page;
createRenderRoot() { return this; }
render() {
if (!this.component || !this.page) return nothing;
const data = getDocumentData<BannerData>(this.component, this.page);
if (!data) return nothing;
return html`
<div class="banner">
<br-manage-content-button
.content=${this.page.getContent(this.component.getModels().document)}
.page=${this.page}
></br-manage-content-button>
<h1>${data.title}</h1>
</div>
`;
}
}Note: Use
createRenderRoot() { return this; }(light DOM) on components that need<br-manage-content-button>or<br-manage-menu-button>, so the Experience Manager can find the meta comments.
Components
<br-page>
Root SDK component. Initializes the SPA SDK and provides Page, Component, and mapping to descendants via Lit Context.
| Property | Type | Required | Description |
|---|---|---|---|
| configuration | Configuration | Yes | SPA SDK configuration (endpoint, httpClient, etc.) |
| mapping | Record<string, string> | Yes | Maps brXM ctype strings to Lit custom element tag names |
Two rendering modes:
- Auto-render (no children) — renders the full component tree
- Custom layout (with children) — you control layout with
<br-component name="...">
Note: Auto-render mode uses a slot fallback internally. Ensure no whitespace or text nodes exist between the opening and closing
<br-page>tags, as any light DOM content (including whitespace) will suppress the fallback. This is not an issue when using Lit template bindings.
<br-component>
Traverses the brXM component tree by name and renders children recursively.
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | No | Name of the child component to resolve |
The name values correspond to component names in the brXM page layout (e.g., menu, main, footer).
<br-manage-content-button>
Renders an "Edit content" overlay in Experience Manager preview mode.
| Property | Type | Required | Description |
|---|---|---|---|
| content | Content | No | The content object to edit |
| page | Page | No | Direct page reference |
| documentTemplateQuery | string | No | Template query for new documents |
| folderTemplateQuery | string | No | Template query for new folders |
| parameter | string | No | Component parameter name |
| relative | boolean | No | Store as relative path |
| root | string | No | Root folder path |
| pickerSelectableNodeTypes | string | No | Selectable node types for picker |
| pickerConfiguration | string | No | CMS picker configuration path |
| pickerInitialPath | string | No | Initial path for the picker |
<br-manage-menu-button>
Renders an "Edit menu" overlay in Experience Manager preview mode.
| Property | Type | Required | Description |
|---|---|---|---|
| menu | Menu | Yes | The menu model to edit |
| page | Page | No | Direct page reference |
Component Mapping
The mapping property on <br-page> maps brXM ctype strings to Lit custom element tag names:
const MAPPING: Record<string, string> = {
'Banner': 'my-banner',
'Content': 'my-content',
'News List': 'my-news-list',
'Simple Content': 'my-content', // multiple ctypes can map to one element
};The ctype is read from ContainerItem.getType() (falls back to the component label). Unmapped ctypes produce a console warning.
Data Extraction
getDocumentData<T>(component, page)
Resolves document data for container items using the standard models.document pattern:
import { getDocumentData } from '@bloomreach/lit-sdk';
const data = getDocumentData<BannerData>(component, page);getContainerItemContent<T>(component, page)
Resolves content from the content field pattern (alternative to document references):
import { getContainerItemContent } from '@bloomreach/lit-sdk';
const data = getContainerItemContent<BannerData>(component, page);Context System
The SDK uses @lit/context to propagate state without prop-drilling:
| Context | Type | Provider | Description |
|---|---|---|---|
| brPageContext | Page | <br-page> | The initialized Page object |
| brComponentContext | Component | <br-page>, <br-component> | Current component in the tree |
| brMappingContext | Record<string, string> | <br-page> | The ctype-to-tag-name mapping |
You typically don't consume these directly — the SDK passes component and page as properties to mapped elements. For components outside the mapping (e.g., a menu), consume the context:
import { consume } from '@lit/context';
import { brPageContext } from '@bloomreach/lit-sdk';
@customElement('my-menu')
class MyMenu extends LitElement {
@consume({ context: brPageContext, subscribe: true })
private page?: Page;
}Experience Manager Preview
When running in the Experience Manager iframe, the SDK automatically handles:
- JWT Authentication — Token from
?token=included in API requests - DOM Comments — Meta comments injected for editing overlays
page.sync()— Called after renders to reposition overlays- Live updates — Re-renders on content editor changes
Advanced Topics
Internal components
<br-container> and <br-container-item> are internal components instantiated automatically by <br-component>. You don't use them directly. <br-container-item> resolves the ctype, creates the mapped element, sets component/page properties, and handles the isHidden() Relevance feature.
Shadow DOM vs Light DOM
| Component | DOM Mode | Reason |
|---|---|---|
| <br-page> | Shadow DOM | Uses <slot> for layout projection |
| <br-component>, <br-container>, <br-container-item> | Light DOM | EM meta comments need light DOM |
| <br-manage-content-button>, <br-manage-menu-button> | Light DOM | EM meta comments need light DOM |
| Your mapped components | Either | Use light DOM if the component needs a manage button |
Your app shell should also use light DOM (createRenderRoot() { return this; }).
Shadow DOM manage button (renderTarget)
If your mapped component must use Shadow DOM but needs a manage button, pass renderTarget to inject meta comments in the light DOM:
<br-manage-content-button
.content=${document}
.page=${this.page}
.renderTarget=${this}
></br-manage-content-button>Field name mapping
brXM compound fields may use type-specific names (e.g., featureitem instead of items). Inspect the Delivery API response and map fields in your component:
const raw = getDocumentData<any>(this.component, this.page);
const data = { ...raw, items: raw.featureitem ?? raw.items ?? [] };Dual-mode components
Components that work both with the SDK and standalone data props:
@customElement('my-component')
export class MyComponent extends LitElement {
@property({ type: Object }) data?: MyData;
@property({ type: Object }) component?: ContainerItem;
@property({ type: Object }) page?: Page;
private _getData(): MyData | undefined {
if (this.component && this.page) {
return getDocumentData<MyData>(this.component, this.page);
}
return this.data;
}
}API Reference
Components: BrPage, BrComponent, BrComponentNode, BrContainer, BrContainerItem, BrManageContentButton, BrManageMenuButton
Contexts: brPageContext, brComponentContext, brMappingContext
Utilities: getDocumentData, getContainerItemContent, isInternalLink, isExternalLink
Type guards (re-exported from @bloomreach/spa-sdk): isComponent, isContainer, isContainerItem, isContent, isDocument, isImageSet, isLink, isMenu, isMetaComment, isMeta, isPage, isPagination, isReference
Types (re-exported from @bloomreach/spa-sdk): Page, Component, ContainerItem, Container, Content, Document, ImageSet, Image, Link, Menu, Menu10, MenuItem, MetaCollection, MetaComment, Meta, PageModel, Configuration, ManageContentButton, Reference
Functions (re-exported from @bloomreach/spa-sdk): initialize, destroy
Troubleshooting
- "this.httpClient is not a function" — Provide an
httpClientin the configuration. See Quick Start. - "No mapping found for ctype" — The
ctypefrom brXM doesn't match your mapping keys. Check the Delivery API response for exact values (case-sensitive). - Components render empty — Verify
getDocumentData()returns data. Check the Network tab for API responses. - Context not reaching components — Ensure your app shell uses light DOM.
- EM overlays not appearing — Check
page.isPreview(), CORS headers, and preview URL configuration.
