Delivery Promises
Unify Delivery Promises Widget
Two elements working together: <unify-delivery-promises-widget> is a headless container that fetches delivery promises once for an item at a given location, and <unify-delivery-offer-widget> is a lightweight offer block placed inside it, one per target, that renders either a single resolved offer (click & go, in-stock at a store, or nearby stores) or, for all-delivery-promises, every offer the API returned. There is no "standard"/"express" delivery-speed tiering — that distinction doesn't exist in the API for home or relay offers, so those are only ever surfaced through all-delivery-promises.
Integrators place one offer block per spot on the page — a product page might show an all-delivery-promises block near the price listing every option, and a location-click-and-go block near a store locator — all inside the same container, so the technical/request attributes (API key, item, amount, location...) are declared once, not once per block.
For installation instructions, see Getting Started with Unify Widgets.
Basic Implementation
<unify-delivery-promises-widget
api-key="your-api-key"
item-id="your-item-id"
source="web"
amount="49.99"
latitude="48.8566"
longitude="2.3522"
>
<unify-delivery-offer-widget target="all-delivery-promises"></unify-delivery-offer-widget>
</unify-delivery-promises-widget><unify-delivery-promises-widget> (container)
<unify-delivery-promises-widget> (container)Fetches once and pushes the result to every <unify-delivery-offer-widget> placed inside it (light DOM children — plain nesting, no slot name needed). Renders nothing itself.
Attributes
| Attribute | Type | Required | Description |
|---|---|---|---|
api-key | String | Yes | API key for Unify requests |
item-id | String | Yes | Product item identifier to check delivery promises for |
source | String | Yes | Order source, matching the values accepted by the API's source field (e.g. web) |
amount | String | Yes | Item amount/price. Sent to the API formatted to exactly two decimals |
quantity | Number | No | Quantity for item-id. Defaults to 1 |
latitude | String | Yes* | Latitude of the delivery location. Required unless address is set |
longitude | String | Yes* | Longitude of the delivery location. Required unless address is set |
demo | Boolean | No | Enable demo mode for testing |
Properties
| Property | Type | Required | Description |
|---|---|---|---|
address | Object | No | { zip_code, city, country_code } — used instead of latitude/longitude when coordinates aren't available |
config | Object | No | { cacheTtl, requestTimeout } — see Config Options |
<unify-delivery-offer-widget> (offer block)
<unify-delivery-offer-widget> (offer block)Must be placed inside a <unify-delivery-promises-widget>. Renders one resolved offer for its target.
Attributes
| Attribute | Type | Required | Description |
|---|---|---|---|
target | String | No | Which offer(s) to render: all-delivery-promises (default), location-click-and-go, location-click-and-collect, or locations-near |
location-id | String | No | A location from promises.store to match against. Used by location-click-and-go, location-click-and-collect (to pick that exact store), locations-near (to exclude that store from the nearby count — only when it matches an actual location in the response; an unset or non-matching location-id excludes nothing, so locations-near lists every store) and all-delivery-promises (to scope its store items to that exact store instead of every store location) |
type | String | No | Only used by target="all-delivery-promises": restricts its list to a single node - home, relay, or store. Unset (default) lists every node. Ignored by every other target |
Properties
| Property | Type | Required | Description |
|---|---|---|---|
config | Object | No | Configuration object (see Config Options) |
Targets
target | Resolved from | Description |
|---|---|---|
location-click-and-go | promises.store | The store location (matching location-id if set) whose reception_type.click_go flag is set — a prepared order ready for pickup |
location-click-and-collect | promises.store | The store location (matching location-id if set) whose reception_type.click_collect flag is set — item in stock at that location, reserve-then-collect |
locations-near | promises.store | Count and list of store locations other than the one matching location-id (all of them if location-id is unset or doesn't match any store) |
all-delivery-promises | promises.home + promises.relay + promises.store | Every offer the API returned, unfiltered — no "best offer" curation. Each home/relay offer and each store location (matching location-id if set) is listed as its own item, tagged home/relay/store. Set type="home"/"relay"/"store" to list only that one node |
Note:
location-click-and-go,location-click-and-collect,locations-nearand thestoreitems ofall-delivery-promisesare all derived from thestorebranch of the API response, which does not currently include per-location stock quantity — only which reception types (click & go / click & collect / e-reservation) are available. Each location's%estimate%/%date_from%/%date_to%come from that location's owndelivery_delayswhen the API provides one, falling back to the carrier-widedelivery_delaysotherwise.
Events
delivery-offer-loaded
delivery-offer-loadedFired by <unify-delivery-offer-widget> once its resolved offer is available (or determined unavailable). Bubbles, so listening on an ancestor also catches it:
offerBlock.addEventListener('delivery-offer-loaded', (event) => {
const { target, offer } = event.detail;
console.log(`${target} offer`, offer);
});Detail shape:
{
target: string, // The offer block's `target` attribute
offer: object|null // Resolved offer, or null when unavailable
}delivery-promises-loaded
delivery-promises-loadedFired by <unify-delivery-promises-widget> once per fetch resolution (idle, error or missing-params), with the raw, unresolved API payload rather than a single offer - useful to react once per request instead of once per offer block. Bubbles, and is distinct from the offer block's own delivery-offer-loaded event above, so a listener on the container never has to tell the two apart:
container.addEventListener('delivery-promises-loaded', (event) => {
const { dataState, promises } = event.detail;
console.log(dataState, promises);
});Detail shape:
{
dataState: string, // 'idle' | 'error' | 'missing-params'
promises: object|null // Raw `delivery_promises` from the API, or null
}Config Options
Customize widget text and behavior. cacheTtl and requestTimeout go on the container's config; everything else (labels, locale, currency) goes on each offer block's config, since those are display concerns specific to that block.
document.querySelector('unify-delivery-promises-widget').config = {
cacheTtl: 300000,
requestTimeout: 10000,
};
document.querySelector('unify-delivery-offer-widget[target="all-delivery-promises"]').config = {
allDeliveryPromisesHomeLabel: 'Home delivery (%carrier%) - %date_from% to %date_to% (%fees%)',
};Labels (offer block)
| Option | Default | Description |
|---|---|---|
locationClickAndGoLabel | "Click & Collect available at %store_name%" | Label for target="location-click-and-go". Supports %store_name%, %city%, %address%, %prep_delay%, %estimate%, %date_from% and %date_to% |
locationClickAndCollectLabel | "In stock at %store_name%" | Label for target="location-click-and-collect". Same placeholders as locationClickAndGoLabel |
locationsNearLabel | "Available in %count% nearby stores" | Label for target="locations-near". Supports %count%. Also accepts { one, other } (each with its own %count% template) to pick a grammatically correct singular/plural form — one is used when the count is exactly 1, other otherwise |
allDeliveryPromisesHomeLabel | "Home delivery (%carrier%) - in %estimate%" | Label for each home item of target="all-delivery-promises". Supports %estimate%, %date_from%, %date_to%, %fees%, %limit_fees% and %carrier% |
allDeliveryPromisesRelayLabel | "Pickup point delivery (%carrier%) - in %estimate%" | Label for each relay item of target="all-delivery-promises". Same placeholders as allDeliveryPromisesHomeLabel |
allDeliveryPromisesStoreLabel | "%store_name% - in %estimate%" | Label for each store item of target="all-delivery-promises". Supports %store_name%, %city%, %address%, %prep_delay%, %estimate%, %date_from% and %date_to% |
carrierLabels | none (raw API code) | Object mapping a raw carrier code (as returned by the API, e.g. chronopost_express) to the display name to use wherever %carrier% is rendered. A code with no entry falls back to itself as-is |
Example of carrierLabels, mapping raw carrier codes to display names for %carrier%:
document.querySelector('unify-delivery-offer-widget[target="all-delivery-promises"]').config = {
carrierLabels: {
chronopost_express: 'Express',
chronopost: 'Chronopost',
},
};Per-target defaults for the "unavailable" state (no placeholders — override with ${target}UnavailableLabel, e.g. locationClickAndGoUnavailableLabel):
target | Default unavailable text |
|---|---|
location-click-and-go | "Click & Go - not available" |
location-click-and-collect | "No stock available" |
locations-near | "No nearby stores available" |
all-delivery-promises | "No delivery options available" |
Example of the singular/plural form of locationsNearLabel:
document.querySelector('unify-delivery-offer-widget[target="locations-near"]').config = {
locationsNearLabel: {
one: 'Available in %count% nearby store',
other: 'Available in %count% nearby stores',
},
};%limit_fees% is the free-shipping threshold (limit_shipping_fees), formatted as currency like %fees%. %address% is the store's street address (blank if the API doesn't return one for that location). %prep_delay% is the raw estimated_preparation_request value from the API — an integer count of stock splits needed to fulfill the order at that store, not a duration in hours; word your label accordingly (e.g. avoid implying a fixed unit).
%estimate% is a bare, no-connector estimate for location-click-and-go/location-click-and-collect/all-delivery-promises, so your label supplies the connector word (in %estimate%, by %estimate%...) since it depends on language. It resolves to a clock time (e.g. "6:00 PM", locale-formatted) when the offer's estimated_delivery_date_to falls today, otherwise to a locale-formatted day/hour count or range from estimated_delivery_from/estimated_delivery_to + unit, via Intl.NumberFormat's native unit range formatting (e.g. "2–3 days", with an en dash — "2–3 jours" in fr-FR). Example: allDeliveryPromisesHomeLabel: 'Home - in %estimate%' renders "Home - in 2–3 days". Set estimateRangeConnector (see Other) to override that native separator, e.g. estimateRangeConnector: ' à ' renders "2 à 3 jours" instead.
Status Messages (offer block)
| Option | Default | Description |
|---|---|---|
loadingMessage | "Loading delivery information..." | Text shown while the container is fetching |
errorMessage | "Error loading delivery information. Please try again later." | Error message on load failure |
missingParamsMessage | "Missing item ID, source, amount or location." | Message shown when a required attribute on the container is missing |
unavailableMessage | Per-target, e.g. "Click & Go - not available" for location-click-and-go (see below) | Message shown when no offer is resolved for this block's target. Overrides the per-target default for every target uniformly; use ${target}UnavailableLabel (e.g. locationClickAndGoUnavailableLabel) to override a single target instead |
Other (offer block)
| Option | Default | Description |
|---|---|---|
locale | navigator.language | Locale used to format dates and currency |
currency | "EUR" | Currency used to format %fees% |
estimateRangeConnector | Unset (native Intl.NumberFormat range, e.g. "2–3 jours" in fr-FR) | Overrides the separator between the two numbers of a multi-day %estimate% range (e.g. " à " renders "2 à 3 jours" instead of the browser's native en-dash range). Ignored for a single-value estimate or a same-day clock time |
Other (container)
| Option | Default | Description |
|---|---|---|
cacheTtl | 300000 (5 minutes) | How long, in milliseconds, a response is reused by containers sharing the same request (e.g. a desktop and a mobile layout of the same product page) |
requestTimeout | 10000 (10 seconds) | How long, in milliseconds, to wait for the delivery-promises request before aborting it and falling back to the error state, instead of leaving offer blocks stuck on loading indefinitely |
Styling
CSS Custom Properties (recommended)
All visual tokens are exposed as CSS custom properties prefixed with --unify-. This is the stable styling API, set on the offer block (not the container, which renders nothing itself).
unify-delivery-offer-widget {
--unify-delivery-promises-error-color: #b91c1c;
}CSS Parts
Structural elements can be targeted using ::part(), on the offer block:
unify-delivery-offer-widget::part(delivery-offer) {
font-weight: 600;
}
unify-delivery-offer-widget[target="location-click-and-go"]::part(delivery-offer--location-click-and-go) {
color: #0066cc;
}
unify-delivery-offer-widget[target="all-delivery-promises"]::part(delivery-offer-item--store) {
font-style: italic;
}⚠️ Parts may change between versions. Use CSS custom properties where possible.
To find available parts, inspect an offer block's shadow DOM in DevTools — every element with a part attribute is targetable from outside.
Examples
Multiple offer blocks sharing one request
<unify-delivery-promises-widget
api-key="your-api-key" item-id="ITEM-12345"
source="web" amount="49.99" latitude="48.8566" longitude="2.3522"
>
<unify-delivery-offer-widget target="location-click-and-go" location-id="LOC-789"></unify-delivery-offer-widget>
<unify-delivery-offer-widget target="all-delivery-promises"></unify-delivery-offer-widget>
</unify-delivery-promises-widget>Splitting all-delivery-promises by node
<unify-delivery-promises-widget
api-key="your-api-key" item-id="ITEM-12345"
source="web" amount="49.99" latitude="48.8566" longitude="2.3522"
>
<h3>Home delivery</h3>
<unify-delivery-offer-widget target="all-delivery-promises" type="home"></unify-delivery-offer-widget>
<h3>Pickup points</h3>
<unify-delivery-offer-widget target="all-delivery-promises" type="relay"></unify-delivery-offer-widget>
<h3>Stores</h3>
<unify-delivery-offer-widget target="all-delivery-promises" type="store"></unify-delivery-offer-widget>
</unify-delivery-promises-widget>Using a structured address instead of coordinates
<unify-delivery-promises-widget
id="promises" api-key="your-api-key" item-id="ITEM-12345"
source="web" amount="49.99"
>
<unify-delivery-offer-widget target="all-delivery-promises"></unify-delivery-offer-widget>
</unify-delivery-promises-widget>
<script>
document.getElementById('promises').address = {
zip_code: '75008',
city: 'Paris',
country_code: 'FR',
};
</script>Updated 7 days ago