UI Changelog
All notable changes to Laioutr UI (@laioutr-core/ui, the commerce-specific organism components built on UI Kit) are documented here, following Keep a Changelog and Semantic Versioning.
2.26.0 - 2026-09-25
Minor Changes
- Added
thumb-up/thumb-downicons and the AI Mode assistant locale keys (tab labels, answer disclaimer, action labels, and placeholder answers) used by the AI Mode assistant view. - Added
microphone,lens, andai-sparkleicons (communication/microphone,media/lens,misc/ai-sparkle), plusaiModelocale keys (English and German) for the new AI Mode search bar. - Added the AI Mode assistant view. Pressing Enter in the AI Mode bar opens a Q&A thread — a tab row (AI Mode label + All / Products / Content), the conversation (user bubble, assistant answer with markdown, disclaimer, and copy/feedback/regenerate actions), and the bar docked as the bottom composer. Answers come from a typed
AiModeAnswerProviderseam (a static mock by default; the active tab is passed as scope) so a real backend can be plugged in without UI changes. - Added the
AiModecomponent — a Google-"AI Mode"-style search bar with a greeting, input row, and static example suggestions, plus supportingAiModeSuggestionsand Storybook stories. Visual only: no search logic, autosuggest behavior, or data binding yet. - Added the
SectionAiModeStudio section, wrapping the newAiModecomponent in aFull Width/Boxedlayout via the standard container-style field.
2.25.5 - 2026-09-22
Patch Changes
- Gallery thumbnails have an accessible name. Each
ThumbnailsSliderbutton is announced as "Show image 2 of 5" in the active locale, and the selected one carriesaria-current. A thumbnail whose image had noalttext was an unnamed button.ImageGallery,MediaGalleryandProductImageGalleryget the names with no change.
The position replaces the image'salttext as the button's name. To name a thumbnail by its content, setlabelon itsthumbnailsitem, or passlabeltoThumbnaildirectly. Selectalways has an accessible name. ASelectwithoutlabelwas announced as an unnamed combobox although it showed its placeholder, and failed thebutton-nameaudit. The trigger is now named bylabel, then by the label of the surroundingField, then byplaceholder, then by the default placeholder text.
Attributes set on<Select>—aria-label,aria-labelledby,class,data-*— reach the trigger button instead of being dropped. The sort selects inSortModesandFilterBarControlsand the two selects inRevieware named for their purpose.- An Embedded Page block (
BlockIframe) without a URL renders no<iframe>, so it no longer fails theframe-titleaudit or adds an empty tab stop. It keeps the height from its Sizing settings. With Sizing set to Auto, an empty block has no height, where it had the browser's default iframe height of 150px — set Sizing to Fixed to keep that gap.
2.25.2 - 2026-09-20
Patch Changes
- The
lsolarandlhugeicon collections go to@nuxt/icon'sserverBundlerather than tocustomCollections. Both options take the same{ prefix, dir }and both resolve the icons, butcustomCollectionsalso makes the module's runtime plugin callsetCustomIconsLoaderon every server-rendered request, with a closure that captures that request's$fetch.@iconify/vuekeeps unresolved loads in a module-scope array and removes an entry only when the load resolves — so one icon that does not resolve holds that whole request alive: its router, its i18n instance, its stores and its payload.
Measured on a storefront before the change: about 3.4 MiB retained per request, and the process aborting at the V8 heap limit after thirteen minutes at half a request a second.
2.25.1 - 2026-09-18
Patch Changes
Mediarendersalt=""when given an emptyalt, so a decorative image can be marked as one. An empty string used to fall back to the media object's own alt text, and when that was missing thealtattribute was dropped entirely — which Lighthouse reports as "Image elements do not have alt attributes". Leavingaltunset still uses the media object's alt text.BannerBasic,BannerIntegrated,BannerShowcaseandHeroSlidename their full-area link after the image's alt text when they have no text of their own. An image-only banner with anhrefused to render that link with no accessible name — announced by screen readers as a bare link, and flagged by Lighthouse as "Links must have discernible text". A component with a heading, subline, description or caption keeps using that text.
2.25.0 - 2026-09-17
Minor Changes
- Posts by topics. Content Slider and Content Grid gain a third data source. Instead of binding a blog collection to the page, you list one or more topic slugs on the section itself, choose how many posts to show, and optionally hide the post currently being read. The section fetches the matching posts on its own, so it can go on a page that has no blog query at all — including one placed by an agent that can only set static props.
When the topic list returns no posts, the section renders nothing instead of a heading above an empty row. A bound blog collection behaves as before.
Breadcrumb structured data. Breadcrumbs gains an option, on by default, to emit a schema.orgBreadcrumbListas JSON-LD next to the visible trail: the home page first, then each entry in order, with absolute URLs and no link on the current page. Two breadcrumb sections on one page emit a single block.
2.24.2 - 2026-09-09
Patch Changes
- The cart sheet no longer holds up hydration. It sits in the shop header on every page and fetched the cart during setup, which kept the page suspended until that request returned — so nothing on the page became interactive until the cart had loaded. The sheet now renders immediately and fills in when the cart arrives.
A cart request that fails no longer fails the page with it. It leaves the sheet empty and names the failure in the browser console.
Adding to cart refreshes the cart once. The header re-registered its refresh handler on every navigation without removing the previous one, so a visitor who moved through five pages triggered five refreshes on the next add.
2.24.0 - 2026-09-08
Minor Changes
- Locales carry a
filter.sortBymessage, translated in all seven shipped languages and overridable throughextendLocale. - The breadcrumb's short trail now behaves as the UI Kit specifies it: it applies below the desktop layout, its trigger disappears once expanded, and a long trail wraps instead of running off the side of the page.
Three things were wrong, all in the same area:- The trail expanded too early. Script switched on
smaller('md')while every rule in the component's own stylesheet restores the expanded trail at@media (--lg). Between 800px and 1279px the collapsed markup was therefore off while the CSS still expected it, and a long trail — a product title at the end of a deep category path — pushed the page sideways. Both now switch atlg, which is what the design's breakpoint matrix shows: a short version exists frominitialthroughmd, and only fromlgis the full trail unconditional. - The trigger stayed after expanding, swapping its icon to a collapse arrow. The design shows it gone, with the home button taking its place; expanding is one-way and home is the way back to the start.
- The trail could not wrap. The container was a nowrap
inline-flex, so the expanded trail had nowhere to go but sideways. It wraps now, which is what the design draws at the narrow breakpoints — 64px of breadcrumb rather than 40px atsand below.
The label truncation stays scoped to the short trail and is released atlgrather thansm. Releasing it while the row was still a single nowrap line is what made the overflow reachable in the first place; the expanded trail shows its labels in full and wraps, as drawn.
Storefronts carrying a local override to stop breadcrumbs widening the page can drop it. - The trail expanded too early. Script switched on
FilterBaracceptsshowSortingLabel, which renders a translated "Sort by" to the left of the sorting select. The label stays hidden whileavailableSortingis empty, so a bar with nothing to sort by never announces a control it does not show.- The Filter Bar block has a new
Sorting labeloption, which puts a translated "Sort by" in front of the sorting select. It is off by default and only offered onceSortingis enabled, so an existing filter bar looks unchanged.
Patch Changes
- Locales carry
addToCart.successToast,addToCart.errorToast,addToCart.unknownErrorandcart.couponApplyFailed, translated in all seven shipped languages and overridable throughextendLocale. - Locales carry
review.submitSuccessandreview.submitError, translated in all seven shipped languages and overridable throughextendLocale. swiper/css/gridships alongside the other Swiper stylesheets, so a slider using Swiper's Grid module lays its slides out in the rows it configures. The module set its container class and nothing wrapped, leaving every slide on one row.LogoSliderno longer shows a single oversized logo before it hydrates. Swiper writes its slide widths as inline styles at init, and init waits until the slider nears the viewport — with no width of its own the slider fell back toswiper/css'swidth: 100%, so one logo filled the container until then.
It also emits thelogo-sliderroot class it had been styling without ever setting, so that class is now a usable anchor for consumer CSS.ReviewtakesisLoggedInand asks an unauthenticated visitor to sign in before opening the form. It used to open the form for everyone and raise the sign-in prompt after the visitor had written and submitted their review.- Cart toasts render in the storefront's language. The add-to-cart confirmation on the product page and on a product tile, the failure toast behind it, and the cart sheet's coupon-code error were English string literals that no locale could reach. (SUPPORT-62)
- Paging, sorting and the star filter on the product-reviews list do what they say. The section listened for none of the three events the list raises, so every control moved and the list stayed as it was. Each now states itself in the URL, the way the other listings do, so a shared link reproduces the page the visitor was on. (SUPPORT-63)
- The product-reviews section saves the review a visitor writes. It listened for the login event and nothing else, so
CreateReviewActionwas never called and the form's contents were dropped; the section now submits them, reporting the outcome in a toast.
A visitor without a customer session is asked to sign in before the form opens, rather than after they have filled it in. Their name and address come from the session, which is what the review needs and the form does not collect. (SUPPORT-61)
2.23.1 - 2026-09-01
Patch Changes
SwiperChromerenders a root element around its swiper, so every slider built on it —ProductSlider,ContentSlider,LogoSlider,CategoryCardSlider— exposes a real element as$elfrom its first render.CommonSwiperleaves a comment placeholder there until its chunk lands, and code that measured or observed the root received that comment.
Stylesheets need no change..swiper-chromestays on the swiper, and every class, id, style and listener passed to the component still reaches it.- The Product Slider section and block no longer throw when they mount on the client instead of hydrating from server markup — in the studio preview, and after an in-app navigation. Both raised
TypeError: IntersectionObserver.observe: Argument 1 does not implement interface Element. Theirview_item_listtracking still reports.
2.23.0 - 2026-09-01
Minor Changes
- The Location sections now load Google Maps from a key in the module config. Set
googleMapsApiKeyand the map renders — a storefront needs no Maps plugin of its own.export default defineNuxtConfig({ modules: ['@laioutr-app/ui'], '@laioutr-app/ui': { googleMapsApiKey: process.env.GOOGLE_MAPS_API_KEY, }, });
The key reaches the browser, so restrict it by HTTP referrer in the Google Cloud Console. The Maps script loads on the first page that shows a map, not on every page. Without a key the sections keep their search, list and filters, the map area stays empty, and the browser console names the missing option.
Breaking: the sections no longer read a$googleMapsApipromise from the Nuxt app. A storefront that provides one must move the key into the module config and delete its plugin.// Before — storefront plugin export default defineNuxtPlugin(() => ({ provide: { googleMapsApi: loader.load().then(() => window.google) }, })); // After — nuxt.config.ts '@laioutr-app/ui': { googleMapsApiKey: process.env.GOOGLE_MAPS_API_KEY }
Patch Changes
- The
LocationFindersidebar no longer grows past the component frame on desktop. Its height now follows the--location-finder-max-height-lgcap, so the location list scrolls inside the sidebar instead of running out of the frame. Bothfull-widthandboxedare affected. The mobile layout is unchanged.
2.22.0 - 2026-08-28
Minor Changes
CommonSwipernow hydrates once it comes near the viewport instead of during the initial hydration pass, so carousels far down a page no longer initialise Swiper on load. Server-rendered markup is unchanged — a slider looks the same before it hydrates, it just does not answer arrows, bullets or autoplay yet. ImportCommonSwiperEagerfor the previous behaviour.
2.21.1 - 2026-08-28
Patch Changes
fonts.defaults.subsetsnow takes effect. Setting it previously changed nothing: every subset a provider returned reached the stylesheet, and an unused one still downloaded as soon as a single character claimed it — 36.7 KB of glyphs that never render, on every page, on one storefront. A storefront that sets nosubsetsis unaffected.
2.21.0 - 2026-08-27
Minor Changes
- Web fonts are configurable. Set
fontson the@laioutr-app/uimodule to reach the whole@nuxt/fontsconfiguration —families,defaults,providersandpreload. A value you set replaces the default, arrays included.falseskips the module, for a storefront that ships its own@font-facerules.'@laioutr-app/ui': { fonts: { families: [{ name: 'Open Sans', preload: true }] }, }
Fonts are served as woff2 only. A provider answers one request per user agent and returns every format tier at once. The legacywofffaces carry nounicode-range, so they match every character, and they are written last — which makes a modern browser download them in place of the woff2 files.
2.20.0 - 2026-08-27
Minor Changes
- The product tile pins a variant in its link only when the connector authored it. A tile in a listing, a grid or a slider produced
/<slug>?variant=<variant-id>for every product. The value came fromdefaultVariant, which most connectors derive from stock, so the same product changed its URL whenever one of its sizes sold out. A derived default now stays out of the link, and every listing and slider URL for those products changes. An authored default is a merchant's choice and does not move with stock, so the link still carries it. An explicit?variant=in a shared link, an ad landing URL or a wishlist entry is untouched.
A product detail page opened without?variant=now opens the product's default variant. It opened the first variant of the product before, whatever the connector named as the default. A default named by option values instead of an id also selects correctly now.
A colour swatch is marked as selected only for an authored default variant. A connector that derives its default — first in stock, first in the configurator — no longer moves the highlighted swatch as stock moves.ProductDefaultVariant.origincarries the distinction.
Patch Changes
- An above-the-fold image emits its preload link with
fetchpriority="high". The<img>already carried the hint, but the preload link is what starts the fetch, and it carried none — so the browser fetched the LCP image at default priority. An explicitfetchpriorityon the component still wins, on the link as on the image.
2.19.1 - 2026-08-27
Patch Changes
- The account button and the review section's login prompt redirect to customer login instead of opening a popup, and carry the current path so the customer comes back to it. Returning to that page needs a connector that reads
AuthLoginOauthAction'sreturnToinput — against one that ignores it, login still works and the customer lands wherever that connector sends them.
2.19.0 - 2026-08-26
Minor Changes
- Product tiles show the colour swatches of the product they render. The swatch row existed but was never filled, so it was always empty. Tiles read the product's option axes directly and no longer load every variant to build a listing, which cuts the data a product grid ships to the browser.
A sold-out product still disables its add-to-cart button. That state now comes from the product's default variant instead of a loaded variant entity.ProductTileBasic'ssizeVariantscarries one entry per value on the product's size axis, each addressing the variant the connector names for that size and disabled when no purchasable variant carries it. The default tile renders no picker, so the prop serves tile overrides that build their own. Where a product has more than one axis, the named variant is the connector's representative for that size rather than a shopper's full combination.
2.18.0 - 2026-08-26
Minor Changes
- A footer menu link can be restricted to specific markets. Each item in
BlockFooterMenugains a "Visible in markets" list; leaving it empty shows the item everywhere, which is what every menu authored before this release does.
This closes a gap rather than adding a concept. Markets are already available at render time throughuseMarket(), and visibility is already a schema concern throughvisibilityField— but that toggle is static, and theifcondition beside it is evaluated by Studio only, so it hides a field from the editor rather than content from a visitor. Projects serving several countries from one storefront had no way to express "this link, not in that market" and were rebuilding the filter per component.
The two pieces are exported for other components to adopt:visibleMarketsFieldfor the schema andinMarket()for the render, fromshared-fields/visibleMarkets. The stored value is the market slug, matchingRenderMarket.slug. - A product-detail URL that names several option values opens the variant carrying all of them. Any single matching value was previously enough, so
?variant=Rot&variant=XLcould open a red product in the wrong size. A URL naming one value still opens the first variant carrying it.
Patch Changes
- Fix
QuantityPickerremoving an item by mistake: it emitteddeletewhen the quantity field lost focus at the minimum value, or when the minus button stepped down to it — so clicking the quantity input of a cart line at quantity 1 removed the line.deletenow fires only for the delete button at the minimum, or a value typed below it.
2.17.0 - 2026-08-24
Minor Changes
Reviewnow reports the write-review intent instead of handling it itself. It emitssubmitwith the form data when the review form is sent, andloginwhen the visitor confirms the login prompt.
Breaking: the login prompt's primary button no longer opens a confirmation dialog of its own — it emitslogin, and the consumer decides where the visitor goes. AReviewthat is rendered without aloginlistener shows the prompt but cannot log anyone in.<!-- before: the button handled itself, and confirmed a review nothing had stored --> <Review :rating="rating" :reviews="reviews" /> <!-- after --> <Review :rating="rating" :reviews="reviews" @login="login" @submit="onSubmit" />
Patch Changes
- Added the
review.logInstring, in all seven shipped locales. The review login prompt's button was hardcoded English. SectionProductReviewsnow sends a visitor who wants to write a review to their account. The login prompt resolves through theecommerce/auth/login-oauthaction, so it works with whichever connector answers it — an OAuth authorization URL where accounts are hosted, a link to the account page where the storefront renders its own.
2.16.0 - 2026-08-24
Minor Changes
- Add French, Italian, Spanish and Polish locale bundles, so storefronts in those
languages stop rendering their UI chrome in English.
ui-kitshippedde,enandnlonly, andUiAppWrapperresolves the bundle from a literal map:const uiKitLocales: Record<string, typeof en> = { en, de, nl }; const uiKitLocale = computed(() => uiKitLocales[appLanguage.value.languageCode] ?? en);
Any other language fell through toen. A project configured withfr,it,esorpltherefore rendered every$tl()string in English — 257 messages across 33 components, including the search field, the cart, "Add to cart", "Sold Out", the filter drawer and the header's account/wishlist/cart labels. The page content was translated; the chrome around it was not.
There was no way to fix this from a consuming app: the map is a literal with no registration hook (themes have$laioutrThemes, locales have no equivalent), so an app could only have patched it by re-implementingUiAppWrapper.fr.ts,it.ts,es.tsandpl.tsfollow the existingextendLocale(en, …)shape and cover all 257 keys — verified key-for-key againsten.ts, with every{placeholder}token preserved. Register is by adding them to the map; no consumer change is needed.
Two pre-existing gaps in the shipped bundles are closed at the same time, since they are the same defect:dewas missingsliderNavigation.play/.pause, andnlwas missing the wholemediaFeedblock. Both silently fell back to English.
Polish avoids the plural forms a static string cannot express —filterStarsandshowProductsread "Gwiazdki: {count}" and "Pokaż produkty ({count})" rather than a genitive that would be wrong for 2–4.
Theuiplayground'sfr-CH,fr-FRandit-CHentries pointed atenbecause no bundle existed; they now use the real ones.
Not a behaviour change for existing projects:de,enandnlresolve exactly as before, and a language with no bundle still falls back toen.
Patch Changes
- Breaking: The cart sheet marks a line whose merchandise cannot be bought.
CartListItemtakes a newisSoldOutprop, renders an "Out of stock" badge, and carriesdata-unavailableon its root for styling.ConnectedCartSheetsets it from the cart item's availability component.
A custom locale must supply the newcart.itemSoldOutkey.
2.15.0 - 2026-08-24
Minor Changes
MenuSideBySidegains abelow-separatorslot on the root level.
The area under the root drawer's separator renderedMobileMenuListItems built from therootMenuItemsprop and nothing else, so a storefront whose quick links are styled buttons rather than list rows had no way to put them there — the only route was forkingMenuSideBySideandMenuSideBySideRootdownstream.
The slot sits between the separator and the menu items and is forwarded at every level that sits between a consumer and it —BlockMenuSideBySidetoMenuSideBySidetoMenuSideBySideRoot— each time only when a consumer actually provides it. All three components now declare their slots withdefineSlots. Nothing renders differently without it.
Patch Changes
BrandHero: a background colour now shows when no background image is set.fallbackColorwas passed toMediaStageall along, butfinalMediafell back to the theme's decorative hero SVG wheneverbackgroundwas empty — so the colour was painted behind an opaque image and only ever visible throughcolorMode: 'plain'. Setting it therefore looked like nothing happened. An explicit colour now takes precedence over the theme default; the default still applies when no colour is set.
The field is relabelled "Background Color" inSectionBrandHero, with a description saying when it applies. The prop name is unchanged.
Behaviour change: an instance that has a background colour AND no image switches from the theme graphic to that colour. That is the reported bug, but it is visible — clear the colour to keep the graphic.BrandHero's description also becomes rich text. The field was atextarea, which takes typed copy only — and on a brand page its whole point is to carry the brand's own description from the catalogue. It is nowrichtext, the type Studio offers a data source on and the same oneBlockTextuses for its body, so an editor can bind it to the query instead of retyping it per brand.
The component'sdescriptionprop widens tostring | HtmlFragmentand renders a fragment throughRichContent, exactly as ui-kit'sCarddoes for its body copy. Callers passing a plain string keep working unchanged.- The product tiles in a Product Grid show their add-to-cart button, brand, description, flags and rating again.
ConnectedProductTileBasicdeclared each toggle as an optional boolean without a default, so Vue cast an unset one tofalseand the component forwarded thatfalseon toProductTileBasic— beating the tile's owndefault: true.BlockProductsListingpasses none of these toggles, so every tile in a grid rendered with all five switched off. A toggle a caller does not set now stays unset and the tile decides.
2.14.0 - 2026-08-21
Minor Changes
- Captions gain a Text Size setting, wired the same way heading and subline already are.
CaptionFlaghad its size hardcoded —caption-sfor the plain variant,caption-xsfor boxed — so no consumer could change it. It now takes an optionalsize, and the variant-derived value stays the fallback, leaving existing callers unchanged.
The option list is a deliberate SUBSET of the shared text-size scale: the caption utilities come from the--caption-*tokens, which stop atxl, so2xl+ are not offered rather than offered and silently ineffective.
The setting lands on the sharedcaptionVariantstyle decorator, so every section and block that already uses it gets the control: Banner Basic, Banner Integrated, Banner Showcase, Product Slider Showcase, Hero Slider (per slide caption) and the Banner Basic block. The schema keeps the canonicaltextSizename with itsautosentinel and one shared mapper (resolveCaptionVariant) translates it to ui-kit'ssize—autobecomes absent, which is what letsCaptionFlagfall back. ProductListingGridaccepts aproductTilescoped slot, bound as{ product }, matching the slotProductSlideralready exposes. Its fallback is the tile it rendered before, so an existing consumer is unaffected.CartSummaryBoxemitscheckoutwhen its call to action is clicked, andCartSheetforwards it. The button keeps itshref, so navigation is unchanged.SocialShareemitssharewith the platform a visitor picked, andBlockSocialSharereports it as aweb/shareevent.
The payload carriesmethodalone — the platform id (facebook,x,linkedin,pinterest,email). The block binds to no entity, so what was shared is the page, and the ambient page context already puts its url, path and type on every event.
Every button is an outbound link to the platform's own dialog, so the event marks the click rather than a completed share. A visitor can still abandon it.- Product lists, product pages, the cart and search now emit commerce events. No configuration: the instrumentation is always on and has no Studio surface.
- Product sliders and the product grid report
view_item_listonce they are on screen, not merely rendered. - A tile reports
select_itemon a navigation andadd_to_cartafter the mutation resolves. - A product page reports
view_itemon load and again on every variant switch. - The cart reports
view_carton open,begin_checkoutfrom the call to action, and a removal or an add for every quantity change, sized to what moved rather than what remains. - The header reports a submitted
search, and a search results page reportsview_search_results.
Raw entities go intotrack()and the projector registry turns them into wire shapes, so no call site builds a payload by hand. - Product sliders and the product grid report
Patch Changes
SectionBannerBasiccan now be made clickable across its whole area.BannerBasichas carried the full-area link all along — a transparentNuxtLinkover the banner, enabled byisFullAreaLink— but the section never passed anhrefand had no link field, so the overlay could never render there.BlockBannerBasicalready has both. This brings the section in line with its own block: aLinkfield in Content, resolved throughlinkResolverand handed toBannerBasicashref.
The existing constraint is unchanged: the overlay only renders when no CTA is configured, since a CTA inside a full-area link would nest one anchor in another. A banner with a CTA keeps exactly the behaviour it has today.- Breaking:
SectionCategoryCardSliderno longer pads itself by default.
The section overrode the sharedpaddingFieldwithdefault: 's', so every instance started with vertical section padding nobody asked for — and a section that pads itself cannot be placed flush against its neighbour. It now uses the shared field unchanged, whose default isnone;marginwas alreadynone.
Behaviour change: existing instances that never touched the Padding field lose that padding. Set it toSexplicitly to keep the old look. Seven other sections still carry the samedefault: 's'override and are deliberately left alone here. - Fix a crash on a hero slide that shows a caption.
SectionHeroSlidercalledresolveCaptionVariantwithout importing it, and ui-app registers no auto-import directory, so building the slide props threw aReferenceError. Only a slide with at least one visible caption reached the call, which is why it stayed latent. - Price the variant a commerce event names, not the product it belongs to.
A product's own price is a "from" price across its variants. The projection already reported the active variant's sku and name, so pairing those with the product's price priced one variant as another — on a product with a wide variant spread it understated the amount by an order of magnitude.projectAnalyticsProductnow reads the active variant's prices and falls back to the product's only where the variant has none.
A variant price never carries the product's strike-through price: that belongs to a different price point, and carrying it across would invent a discount.
A tile'sadd_to_cartvalue is computed from the variant actually being added, taken from the id in the add-to-cart payload rather than from the product.
2.13.0 - 2026-08-19
Minor Changes
MediaVideoemits amilestoneevent as playback crosses each quarter of the video, carrying{ milestone, currentTime, duration }wheremilestoneis the fraction reached —0.25,0.5,0.75or1. Each fires once per mounted player.useMediaMilestones(playback, onMilestone, config?)is the headless detector behind it, for wiring the same behaviour to a non-native player. It takes reactivecurrentTimeandduration, knows nothing about media elements or analytics, and accepts{ fractions }to replace the default quarters.useMediaMilestones({ currentTime, duration }, (event) => report(event), { fractions: [0.1, 0.9] });- Make
BlockMedia's Sizing setting apply to the image only, not to the image plus its description.Sizerwrapped the whole block, so an Aspect Ratio of 16/9 was the ratio of image and caption together: the image lost exactly the height the caption took, and a longer caption shrank the image further. The same held for Responsive Aspect Ratio and for both fixed-height modes.MediaPreviewnow takes an optionalsizingand applies it to its media box, with the description below it and outside the sized box — which is where a caption belongs regardless of the mode.BlockMediapasses the field straight through instead of wrapping itself.
Backwards compatible: withoutsizing,MediaPreviewkeeps its previous geometry and fills whatever box it is given, caption included. Consumers that size the outer box themselves (as the stories do) are unaffected. - Let
CartSheetbe told how many items its header pill should report, via a new optionalcartContent.itemCount.
The pill was hardcoded tocartContent.cartListItems?.length— the number of LINES, which only equals the number of items while every line has quantity 1. A storefront whose header cart badge counts units (the usual choice, and whatCartBase.totalQuantityprovides) therefore showed two different numbers for the same cart: a cart with two products at quantity 3 read 6 in the header and 2 in the drawer.itemCountfalls back to the line count when it is not passed, so existing call sites are unaffected. HeroSlide,BannerBasic,BannerIntegratedandBannerShowcaseaccept anhrefprop that turns the whole component into a single link, rendered as a transparent full-area overlay. It activates only when no CTA button is present, so a component is either button-driven or fully clickable — never both (which would nest interactive elements inside an anchor). Unset by default; existing usage is unchanged.
The Hero Slide, Banner Basic, Banner Integrated and Banner Showcase blocks gain a Link field that makes the whole component clickable.- Add
BlockBasicTable— a Studio block for theBasicTablemolecule.
The primitive has been in ui-kit since the DescriptionList rename, but only domain compositions could reach it: there was no way to author a plain key-value table in the Studio. Rows are an authored array of label/value pairs, and the block exposes the primitive'soutlined/plainvariant as its Design style.
Standalone, so it is offered in every section rather than only in the ones that allow-list it. - The Mega Menu, Mobile Menu (Shop), and Side-by-Side Menu blocks gain a Start Level control that descends the fetched category tree by the configured number of levels before rendering. This lets editors skip a synthetic upstream root node (e.g. a Magento "Root" category) so the first business-facing level becomes the top level, without changing the query or connector. It applies only to the queried Root Menu tree — manually authored navigation items are unaffected. Default
0preserves the current behaviour. - Hide
BlockPaginationwhen the result set fits on a single page, with ashowOnSinglePagetoggle to keep it.
A lone "1" button that cannot lead anywhere reads as a broken control, so the block now renders nothing when there is one page or none. The page count is derived frompagination.total / pagination.limit; alimitof 0 is treated as unpaged, i.e. one page.
Behaviour change: storefronts that showed the controls on single-page listings will stop doing so. SwitchshowOnSinglePageon in the block's Rules group to keep the old rendering — worth doing where a listing's height should not change as filters narrow the result set, since the control appearing and disappearing moves everything below it.
2.12.0 - 2026-08-13
Minor Changes
- Add
MediaFeed, a full-screen short-form media feed in the TikTok/Reels pattern: vertical scroll-snap, one item playing at a time, an action rail, a mute toggle, and a read-more sheet. Only the item in view is interactive — the others areinert, so tabbing never reaches a control that is off screen, and each item carriesdata-state="active" | "inactive"to style or query against. Video items carry a play/pause affordance over the whole backdrop; image items have nothing to pause and carry none.
Render it from props (items+v-model:open) or drive it from anywhere on the page withuseMediaFeedStore():const { openFeed, setItems } = useMediaFeedStore(); // Seed on mount so a shared link resolves before anyone opens the feed. setItems(reels); // A tile opens at a position or at an item id. openFeed(reels, 'reel-1235512');
Browsers only allow autoplay while muted, so the feed starts silent and the top bar carries a mute toggle back to sound — bound tov-model:muted, and left out when no item is a video. Replace it through thetop-bar-endslot.
SeturlParamto sync the item in view to the URL —?reel=<id>— so an item can be shared and the back button closes the feed instead of leaving the page. The parameter carries the item id, not its position, so a link survives the feed being reordered.
The compound parts are exported for composition:MediaFeedIconButton,MediaFeedTopBar,MediaFeedDesktopNavigation,MediaFeedItemInformation,MediaFeedDisclosureTeaser,MediaFeedOffCanvasSheetandMediaFeedItem. Action rails and CTAs are scoped slots, because favourite state and counts come from your own queries rather than from the item data.MediaFeedIconButtondraws an engagedAnimatedIconin red, so a favourited heart reads as favourited while the rail's other icons stay on the button's own colour. Override it per rail or per button with--animated-icon-active-color.
New inui-kitalongside it:useOverlayHistory— syncs any overlay's open state and current item to a query parameter. Opening pushes one history entry, item changes replace it, and back closes the overlay rather than navigating away. Usable for the lightbox, a cart drawer or any other overlay. Several overlays can run at once as long as each owns its own parameter.AnimatedIcon— an icon that reflects a boolean active state. On the false → true transition the two artworks cross-fade — the outgoing one springs, the incoming one grows in from a quarter size — while three copies of the active artwork fly up and outwards and fade. The motion is sized in multiples of the icon, so it holds at everysize. Overlapping bursts are kept separate so rapid repeat taps each animate in full. It reflectsdata-state="active" | "inactive", and--animated-icon-active-colorcolours the active artwork and the hearts it throws while the resting outline keeps the ambient icon colour.AnimatedButton—Buttonwith a continuous pulse, paused on hover, underprefers-reduced-motion, or viapaused.$count(1234)— compact number formatting for the current locale, honouring your i18nnumberFormatswhere you define them. The default abbreviation is CLDR's and differs by language: English shortens from a thousand (1.2K), German only from a million (1234, then1,2 Mio.), so a badge on a fixed width wants either room for the long form or a format of its own.Badgegains aglass-blackvariant for badges sitting over arbitrary media.MediaandMediaVideogainv-model:currentTime, the sibling ofv-model:paused: assigning to it seeks, and the element reports its own progress back, so a scrubber, a chapter link or a rewind needs no template ref.
(DEV-414, DEV-440) - Videos rendered through
Mediawait until they are near the viewport before fetching, so a page or feed carrying several no longer loads all of them up front. Videos under aMediaAboveTheFoldprovider are unaffected; elsewhere setvideoPreloadto override it per video. (DEV-414)
Patch Changes
- Fix the filled heart icon (
essentials/heart-filled) rendering atsize="s"andsize="m". - Keep product tiles rendering when a variant is missing the components they read
A query result can legitimately carry a product variant whose components have not been resolved. The shared product-tile mapper readoptions.selectedandavailability.statusoff every variant, so a single such variant took the whole Product Grid, Product Slider or Product Slider – Showcase down behind its error boundary. Those variants are now skipped, and the tile renders with a momentarily shorter size list instead.
2.11.0 - 2026-08-11
Minor Changes
- Dutch locale for ui-kit
#ui-kit/locale/nljoinsenandde, covering every message key the kit defines — cart and checkout, the add-to-cart and sold-out states, search, filters, reviews, the withdrawal form, product-detail pricing, the location finder, and the newsletter.
A storefront whose language resolves tonlnow renders ui-kit's own chrome in Dutch. Until now onlyenanddeexisted, so a Dutch market showed English controls — "Add to Cart", "Sold Out", "Select an option" — around product data in its own language. Markets in other languages are unaffected and still fall back to English.
Patch Changes
CategoryCardSliderandCategoryCardGridrender theirnodesprop and their default slot together,nodesfirst. Previously the default slot replacednodesentirely, and because the Category Card sections always pass one,nodesnever rendered through them at all.
So a Category Card Slider bound to a category data source — e.g. the current category's Child Categories — showed an empty slider. It now shows one card per category, with any manually placedBlockCategoryCards after them.- The Filter Bar's header and quick-filter rows line up with the surrounding page content on large screens. Their horizontal padding was a fixed
--spacing-ml, narrower than the page container's own padding, so both rows sat slightly inside the content column fromlgupwards. They now read--container-padding, the same token the container uses, and follow it at every breakpoint abovelg. - The add-to-cart button on tiles in the Product Slider Showcase section did nothing when
clicked — no spinner, no toast, no cart request — while the same button in the plain
Product Slider section worked. Both now behave identically: per-tile loading state,
success and error toasts, and the cart count updating.
ProductSliderShowcasegains an optionalproductTileslot for callers that render their own tile; leaving it unfilled keeps the current one. - Breaking:
AuthLoginOauthAction(ecommerce/auth/login-oauth) no longer returns a bareauthorizationUrl. It returns a discriminated union: the OAuth authorization URL when the customer has no session, and aLinkto their account when they do. Narrow ontypebefore reading either.// Before const { authorizationUrl } = await mutateAsync(undefined); window.open(authorizationUrl, '_blank', 'popup,width=500,height=500'); // After const result = await mutateAsync(undefined); if (result.type === 'oauth') { window.open(result.authorizationUrl, '_blank', 'popup,width=500,height=500'); } else { await navigateTo(resolve(result.link), { external: true }); }
Clicking the account icon in the shop header no longer restarts the OAuth flow for a customer who is already signed in — they go straight to their account. The Shopify connector answers from its own cookies without a network call, and returns before writing anything, so an account redirect in one tab can no longer invalidate a login in progress in another.
The account destination is aLinkrather than a URL string, so the frontend resolves it. Shopify returns an external link to its hosted account page; a connector whose account is a storefront page returns apageTypelink from the same contract, with no change to the header.
The login popup no longer fails silently in Safari and Firefox. It is opened while the click is still a trusted gesture rather than after the request that resolves its URL, and falls back to a full-page redirect where a popup is blocked anyway.
A signed-in customer's cart now carries their identity, so checkout opens authenticated and the resulting order attaches to their account. A cart created while signed in is associated at creation; one filled beforehand is associated on the next cart read, so signing in mid-session no longer leaves the cart anonymous.
2.10.3 - 2026-08-06
Patch Changes
- Add
ProductDetailButtonGroup, a vertical stack of full-width call-to-action buttons (label, link, variant, size) for the product detail page. - Add
BlockProductDetailButtonGroup, a block wrappingProductDetailButtonGroupwith a repeatable buttons array built on the shared button field set. Placeable only inside the Product Detail section's content slot.
2.10.0 - 2026-08-03
Minor Changes
ContainergainscolumnsTabletandgapTabletprops, adding a tablet stage between mobile and desktop. Column count and gap now resolve in three stages — mobile (below 600px), tablet (600–1279px), desktop (1280px and up) — one breakpoint scheme shared with the Sizer. Both fall back to their mobile value when unset (columnsTablet→columnsMobile,gapTablet→gapMobile), so containers that only configured mobile and desktop render as before at those two stages.
Breaking (intended): the desktop gap now switches at 1280px instead of 800px. Containers with an explicit desktop gap and no tablet gap now use the mobile gap between 800–1279px where they previously used the desktop gap — the same breakpoint unification the Sizer already applies. Column behaviour is unaffected. (SUPPORT-18)
The Container section adds Tablet columns and Gap (Tablet) controls, so editors can set a distinct column count and gap for tablet viewports (600–1279px); previously the tablet band inherited the mobile columns and, above 800px, the desktop gap.- Banner Basic, Banner Integrated and Banner Showcase — both their section and block variants — gain a Background Color control that fills the inner banner tile (the surface behind the copy, shown when no background image is set). This is independent of a section's outer Backdrop background, so the band around the banner and the banner tile itself can be coloured separately. Text and icon contrast adapt automatically to the chosen colour; unset banners keep their pale theme fallback. (SUPPORT-31)
- All three CTA banner sections — Banner Basic, Banner Integrated and Banner Showcase — gain a Sizing control (fixed height, responsive height, or aspect ratio) to give the banner a definite height. Banner Basic's Content Alignment becomes two-dimensional (vertical + horizontal, e.g.
bottom-left) and Banner Integrated gains a Vertical Alignment control, so content can now sit at any edge — including the bottom. Vertical alignment only takes effect once the banner has a definite height (with Sizingautoit stays at the top) and composes with the existing Content Padding control. Existing sections render unchanged: stored alignment is preserved and Sizing defaults toauto. (DEV-343, SUPPORT-16)
2.9.2 - 2026-08-02
Patch Changes
- Fix filter off-canvas accordion opening the wrong panel.
FilterOffCanvasAccordionItemusedactiveValue(the display text) as its rekaAccordionItemidentity, so every unselected list/range filter sharedvalue=""and clicking any trigger opened the first one, overlapping the others. The item now takes a dedicated, uniquevalueprop for identity, withactiveValueused for display only.
2.9.1 - 2026-08-02
Patch Changes
ProductTileBasicaccepts an optionalsizeVariantsprop ({ value: string; label: string; disabled?: boolean }[], default[]), carrying each product's selectable size options through to the tile. The prop is data-only — the default tile renders no size picker — so overrides can render their own selector from it without changing existing tiles.- Expose per-product size variants on the product-tile pipeline.
productTileMappernow emits asizeVariantsarray ({ value, label, disabled }, keyed by variant id,disabledfor sold-out sizes) derived from the variants already fetched by the shared product-tile query; products without a size option yield[]. The shared query's variant limit is raised from 5 to 30 so full size runs are no longer truncated. Consumers ofProductTileBasic(Product Slider, Product Slider Showcase, and tile overrides) receive this data without forking the query or mapper.
The Product Slider Showcase now defaultsShow Product Flagsto on, matching the standard Product Slider (new sections only; existing configurations are unaffected).
2.9.0 - 2026-08-01
Minor Changes
- Add
BlockProductSlider— the Product Slider available as a standalone block so it can be placed inside a container slot, not only as a full-width section. Its configuration, data binding and rendering are identical toSectionProductSlider.
2.8.6 - 2026-08-01
Patch Changes
- Add a
Child Categorieslink (Category → Category) and let the Category Card Slider render categories from a data source.- canonical-types: new
ChildCategoriesLinktoken (ecommerce/category/child-categories). - shopify: implements the link by reading the navigation menu (new
categoryMenuHandleoption, defaultmain-menu), locating the source collection's node and returning its collection children as inlineCategoryentities (title/slug + collection image). Localized via@inContext. - ui-app:
SectionCategoryCardSlidergains an optional Category external-source query (categories) whose entities render as cards. Manual slot cards still take precedence, so existing usage is unchanged. Bind it in Studio to the current category'sChild Categoriesto show its sub-categories.
- canonical-types: new
2.8.1 - 2026-07-23
Patch Changes
- Add the missing Product Slider preview image so the Studio component picker shows a preview for it. The
previewSrcalready pointed toSectionProductSlider.png, but that asset was absent, leaving the entry with a broken preview link.
2.8.0 - 2026-07-20
Minor Changes
- Breaking: The
aimetadata on section and block definitions is now a typedAiComponentMetadataobject with exactly two optional fields —description(agent-facing facts that don't fit the picker-facingstudio.description) andexamples(a worked slot/block composition for complex multi-block sections). The previous free-form{ label, description }shape, the co-locatedai-descriptions/*.mdfiles, and theaiDescription()helper are removed. An absentaiobject is the normal state:studio.descriptionplus the field/slot schema carry the load for agents, and all built-in section/block descriptions have been enriched with the facts that previously lived inaimetadata. Prescriptive guidance fields (useWhen,avoidWhen,constraints,neverWith,pairsWellWith,typicallyFollowedBy,supports, …) are gone — evals showed they measurably degrade agent page composition.// before ai: { label: 'Button', description: aiDescription('BlockButton') } // after — most definitions: // (no ai key at all; put facts in studio.description) // after — only where a fact doesn't fit the picker text or an assembly is non-obvious: ai: { description: 'Currently a progress display only — it does not yet fetch the next batch.', examples: 'gallery slot: BlockProductMediaGallery; content column: BasicInfo, PriceInfo, CartButton in on-page order.', }
Patch Changes
- Add
studio.packageandstudio.kitmetadata to section/block definitions for the Studio picker tabs and type filter. Every built-in section and block description has been reviewed and enriched so the picker text carries the component's non-obvious capabilities and data-binding facts.
2.7.0 - 2026-07-14
Added
- UI Kit — Video sources now support a
focalPoint, mirroring image sources. The built-inMediaVideorenderer applies it asobject-position(per viewport, withcenter centeras the fallback) so the important region stays in frame when the video is cropped byobject-fit: cover.
Fixed
- UI Kit — Registered the
$unitPriceformatter as an auto-import. It is now available both as the$unitPrice(...)template global and from#imports, matching the other formatters. Previously it was only wired into the runtime plugin, so consumer typechecks reportedProperty '$unitPrice' does not existon templates that used it even though it worked at runtime.
2.6.0
Added
- UI Kit —
TableOfContents, a numbered navigation list with scroll-spy active-item tracking, and the underlyinguseScrollSpycomposable. - UI — An optional, auto-generated table of contents for
ArticleDetail, built from the body's H2 headings, with a sticky sidebar on desktop and a collapsible accordion on mobile. - UI App — A
showTableOfContentsStudio feature flag onSectionBlogPostDetail(default on) to toggle the new auto-generated table of contents. - UI — The EU withdrawal-button feature.
WithdrawalFormrenders a statutory right-of-withdrawal (Widerruf) dialog form with the three legally-permitted fields (name, order/contract reference, email) and client-side validation.BlockWithdrawalButton(ui-app) makes it a Studio-configurable button that opens the form in a dialog and dispatches theecommerce/legal/withdrawalaction.FooterandSectionFootergain abottomContentslot so the button (or any block) can sit in the footer's bottom row, and awithdrawallocale namespace (en/de) supplies the default labels and validation messages.
Changed
- UI Kit —
openDialogandaddToastnow accept a ref or getter for their content (MaybeRefOrGetter<…>), so a dialog's or toast's title, description, and other fields can stay reactive after it opens — e.g.openDialog(() => ({ title: t('...'), ... }))re-renders on locale or prop changes. Passing a plain object works exactly as before.addToastadditionally returns the new toast'sid(matchingopenDialog), and the toaster store'stoastsis now a resolved computed rather than a writable ref (it was already only read internally).
Fixed
- UI Kit — Form inputs placed inside
<Field>now inherit the field'sdisabled,readonly,required, andinvalidstate. Previously these were silently dropped (onlyidanderrorMessagepropagated), so a control inside<Field disabled>or<Field :invalid>stayed enabled/valid. A control is now in that state if either its own prop or the surrounding<Field>declares it, matching native<fieldset disabled>.
2.5.0
Added
- UI —
TableProductSpecifications, a product specification sheet built onBasicTable. Rows carry alabeland a typedvalue(string | number | boolean | Measurement | Money) that the newTableProductSpecificationsValuesubcomponent formats per locale — numbers viaIntl.NumberFormat, booleans as localized Yes/No (tableProductSpecifications.{yes,no}),Measurementvia$measurement,Moneyvia$money. An optional per-rowsectionNamegroups rows into sections (oneBasicTableper section, first-seen order); absent ⇒ a single ungrouped table. See Product Specifications Table. - UI App —
BlockTableOpeningHoursandBlockTableProductSpecifications, two standalone Studio blocks. Each can be filled from a query (a Location's opening hours / a Product'sspecifications) or with manually entered rows, switchable via a Data Source toggle, and exposes anoutlined/plainstyle toggle.BlockTableProductSpecificationsforwards the product's typed specification values unchanged and supports per-row sections (an optional Section field on manual rows) for grouped output.
Changed
- UI Kit — Breaking:
DescriptionListwas renamed toBasicTableandDescriptionListItemtoBasicTableRow.BasicTablenow renders as a semantic description list (<dl>with<dt>/<dd>rows) instead of presentational<div>s, and the label column is overridable via the public--basic-table-label-colcustom property. The grid/subgrid layout and the CSS surface (.basic-tableroot class, BEM classes) are unchanged. See Basic Table.- import DescriptionList from '#ui-kit/components/DescriptionList/DescriptionList.vue'; - import DescriptionListItem from '#ui-kit/components/DescriptionList/DescriptionListItem.vue'; + import BasicTable from '#ui-kit/components/BasicTable/BasicTable.vue'; + import BasicTableRow from '#ui-kit/components/BasicTable/BasicTableRow.vue'; - UI — Breaking:
OpeningHoursWeeklyTablemoved out of UI Kit into@laioutr-core/uiasTableOpeningHours, now built on theBasicTableprimitive and gaining anoutlined/plainvariant. Its weekday-grouping behavior is unchanged. See Table Opening Hours.- import OpeningHoursWeeklyTable from '#ui-kit/components/OpeningHoursWeeklyTable/OpeningHoursWeeklyTable.vue'; + import TableOpeningHours from '#ui/components/TableOpeningHours/TableOpeningHours.vue';
2.4.0
Added
- UI Kit —
<Media>gains aplaybackmode for video and audio.playback="background"is a one-switch decorative loop —autoplay,muted,loop,playsinline, anddisablePictureInPictureon,controlsoff — replacing the hand-spelled cluster;interactive(the default) is the native player. Each attribute still has its own prop that overrides the mode.<Media>now exposesv-model:pausedso a consumer can render and position its own pause control (WCAG 2.2.2), and it suppresses autoplay underprefers-reduced-motion: reduce, seeding the paused state from that decision.MediaStageadoptsplayback="background"for video backgrounds; it ships no pause control of its own, so a consumer that needs one renders the background with<Media>directly and bindsv-model:paused. See Rendering video and audio. - UI Kit —
GridFillgainsjustifyandalignprops ('start' | 'center' | 'end', both default'start') controlling main- and cross-axis item alignment. The values are stated logically: undersizing="greedy-first"the component internally reverses its axis to pack growing items into the first row, and un-mirrors the alignment sostart/endstill resolve to the intended physical direction (centerstays symmetric). Consumers never see the reversal. - UI Kit —
Inputgains aplainvariant alongsideoutline(default) andsubtle. It paints resting, hover, focus, disabled, and error backgrounds and borders from the--input-field-plain-*design tokens, for fields that sit on an already-styled surface and shouldn't carry their own box. - UI Kit —
EmailInputFormandEmailInputFormDisplaygain a forwardedvariantprop ('boxed' | 'plain'). Theplainvalue renders the email field with the new plainInputstyling, so footer and popup signups can sit flush on a colored surface. - UI Kit —
NavigationMenuTextItemgains an optionaltextColorprop accepting a resolved CSS color string. When set it overrides the surface-tone label color for that item; unset, the item inherits the surrounding surface tone. - UI —
MenuBasicItemandMegaMenuItemgain an optionaltextColor(ColorFieldValue) that overrides the surface-tone label color of a top-level menu item or trigger.NavigationNode(theMenuSideBySidedata shape) carries a matchingtextColorfield. All three resolve the color viacolorValueToCss()and fall back to the surface-tone cascade when unset. - UI —
ArticleDetailgains an optionalshowSocialShareprop (defaultfalse) that toggles the trailingSocialSharerow. (ArticleDetailis the renamedBlogPostDetail— see Changed.) - UI App —
SectionGlossaryDetail, the reading view for a single glossary entry. It binds theGlossaryentity'sbaseandcontentcomponents and renders them through the sharedArticleDetailcomponent (social share off). - UI App —
BlockProductDetailCouponBox, a non-standalone block placeable in the Product Detail section's content slot. It wrapsCouponBoxwith editor-configurable text, code, optional discount badge, and icon, and hard-wiresaction="copy"so shoppers copy the code to the clipboard. Avarianttoggle switches between the default and promotion-code presentations. - UI App — Banner sections gain a Design → Layout Content Padding control.
BannerBasic,BannerIntegrated, andBannerShowcasetake a newcontentPaddingprop ('default' | 'm' | 's' | 'none') with matching--content-padding-{m,s,none}modifier classes; an unset value preserves the existing padding. - UI App —
SectionMediaTextgains a Design → Layout Padding control (none/S/M/L).MediaTexttakes a newinnerBlockPaddingprop ('s' | 'm' | 'l') forwarded toBackdrop; unset defaults to none, so existing sections render unchanged. - UI App —
BlockMenuBasic,BlockMenuSideBySide, andBlockMegaMenuexpose a per-item text-color picker via anas: 'style'decorator on author-typed item titles. The picked color is applied as the item label'stextColor; entity-driven (CMS query) items do not expose the picker.
Changed
- UI — Breaking:
BlogPostDetailwas renamed toArticleDetailand promoted to the shared reading view behind both the Blog Post and Glossary sections. There is no backwards-compatible alias.- import BlogPostDetail from '#ui/components/BlogPostDetail/BlogPostDetail.vue'; + import ArticleDetail from '#ui/components/ArticleDetail/ArticleDetail.vue'; - <LBlogPostDetail :title :media :body /> + <LArticleDetail :title :media :body /> - UI App — Breaking:
SectionGlossaryListwas renamed toSectionGlossaryListing. The Studio component string changed and no data migration shipped, so stored sections must be re-added.SectionBlogPostDetailandSectionGlossaryDetailboth now render through the sharedArticleDetailcomponent. - UI Kit — Breaking: the
sectionGlossaryListlocale namespace was renamed tosectionGlossaryListing(keysectionGlossaryListing.heading; EN "Glossary" / DE "Glossar"), matching the section rename. Custom locales overriding the old namespace must rename it. - UI App —
SectionLocationFinder's container-style control migrated to the sharedcontainerStyleFieldtoggle. The field name (containerStyle) and stored values ('full-width' | 'boxed') are unchanged, so the migration is data-safe.
Fixed
- UI — Un-mirrored
Containeralignment under the "Greedy First" sizing mode.Containernow maps its 2DalignmentontoGridFill's logicaljustify/alignprops, and the axis-reversal handling moved intoGridFillitself, so alignment resolves consistently across all sizing modes. - UI Kit —
AlphabeticalIndexgroup content is now top-aligned (align-items: flex-startinstead ofcenter), so columns of uneven height line up along their top edge. - UI Kit —
.location-finder--boxednow clamps to--container-max-widthand centres withmargin-inline: autofrom the--lgbreakpoint up, so boxed finders sit centered on desktop instead of stretching full-width. - UI App —
BlockButtonset to Hug width now respects its sizing inside Media & Text; the column'salign-itemschanged fromstretchtoflex-startso a hug button no longer stretches to the full column width. - UI —
NewsletterRegistrationmedia column no longer collapses, and its email field now uses the plainInputstyling. - UI Kit —
LocationFinderMappre-bundlesvue3-google-mapvia ViteoptimizeDeps, resolving ESM export failures in dev.
2.3.0
Added
- UI —
BlockProductDetailEnergyLabelfor the Product Detail page. Renders the EU energy efficiency label: an inline energy-class badge (opening the full label in a lightbox) and an optional product data sheet link next to the product information. The block queries the product's variants for the newProductVariantEnergyLabelcomponent and renders the label of the selected variant (resolved viauseProductVariantContext, falling back to the first variant). It is non-standalone and placeable in the Product Detail section.
A newProductVariantEnergyLabelentity component (@laioutr-core/canonical-types/entity/product-variant) defines the per-variant shape:badge: Media— small inline energy-class badge image (A–G).label: Media— full-size energy label image, opened in a lightbox.title?: string— human-readable title (e.g. "Energy class A++"), used as the alt text on the badge image. Optional: consumers fall back to a generated name or a translated default.energyClass?: string— energy efficiency class (e.g. "A", "A++"). Free-form string, not an enum, so the type survives the EU periodically rescaling its class vocabulary (regulation 2017/1369).energyClassScaleMax?: string/energyClassScaleMin?: string— the most and least efficient class on the product category's regulated scale.eprelRegistrationNumber?: string— identifier for the variant's entry in the EU EPREL registry.dataSheetLink?: string— optional URL of the product information sheet / data sheet PDF.
The component lives onProductVariantbecause each EU energy label is registered per commercial model in EPREL, keyed by the model identifier / GTIN — which is variant-level. Variants of the same product can carry different energy classes. Adapters (shopify, shopware, ambiendo, etc.) populate this component when the underlying variant carries an EU energy label; variants without the data omit the component and the block renders nothing. Until adapters implement the resolver, the block is placeable but renders nothing in storefronts.
A newEnergyLabelcomponent renders the badge image, lightbox trigger, and optional data sheet link. Props:badge: Media,label: Media,title?: string,dataSheetLink?: string, pluswidth/heightfor the badge image. Whentitleis omitted, the alt text falls back to thepdp.energyLabeltranslation. - UI App —
BlockProductDetailRatingfor the Product Detail section. Renders the product's average star rating, the rating value (x/maxRating), and the review count. The product detail query now fetches theProductRatingcomponent so any PDP block can readproduct.components.rating. - UI Kit —
Countdowncomponent (#ui-kit/components/Countdown/Countdown.vue) and auseCountdowncomposable for editorial countdowns. Unit labels come fromIntl.NumberFormat, so they are localized and plural-aware automatically; theunitDisplayprop ('long' | 'short' | 'narrow') sets their verbosity.Countdownalso takes an optional frozennow(for tests/Storybook), and a purecomputeCountdown(endDate, now)helper is exported. Addscountdown.expiredandpromotionBanner.{codeCopiedTitle,codeCopiedSubline,copyCodeAriaLabel}locale entries (EN + DE).useNownow accepts an optional tick interval —useNow(intervalMs = 60_000)— and is seeded viauseState, so the shared clock renders byte-identically across SSR and hydration; consumers no longer needdata-allow-mismatchon time-dependent nodes. - UI —
PromotionBanner(moved here from UI Kit because promo-codes + checkout language are commerce-domain). Accepts headings, optional countdown (viauseCountdown), a promo-code copy button, a CTA, and a surface preset (default | pale | bright | solid) orcustomcolors with per-slot overrides (background / text / countdown / icon). It auto-promotesvariantto'custom'when anycustomColorsfield is set, and adds@vueuse/coreas a dependency. - UI App —
SectionPromotionBanner, registered in theBannerstemplate list. WrapsPromotionBannerfor canonical margin / padding / container-style chrome, with Content (heading, subline, icon, code button, CTA, countdown) and Design (styling + layout) panels. The promotion banner is configured directly on the Section — there is noBlockPromotionBanner. - UI — Plumbed
target?: stringthrough link-rendering components so consumers can choose the browsing context of editorial links. Default behaviour is unchanged whentargetis omitted (_self):BrandList—BrandListItem.targetcarries through to the per-brandNuxtLink.HeaderBasic— newlogoTargetprop on the logo link.HeaderShop— newlogoTargetprop on the logo link.LogoSlider/LogoGrid/LogoSliderSlide—LogoSliderSlideProps.targetforwards to the slide'sMaybeLink.TopBar—informationLinks[].targetforwards to each navigation item.
- UI Kit —
target?: stringprop onLinkTile,LinkTileBasic,LinkTileCompact,LinkTileBig, andNavigationMenuTextItem. Forwarded to the underlyingNuxtLink/MaybeLinkto open the link in a new browsing context (_blank), with_selfremaining the default. - UI App — Editors can now choose a link
target(_self/_blank, default_self) in Studio across single-link Blocks and Sections, via the newlinkTargetOptionsshared field. NewTargetfields onBlockButton,BlockCategoryCard,BlockLogoSliderSlide; per-itemTargetonSectionBrandList.brandLinksandSectionTopBar.informationLinks;Logo Link TargetonSectionHeaderBasicandSectionHeaderShop. Existing configurations are unchanged — the runtime default_selfmatches prior behaviour. - UI Kit —
sectionBrandList.headingandsectionGlossaryList.headingtranslation keys (EN: "Brands"/"Glossary", DE: "Marken"/"Glossar"). These act as the locale-aware fallback heading when an editor leaves the section'sheadingfield empty. - UI App —
SectionGlossaryList, with a newglossaryQueryshared field. Renders a shared alphabetical index of name + link entries, sourced either from a bound Hygraph data source (any app registering aGlossaryentity) or from a manually authoreditemsarray. A configurableheadingfield falls back to a locale-aware default when left empty. - UI Kit —
rel?: stringprop onButtonandIconButton. Forwarded to the underlyingNuxtLinkonly when the component renders as a link (hrefset), ignored when rendered as a<button>. Accepts any valid HTMLreltoken or space-separated combination — e.g.'prev','next','noopener','noopener noreferrer','external nofollow'. - UI App —
SectionLocationFinder,SectionLocationDetail, andBlockLocationCard.SectionLocationFinderresolves locations via the new canonicalLocationentity and renders a finder with a configurable heading, container style, and Google Maps Map ID; it also exposes alocationCardsslot for manually curatedBlockLocationCardinstances merged into the same list and map.SectionLocationDetailrenders the full location page — map, header, image gallery, info list, and a rich description.BlockLocationCardis non-standalone (only usable insideSectionLocationFinder'slocationCardsslot); cards without coordinates are silently dropped. Both sections read the Google Maps API promise fromuseNuxtApp().$googleMapsApiand render inert until the storefront registers that plugin.
Changed
- UI Kit —
Mediais now a dispatcher. Images render via the built-in image renderer (unchanged public prop API and DOM/CSS output). Video and audio now render via new built-in native renderers:MediaVideo(native<video>) andMediaAudio(native<audio>with the optionalcovershown above it). AMediavalue of any type renders out of the box with no registration. Playback is controlled by new flat props on<Media>, mirroring the HTML attributes 1:1:controls(defaulttrue),autoplay,muted,loop,playsinline(defaultfalse). These are set by the consuming Block, not carried on theMediavalue. Consumers can override the built-in renderer for a media type (e.g. for HLS/DASH or a custom player):// plugins/media-renderers.ts import { provideMediaRenderers } from '#ui-kit/components/Media/MediaRenderersProvider'; import VidstackMedia from '../components/VidstackMedia.vue'; export default defineNuxtPlugin((nuxtApp) => { provideMediaRenderers(nuxtApp.vueApp, { video: VidstackMedia, audio: VidstackMedia }); });MediaStagenow drives its background<Media>as a decorative loop (autoplay muted loop playsinline,controls={false}); a picked video plays silently and loops behind the foreground content.MediaVideosuppressesautoplaywhen the user prefers reduced motion (prefers-reduced-motion: reduce), settling on theposterframe instead. - UI App — The
media/backgroundImage/bannerImagefields of the banner and media sections and blocks now accept bothimageandvideoassets (previously image-only). Affected:BlockBannerBasic,BlockBannerIntegrated,BlockBannerShowcase,BlockCategoryCard,SectionBannerBasic,SectionBannerIntegrated,SectionBannerShowcase,SectionBrandHero(background only),SectionMediaText,SectionProductSliderShowcase.SectionPageNotFoundstays image-only (it renders the asset as a CSSbackground-image, not via<Media>). - UI Kit —
Paginationnow emits SEO-correct sequence semantics whenhrefTemplateis set: the previous/next anchors carryrel="prev"/rel="next"only on edges where the target page actually exists; on the first page the previous control renders as a<button>(nohref), and on the last page the next control does the same, so crawlers don't follow dead links to page 0 or page N+1. - UI — Breaking: Replaced
BrandListwith the genericAlphabeticalIndexcomponent — an alphabetically grouped link list with a configurableheadingand an optional per-itemcount, usable for brands, glossaries, and similar A–Z indexes.// Before interface BrandListProps { brands: { name: string; href: string; count?: number }[]; } // After interface AlphabeticalIndexProps { heading?: string; items: { name: string; href: string; count?: number }[]; }
Upgrade: import from#ui/components/AlphabeticalIndex/AlphabeticalIndex.vue, rename thebrandsprop toitems, and passheadingexplicitly (the old built-in"Brands"translation no longer renders automatically). - UI App — Breaking (
SectionBrandList): thebrandLinksschema field is renamed toitems, and a newheadingtext field is added. BothSectionBrandListandSectionGlossaryListnow render the shared alphabetical index;SectionBrandListkeeps the optional count. Stored configurations referencing the oldbrandLinksname will not migrate automatically.
Removed
- UI Kit — Breaking: the orphaned
brandGrid.titletranslation key — it is no longer referenced anywhere. Consumers that overrode this key in custom locales can delete the override. - UI Kit —
PromotionBannerhas moved to@laioutr-core/ui(promo-codes are commerce-domain). ui-kit no longer exportsPromotionBanner/types— import from@laioutr-core/uiinstead.
Fixed
- UI — Fixed component props that were silently dropped because they didn't match the target component's API:
FeaturePillListpassedleft-icontoBadge(whose prop isicon-left), so pill icons never rendered.OpeningStatusIndicator,OpeningStatusDetail,LocationCard,LocationFinder, andLocationHeaderpassedvarianttoText, which has novariantprop. Headings usingvariant="heading"rendered with the defaultbodystyling.FilterBarboundv-model:opentoFilterOffCanvas, which exposesv-model:isOpen— the off-canvas filter panel could not be opened.PromotionBannerpassed an invalidtype="text"toButton; the dead attribute was removed.
2.2.3
Added
- UI Kit —
$unitPricei18n formatter that renders aUnitPriceas a localizedprice / referencestring (e.g.13.99 € / 100 ml), composing the existing$moneyand$measurementformatters. Available as a template global alongside$money/$measurement, and used internally byVariantSelectionCard.
Changed
- UI —
PriceInfo,ProductTileBasic, andCartListItemnow render their unit price via the new$unitPriceformatter instead of inlining$money(...) / $measurement(...). No visual change — the rendered string and each component's BEM classes are unchanged.
Fixed
- UI Kit —
.radius-containedcollapsing to square corners on tall elements. Inset banners (BannerBasic,BannerShowcase,BannerIntegrated,BrandHero) now keep their rounded corners regardless of height — previously a banner taller than the viewport width (common at mobile widths) lost its rounding entirely. Full-bleed, edge-to-edge banners still render square. No action required. - UI Kit —
OpeningStatuscomponents and composables now use the sharednowclock.
2.2.2
Changed
- UI —
BackgroundAwareButton's adaptive lookup variants are nowadaptive-primary/adaptive-secondary/adaptive-ghost(previouslyprimary/secondary/ghost), so they no longer shadowButton's literalprimary/secondaryvariants. The pass-through variants (tertiary,secondary-white,ghost-*,glass-*,subtle,info,positive,danger,close-*) are unchanged.
In Studio, the newbackgroundAwareButtonFieldsshared field exposes the threeAdaptive *options alongside the existing literalButtonVariantoptions. Every section and block with a CTA now uses this field — banner sections, the sliders,BlockButton,BlockCard,BlockText, and now also the header, footer email button, 404 page, and pricing-plan card, whose CTAs newly adapt to their surrounding surface tone. Editors opt into per-surface remapping by picking anAdaptive *option.
Breaking: Custom themes that overridebackgroundAwareButtonsindefineTheme/extendThemeneed their map keys renamed accordingly (light:light:primary→light:light:adaptive-primary, same forsecondaryandghostacross alllight|dark×light|dark|brightcombos). Stored Studio CTAs where an editor previously picked "Primary" or "Secondary" will, after upgrade, render as the literal Button variant — the per-surface remap is no longer applied unless the editor re-selects anAdaptive *option. - UI Kit — Breaking:
Scrollbarnow owns its interaction and sizing logic and takesprogress(0..1) andvisibleRatio(0..1) instead ofthumbSize/thumbPosition(px), and emitsupdate:progressinstead oftrack-click/drag-start. It computes thumb size/position, handles drag and track-click, and hides itself when content fits.<!-- Before --> <Scrollbar :thumb-size="120" :thumb-position="40" @track-click="onClick" @drag-start="onDragStart" /> <!-- After --> <Scrollbar :progress="0.3" :visible-ratio="0.25" @update:progress="(progress, source) => …" />sourceis'drag'(continuous) or'click'(jump-to-position).
Fixed
- UI Kit —
SwiperScrollbarthumb mis-sizing and mis-positioning underslidesPerView: 'auto': the thumb now reflects true content size (including gaps and uneven slide widths), dragging tracks the cursor, and clicking the track animates to the nearest slide. No API change forSwiperScrollbarconsumers.
2.2.1
Changed
- UI — Caption styling on
BannerBasic: plain/boxed variant, colour scheme, and text-shadow controls — replacing the previous colour-only override. A newcaptionVariantFieldshared field bundles the controls into a single sidebar group onBlockBannerBasicandSectionProductSliderShowcase.Containerno longer strips border-radius from CTA banners in full-width containers (banners now own their own radius).
Breaking:BannerBasicreplacescaptionColor: stringwithcaptionVariant: Omit<CaptionFlagProps, 'text'>. Migrate inline:// Before <BannerBasic :caption="caption" caption-color="#ff0000" /> // After <BannerBasic :caption="caption" :caption-variant="{ variant: 'plain', textShadow: 'none' }" />
Breaking:SectionProductSliderShowcaseschema replacescaptionStyle.colorwithcaptionVariant. Existing pages with values stored undercaptionStylesilently drop them on next save — re-author the caption styling via the new Variant panel in Studio. - UI — Sizing-system consolidation. A new
Sizerprimitive (in UI Kit) and matchingsizingFieldshared schema route outer-box sizing. Banner blocks (BlockBannerBasic,BlockBannerIntegrated,BlockBannerShowcase) plusBlockMediaandBlockIframenow route outer-box sizing throughSizerinstead of bespoke per-block fields. Banner blocks also gain vertical content alignment (2D on Basic, vertical-only on Integrated).Placeholderaccepts an optionaltextprop.
Breaking:BannerBasicno longer acceptssizing/aspectRatioprops. Wrap in<Sizer>in the consuming Block.
Breaking:BlockMediadropsaspectRatio,height, andorientationschema fields;BlockIframedropsdesktopHeightandmobileHeight. Existing pages with values stored under these names silently drop them on next save — author the desired height via the newsizingfield in Studio.
Removed
- UI Kit — Breaking:
Iframecomponent removed from@laioutr-core/ui-kit. Inline an<iframe class="…" :src :title />directly and letSizer(in your Block) drive the height. - UI Kit — Breaking:
MediaPreviewno longer acceptsaspectRatio,height, ororientation. Wrap in<Sizer :sizing="…">(or any parent that provides a definite height) and letMediaPreviewfill it. Portrait orientation is now done by typing a portraitaspect-ratiodirectly in the schema (e.g.3/4).
Fixed
- UI — Fixed
ProductSliderShowcasepadding and width: the slider no longer reserves bleed padding on the wrapper, the banner respects its container, and the slider fills the remaining row width on desktop. - UI Kit —
SwiperChromemobile navigation positioning.
2.2.0
Added
- UI —
Footeraccepts two new optional props:logoPosition?: 'top' | 'bottom'(default'top') andlogoAlignment?: 'left' | 'center' | 'right'(default'left', only meaningful whenlogoPosition === 'bottom'). WhenlogoPosition === 'bottom', the top logo-column is hidden, menus span the full top row, and the logo image renders inside the bottom bar between the icons cluster and the copyright;logoAlignmentcontrols its placement. Existing behaviour is preserved at the defaults. - UI — Editors can now pick a button size per CTA across every section and block.
buttonFieldsexposes a new Size selector (xs/s/m/l, default'm'), and a newbuttonSizeOptionsexport covers inline button-size selects. Existing CTAs render unchanged unless an editor explicitly picks a different size. For ui consumers building their own CTA shapes, the following accept an optionalsizeon each CTA entry (defaults preserve previous rendering):HeroSlide,PageNotFound,BannerShowcase,ContentGrid,HeaderBasic.ctaButtons[], theContentSlider/CategoryCardSlider/ProductSlidercta types;PlanCardgainsctaVariant/ctaSize. - UI — Forwarded the new
sectionBackground(andcustomSectionBackground) props throughContainerandMediaTexttoBackdrop. Both default to'none', so existing consumers render unchanged. - UI —
HeroSlideaccepts new optional props:headingSize?: 's' | 'm' | 'l',sublineSize?: 's' | 'm' | 'l'(defaults'l'/'m'), andheadingTextShadow?: 'none' | 'soft' | 'strong'/sublineTextShadow?: 'none' | 'soft' | 'strong'(default'none'). The text-shadow values apply a layered shadow for legibility against busy hero backgrounds, resolving from the global--text-shadow-soft/--text-shadow-strongcustom properties. Defaults preserve previous rendering. - UI — Added opening-hours primitives (in UI Kit) and location-card domain components (here):
useOpeningStatuscomposable,OpeningStatusIndicator,OpeningStatusDetail,OpeningStatus,OpeningHoursWeeklyTable, plusLocationCard(list / map-popup variants) andPaymentMethodList. - UI —
NewsletterRegistrationaccepts two new optional props:caption?: string(rendered above the heading) andbody?: string(rendered as a<RichContent>block between the headings group and the form), mirroring the caption + body fields exposed byBlockText. Both are unset by default. - UI — Editor: every section AND block with a configurable heading or subline now exposes an HTML-element selector (
H1–H6/DIVfor headings;P/H1–H6/DIVfor sublines) directly next to the field via theas: 'style'decorator popup — no more separate "SEO" panel. Newly available on 13 sections and 7 blocks; section defaults: heading →h2, subline →div; block defaults: heading →h3, subline →h4. The underlying ui components (BannerBasic,BannerShowcase,BannerIntegrated,BrandHero,CategoryCardSlider,CategoryCardGrid,ContentGrid,ContentSlider,EditorialGrid,HeroSlide,LogoGrid,LogoSlider,NewsletterRegistration,PageHero,PageNotFound,ProductSlider,ProductSliderShowcase,VariantSelectorConfigurator) accept new optionalheadingAs/sublineAsprops (additive). - UI Kit —
CaptionFlagaccepts an optionaltextShadow?: 'none' | 'soft' | 'strong'prop. When'soft'or'strong', a layered text-shadow improves legibility against busy backgrounds. Defaults to'none', so existing consumers render unchanged. The values resolve from two new global CSS custom properties,--text-shadow-softand--text-shadow-strong, so consumers can override them per theme. - UI Kit —
HighlightedTextatom — renders a string with substring matches wrapped in<mark>. Diacritic-insensitive (NFD fold + combining-mark strip), highlights all occurrences, accepts ahighlightClassfor per-instance styling. Replaces the inline highlight loop inInputAutocomplete. - UI Kit — Opening-hours primitives in ui-kit:
useOpeningStatus(openingHours, now)composable (reactiveisOpen+ next state-change event across a 30-day horizon, respecting IANA timezone and one-off date exceptions),OpeningStatusIndicator(open/closed pill),OpeningStatusDetail(localized one-liner),OpeningStatus(Indicator + Detail combined), andOpeningHoursWeeklyTable(weekly schedule as aDescriptionList, grouping consecutive same-hours days). AddsopeningStatus.*,openingHoursWeeklyTable.*, andlocationCard.*i18n keys. (LocationCardandPaymentMethodListland in@laioutr-core/ui.) - UI Kit —
sectionBackground?: BackdropBackgroundprop onBackdrop. When set andcontainerStyle === 'boxed', an outer band element paints edge-to-edge around the constrainedOnSurfaceroot, creating two distinct color zones. Infull-widthmode the band is inert. Accepts the same value space asbackground; defaults to'none', so existing consumers render identically. - UI App — A new Section Background field-pair (
sectionBackground+customSectionBackground) on every section that exposes acontainerStyle+backgroundconfig (17 sections). It paints the edge-to-edge band around a boxed container — visible only inboxedmode, hidden infull-width. Defaults preserve current rendering. - UI Kit —
block?: booleanprop onButton(and through extensionBackgroundAwareButton), defaulting tofalse. Whentrue, the button switches toflex+width: 100%and stretches to its container width. New Storybook story:Block. - UI Kit —
laioutr:beforeModuleRegisterNuxt hook. Fires before the ui-kit module registers each upstream Nuxt module, with{ name, key, options }. Consumers can mutateoptionsto override defaults applied byregisterModule:// nuxt.config.ts export default defineNuxtConfig({ hooks: { 'laioutr:beforeModuleRegister': ({ name, options }) => { if (name === '@nuxt/image') { options.providers = { ...options.providers, customProvider: { /* … */ } }; } }, }, }); - UI App —
SectionContentGrid— hybrid slot/query wrapper.dataSource: 'slot'(default,BlockCardvia thedefaultslot) ordataSource: 'query'(auto-populated from a blog post collection). - UI App — Studio preview images (
previewSrc) for 31 blocks (plus 2 refreshed). Editors browsing the block picker now see a representative illustration for each placeable element. - UI App —
BlockHeroSliderSlidegains per-element text-shadow controls (caption, heading, subline;none/soft/strong, defaultnone) and per-slide text-size controls for heading and subline (S/M/L; defaults headingl, sublinem). Existing stored slides render unchanged.
Changed
- UI —
HeaderBasicMenu→MenuBasic. The component was a generic basic navigation menu (used by bothSectionHeaderBasicandSectionHeaderShop), not a sub-component ofHeaderBasic. Exported typeHeaderProps→MenuBasicProps,MenuItem→MenuBasicItem; root CSS class.header-basic-menu→.menu-basic.
Breaking:BlockMenuHeaderBasic→BlockMenuBasic.defineBlock({ component: 'BlockMenuHeaderBasic' })→'BlockMenuBasic'— stored project configurations that reference this block by name need a one-time migration. Studio label'Basic Header Menu'→'Basic Menu'. - UI — Breaking: the
image?: MediaImageprop onNewsletterRegistrationis removed in favor of a newmediaslot. The right-side / top-on-mobile media area is now consumer-provided via<template #media>...</template>. The legacytheme.image('newsletterRegistrationTeaser')fallback is gone; if no media is provided, the content side takes the full width. - UI — Migrated (2 sections):
SectionProductSlider,SectionProductSliderShowcaselose their dedicated "SEO" fieldset;headingElementis now stored atheadingStyle.element. Action required: anyone with storedheadingElementvalues on these two sections must re-pick the heading element in Studio once. - UI Kit — Breaking:
TextGroupprop and type renames —headingTag→headingAs,sublineTag→sublineAs;TextGroupHeadingTag→TextGroupHeadingAs,TextGroupSublineTag→TextGroupSublineAs.TextGroupSublineAsis widened to include'h1'.SwiperChromegets the same rename plus an addedsublineAs. External consumers that import the old type names or bind:heading-tag/:subline-tagmust update to the new names. - UI Kit — The
highlightMatchutility now returns segments withhighlight: boolean(wasmatched: boolean) and finds every occurrence (was only the first). - UI App — Breaking (
BlockButton): the singleiconfield is replaced byiconLeftandiconRight. Stored configs using the oldiconkey must migrate toiconRightto preserve the original right-aligned visual. A newwidthfield (hugdefault /fill) is added, and the schema is reorganized into Content and Design panels. - UI App —
SectionContentSlidernow accepts the three banner blocks (BlockBannerBasic,BlockBannerIntegrated,BlockBannerShowcase) andBlockTextin its slot, alongside the existingBlockCard/BlockMedia. - UI App —
SectionProductSlidernow exposes the standardbackgroundandcustomBackgroundcontrols in Design → Styling. - UI App — Rewrote
studio.label,description, andtagsfor all 36 sections and 37 blocks so the copy reads naturally to UX designers and e-commerce managers in Studio — removing implementation jargon and giving several blocks clearer labels (e.g.BlockIframe→ "Embedded Page",BlockMedia→ "Image or Video",BlockProductDetailCartButton→ "Add to Cart",BlockText→ "Text Block"). No runtime behavior change. - UI — Removed the strawberry-field theme.
Fixed
- UI — Fixed
FilterOffCanvas/FilterModalfilter selections never reaching the parent'sselectedFilters. Three inner filter components had moved to the canonicalmodelValuechannel, so the parent's:checkedbinding fell through as a raw HTML attribute and@update:checkedwas never emitted — brand checkboxes did not toggle, the price-range slider receivedchecked="[object Object]", and the stock-filter switch toggled visually but never propagated.
Breaking:FilterOffCanvasSwatchList's v-model channel is renamed from'checked'to the defaultmodelValue. Consumers usingv-model:checked="…"must switch tov-model="…". - UI —
SearchAutoSuggestItemnow uses the sharedHighlightedTextatom instead of its own inline highlighting, picking up diacritic-insensitive matching and all-occurrences highlighting. Visual output is unchanged. - UI Kit —
Card's body text not visually centering or end-aligning whentextAlignmentis set — the capped-width body box now follows the chosen alignment viaalign-self, matching the caption/heading/subline. - UI App —
SectionMediaTextcustom background. When an editor picked a custom color, nothing rendered because the value was passed to a non-existent prop. The custom color is now resolved intobackgrounddirectly. Preset values (none,pale,solid,default) keep working unchanged.
2.1.0
Added
- UI — Restored banner / category-slider / showcase parity with v1:
BannerBasic: new optionalsizeprop ('s' | 'm' | 'l', default'm') andaspectRatioprop.CategoryCardSlider: new optionalctaprop ({ text, link, variant }).- New
BlockBannerBasic,BlockBannerIntegrated,BlockBannerShowcaseblocks — first-class block versions of the banner trio (previously only available asSectionBanner*).BlockBannerBasicgains an optionalaspectRatioschema field. SectionCategoryCardSlider: new optionalctaschema field withctaVisiblevisibility decorator.SectionProductSliderShowcase: new optionalbannerTextSizeschema field (s/m/l, defaultm).
Changed
- UI —
SectionContentSliderremoved the cosmetic hardcodedsurface-tone="'light'"binding; surface tone now derives from the outerBackdropas intended (no rendering change). - UI — Routed elevation shadows through the
--shadow-s/--shadow-m/--shadow-ldesign tokens instead of hardcodedbox-shadowvalues, so theme overrides take effect on Card, NavigationMenuContent, Select, ContextMenu, DropdownMenu, AlertDialog, Toast, ThemeEditor, PlanCard, PersonaQuote, SearchAutoSuggest, and the variant-selector controls. Default-theme rendering is unchanged. - UI — Reverted HeroSlider height behaviour to v1.
Fixed
- UI Kit —
NavigationMenuTextItemWrapperrendering white text on a light surface when used inside a topbar (inTopbar+surfaceTone="light"). Items now correctly inherit dark text on a light background. AffectsSectionTopBarwith its defaultcolorMode="grey"and any other consumer combininginTopbarwithsurfaceTone="light". No action required.
2.0.0
A large coordinated refactor across the UI layer (UI Kit, UI, and the section/block app layer). This is the major release that unified naming, the surface-tone system, form prop contracts, and the public CSS API. The most consequential consumer-facing changes are summarized below; see the migration sub-sections at the end for diffs.
Added
- UI — New ui components:
Container(backdrop + grid + alignment composition), theMegaMenucompound (MegaMenu+MegaMenuTrigger+MegaMenuContent),BlogPostDetail,ProductDetail,ScrollAnchor,BlogPostListing, aPopUpshell + presets (PopUpInfo,PopUpNewsletter,PopUpPromotion),SortModes, andSearchAutoSuggest(+SearchAutoSuggestItem). - UI — Auto-import prefix unified to
L.@laioutr-core/uinow registers its auto-imported components with prefixL(matching UI Kit) instead ofLui. Every<LuiX>template tag pointing at auicomponent must be renamed to<LX>. Combined with the cross-package moves, consumers can apply a single<Lui→<Lsweep across templates. - UI — New UI-Kit atoms relevant to ui consumers:
IconButton,TextGroup,OnSurface(replacesOnBackground),LinkTile(replacesCategoryCard),HighlightToggle,StatusMessage,MediaStage,BadgePromotion(replacesSavingsBadge),NavLinkItem,NavSectionHeading, and the Swiper widget family moved in fromui. - UI — New sections/blocks:
SectionContentGrid,SectionEditorialGrid,SectionCategoryCardGrid,SectionProductReviews,SectionContentSlider;BlockCategoryCard,BlockUspBannerItem,BlockProductsListing,BlockFilterBar,BlockSortModes,BlockPagination,BlockMobileMenuBasic,BlockMobileMenuShop,BlockMenuSideBySide,BlockMenuHeaderBasic,BlockMegaMenu,BlockProductDetailVariantSelectorOptions. - UI Kit — New components:
IconButton(icon-only button; requiredlabelrendered asaria-label, requiredicon),TextGroup(caption + heading + subline molecule),OnSurface(surface-tone context provider, replacesOnBackground),LinkTile(replacesCategoryCard),HighlightToggle(replacesBillingCycleSwitch),StatusMessage(generic icon-led message, replacesLoginReviewPanel),MediaStage(replacesCtaBannerBase),BadgePromotion(replacesSavingsBadge, redesigned),NavLinkItem(replacesMenuLinkItem),NavSectionHeading(replacesMenuSectionTitle/FooterTitle),SwiperChrome,Iframe(sandboxed wrapper), and the reka-ui atomsTabs,Popover,ContextMenu,Listbox,InputSlider,InputAutocomplete,InputCombobox,InputPin. - UI Kit — New
useSwiperEdgeState()anduseFieldContext(props)composables. The Swiper widget family (SwiperBullets,SwiperNumbers,SwiperNavBar,SwiperArrows,SwiperScrollbar,ThumbnailsSlider) andDarkModeSwitch,StockInfoSummary(wasBuyBoxStockInfo),LanguageSwitcher,RatingProgressBarFilter,CommonSwiper, and theswiperBreakpointshelper move into ui-kit. - UI Kit — Many new locale keys (countdown, pagination, social-share, variant-selection, opening-hours, and more). New CSS tokens
--on-light-*,--on-dark-*,--on-bright-*. - UI App — New sections:
SectionContentGrid(hybrid slot/query),SectionEditorialGrid,SectionCategoryCardGrid,SectionProductReviews(replacing the legacy block-level implementation).SectionContentSlideradopts a hybrid slot/query data source (defaultslot).SectionUspBanneris reshaped: it dropsbannerStyleandcolors(moved to top-levelcolor/iconColor) and extracts itsusps[]into a newitemsslot populated byBlockUspBannerItem. - UI App — New blocks:
BlockCategoryCardandBlockUspBannerItem(non-standalone children);BlockProductsListing,BlockFilterBar,BlockSortModes,BlockPagination(split out of the previous combinedSectionProductListing);BlockMobileMenuBasicandBlockMobileMenuShop(non-standalone, hosted in header sections'mobileslots);BlockMenuSideBySideandBlockMenuHeaderBasic(non-standalone, headerdesktopslots);BlockMegaMenu(thin wrapper around the newMegaMenucompound);BlockProductDetailVariantSelectorOptions.
Changed
- UI — Breaking: extensive component renames in
ui. Final → old names include:HeaderBasic←Header;HeaderShop←ShopHeader;HeroSlider←BasicHeroSlider;HeroSlide←BasicHeroSliderSlide;MobileMenuBasic←MobileMenu;MobileMenuShop←MegaMenuMobile;MenuSideBySide←SideBySideMenu;EditorialGrid←ArticlesGrid;ContentGrid←CardGrid;ContentSlider←GalleryContentSlider;BrandList←BrandGrid;MediaText←CmsImageText;MediaGallery←LightboxGallery;SearchResultHero←SearchResultHeader;PageNotFound←Error404Page;ProductListingGrid←ProductGrid;QuoteCardSlider←QuoteSlider; the BuyBox family renamed toEnergyLabel,BenefitsBox,PriceInfo,CouponBox,QuantityDiscount,ProductTitle,AddToCart; theVariantSelector*/FilterOffCanvas*/PlanCard*families renamed. Every*Propstype, BEM block, and Storybook title follows the new name. - UI — Breaking: surface-tone migration.
BackgroundBrightness→SurfaceTone('light' | 'dark' | 'bright') across the form-input families.OnBackgroundis deleted — useOnSurface+ theuseSurfaceTone()composable.colorToBackgroundrenamed tocolorToSurfaceTone.HeaderBasicMenu'stextColoris replaced bysurfaceTonewith a semantic value flip: oldtext-color="dark"(dark text on light bg) maps to newsurface-tone="light"— invert the value, don't just rename. - UI — Breaking: Button refactor.
Button.typeprop removed (useIconButtonfor icon-only buttons). Sizes'small' | 'medium' | 'large'→'xs' | 's' | 'm' | 'l'(default'm').ButtonVariantflattened to a single 14-value union;'input-field'→'subtle','video-control'removed. Icons move from#iconLeft/#iconRightslots to typediconLeft/iconRightprops. - UI — Breaking: form prop normalization.
Fielddropserror: string(useerrorMessage+invalid);InputisError→invalid;Labelerror→invalid;SwitchandCheckboxv-model channel renamed from'checked'to defaultmodelValue(v-model:checkedcallsites must becomev-model);Selectsizeprop removed (express via:trigger="{ size: 's' }"). - UI — Breaking:
is*prefix sweep. For props where the subject is the component itself, theisprefix is dropped — e.g.Badge.isRounded→rounded,Button.isLoading→loading,Toast.isOpen→open,Pagination.isNavigationNumbers→variant,HeaderBasic.isSticky→sticky,HeroSlider.isFullHeight→fullHeight,CouponBox.isApplied→applied,ProductTileBasic.isAddToCart*→addToCart*. World-state facts (isUserLoggedIn,isSoldOut,isAboveTheFold, etc.) keepis*. - UI — Breaking: TextGroup tag-prop rename.
headingTag/sublineTag→headingAs/sublineAs; typesTextGroupHeadingTag/TextGroupSublineTag→*As.TextGroupSublineAswidened to include'h1'. - UI — Breaking: Studio data migration required for every renamed/deleted section and block component string, every renamed schema field, and every promoted/demoted section ↔ block. Highlights:
- Section renames:
SectionBasicHeader→SectionHeaderBasic,SectionImageAndContent→SectionMediaText,SectionShopHeader→SectionHeaderShop,SectionError404→SectionPageNotFound,SectionPricingPlans→SectionPlanCardSlider,SectionPricingTable→SectionPlanComparisonTable, the banner blocks promoted toSectionBanner*, and more. - Block renames:
BlockBasicHeroSliderSlide→BlockHeroSliderSlide,BlockTestimonial→BlockPersonaQuote,BlockProductGrid→BlockProductsListing,BlockCmsButton→BlockButton,BlockCmsCardContent→BlockCard, etc. - Schema fields normalized to the section-config standard across 30+ sections/blocks:
blockMargin→margin,blockPadding→padding,textAlignment→alignment,headline*→heading*,actionButton[0]→cta+ctaVisible, and thectaButton.variantenum reshaped from 12 to 14 values (e.g.white-ghost→ghost-white,input-fielddropped). - Header refactor:
HeaderBasic/HeaderShopare now slot-based; inline navigation/mobile-menu props (menuItems,mobileMenuItems,navigationMenuItems,socialLinks) are removed and provided viadesktop/mobileslots filled by the hosting Section. MegaMenu(wasMegaMenuDesktop): complete API replacement — oldcolorMode/isOpen/megaMenuItems/contentLayoutprops gone; newitems/surfaceTone/defaultValueprops and splitMegaMenuTrigger/MegaMenuContentcompound parts.
- Section renames:
- UI — Breaking: public CSS class renames following the component renames — e.g.
.cms-image-text*→.media-text*,.brand-grid*→.brand-list*,.shop-header*→.header-shop*,.product-grid*→.product-listing-grid*,.error-404-page*→.page-not-found*,.basic-hero-slider*→.hero-slider*, plusis-state-modifier prefixes dropped (.breadcrumbs-item--is-active→--active,.swatch-chip.is-selected→.swatch-chip--selected).Containeruses.s-scontaineras its root block. - UI — Breaking: locale key renames —
lightboxGallery.*→mediaGallery.*,colorSwatch.*→swatchChip.*,searchInput.*→inputSearch.*,passwordInput.*→inputPassword.*,savingsBadge.*→badgePromotion.*,shopHeader.*→headerShop.*;couponCodeInput.*removed (reusescart.couponCodeandinputSearch.clear). - UI Kit — Breaking —
Buttonrefactor.Button.typeremoved (use the newIconButton). Sizes'small' | 'medium' | 'large'→'xs' | 's' | 'm' | 'l'(default'm').ButtonVariantflattened to a single 14-value union;CtaButtonVariant/IconOnlyButtonVariantremoved.'input-field'→'subtle';'video-control'removed. Legacy alias layer dropped (LegacyButtonVariant,normalizeButtonVariant,VARIANT_ALIASES, …).iconLeft/iconRightnow acceptIconNameprops instead of#iconLeft/#iconRightslots.isLoading→loading; newspinnerType?: 'row' | 'round'.BackgroundAwareButtonVariantrebuilt to mirror the new shape — theme-levelbackgroundAwareButtonsmaps must rewrite their value entries. - UI Kit — Breaking — surface-tone migration.
OnBackgrounddeleted; useOnSurface+useSurfaceTone()(useSurfaceTone(props)resolves explicit prop > nearest ancestor context >'light').BackgroundBrightnesstype removed in favor ofSurfaceTone('light' | 'dark' | 'bright') across the Field, Input, Switch, Select, Checkbox,InputRadio, and Swatch families.colorToBackground→colorToSurfaceTone.Backdrop'scontainerBackgroundandbackgroundBrightnessprops removed — pass:backgrounddirectly.MediaPreview'smodeprop →surfaceTone.provideSurfaceToneContext→ internal_provideSurfaceToneContext(only<OnSurface>may set tone publicly). - UI Kit — Breaking — form prop normalization.
Fielddropserror: string(useerrorMessage+invalid).Input/InputAutocomplete/InputCombobox:isError→invalid.Label:error: boolean→invalid: boolean.SwitchandCheckbox: v-model channelchecked→ defaultmodelValue(v-model:checkedcall sites must becomev-model);Switchsize'small' | 'medium'→'s' | 'm'.Selectsizeprop removed (use:trigger="{ size: 's' }").InputPassword:readOnly→readonly. - UI Kit — Breaking —
is*prefix renames (dropiswhere the component is the subject):Badge.isRounded→rounded,Button.isLoading→loading,Label.isRequired→required,ThumbnailsSlider.isCentered→centered,Toast.isOpen→open,LinkTileBig.isBackgroundDark→backgroundDark,Pagination.isNavigationNumbers→variant: 'arrows' | 'numbers',OptionTileImage/OptionTileTextnotAvailableobject flattened tounavailable?+unavailableTooltip?. - UI Kit — Breaking — other prop / API changes.
Accordion.accordionStyle→variant.LoadingSpinner:type→variant, colorvariant→colorScheme, size'small' | 'large'→'s' | 'l'.IconListsize scale →'xs' | 's' | 'm'.Cardredesigned (size →'m' | 'l', many new props,Card/types.tsdeleted — importCardProps/CardCtafromCard.vue).BadgePromotionprops redesigned (BEM root.badge-promotion).Sheetdropscomponent/customClass. Various banner-refactor changes (variantprop removed from the three banners; border-radius now context-driven via.radius-contained).reka-uibumped^2.3.1→^2.9.6. - UI Kit — Numerous BEM/CSS class renames and the removal of
<style scoped>/:deep()/:global()across many components (full diff preserved below). - UI App — Breaking — section renames (Studio data migration required):
SectionBasicHeader→SectionHeaderBasic;SectionBasicHeroSlider→SectionHeroSlider;SectionImageAndContent→SectionMediaText;SectionTestimonialPersonaQuote→SectionPersonaQuoteSlider;SectionTestimonialQuoteCardSlider→SectionQuoteCardSlider;SectionBrandLink→SectionBrandList;SectionSearchResultHeader→SectionSearchResultHero;SectionPricingPlans→SectionPlanCardSlider;SectionPricingTable→SectionPlanComparisonTable;SectionPageHeader/SectionCategoryHeader→SectionPageHero;SectionShopHeader→SectionHeaderShop;SectionError404→SectionPageNotFound;SectionProductDetailContainer→SectionProductDetail;SectionLogoPresentation→ split intoSectionLogoSlider/SectionLogoGridby the oldviewdiscriminator;BlockCtaBanner{Basic,Integrated,Showcase}→ promoted toSectionBanner{Basic,Integrated,Showcase};SectionBannerContainer→ consolidated intoSectionContainer. - UI App — Breaking — block renames (Studio data migration required):
BlockBasicHeroSliderSlide→BlockHeroSliderSlide;BlockTestimonial→BlockPersonaQuote;BlockProductGrid→BlockProductsListing;BlockQueryPagination→BlockPagination;BlockQuerySorting→BlockSortModes;BlockCmsButton→BlockButton(Studio labelCMS Button→Button);BlockCmsCardContent→BlockCard;BlockSingleLogoPresentation→BlockLogoSliderSlide. - UI App — Breaking — schema field renames (Studio data migration required) normalized across 30+ sections/blocks to the section-config standard, including:
blockMargin→margin;blockPadding/innerBlockPadding→padding;textAlignment/contentPosition/contentAlignment→alignment;headline*→heading*;actionButton[0]→ctaobject +ctaVisible;accordionStyle(BlockAccordion) →variant;image(SectionNewsletterRegistration) →media;BlockIframe.url→src;SectionScrollAnchor.id→anchorName;SectionPageHero.title→heading; plus many per-section renames onSectionTopBar,SectionFooter,SectionProductSlider,SectionProductSliderShowcase,SectionCategoryCardSlider,SectionQuoteCardSlider, and others.is*-prefixed schema fields (isSticky,isSaleDesign) are kept unchanged so stored configurations continue to bind without migration. - UI App — Breaking —
ctaButton.variantvalue remap (Studio data migration required): the CTA variant enum was reshaped from 12 to 14 values. Notable remaps:white-ghost→ghost-white;white-secondary→secondary-white;black-ghost→ghost-black;white-close→close-white;black-close→close-black;glass→glass-black(verify with design);input-field→ dropped (map tosubtleor drop);black-close-always-black/white-close-always-whitelose their always-color semantics. New variants without a predecessor:tertiary,positive,danger,subtle,info. - UI App — New shared-field presets in
shared-fields/:background.ts,margin.ts,padding.ts,size.ts,visibility.ts,buttonVariant.ts,themedStyleColor.ts,button.ts,containerStyle.ts,headingElement.ts,surfaceTone.ts. Top-level panels normalized toContent+Design(withStyling/Layoutdividers) across 30+ sections/blocks. - UI App —
SectionBlogPostDetail,SectionProductDetail,SectionScrollAnchor,SectionBlogPostListing,SectionMediaText,BlockMedia, andBlockIframeare now thin configuration adapters over theiruicomponents, with no schema changes.
Removed
- UI — Breaking:
BuyBox(static demo), theInpageNavigation*family (covered byCategoryCardGrid/CategoryCardSlider),CmsContainer/ContainerContent,PageHeader,CategoryNodeButton,LightboxModal(useLightboxfrom UI Kit),FilterBarQuickFilters,MobileMenuButtonGrid,GridCardContent/GridCardTextContent, andFooterMenuItem/FooterTitle(replaced byNavLinkItem/NavSectionHeading). - UI — Breaking: components moved to UI Kit —
Card,Iframe,DarkModeSwitch,BuyBoxStockInfo,LanguageSwitcher;Media(importMediaPreviewdirectly). - UI — Breaking: sections/blocks deleted with Studio migration —
SectionBlogSlider/SectionFlexibleContentSlider→SectionContentSlider;SectionCardGrid→SectionContentGrid;SectionInpageNavigation*→SectionCategoryCardGrid/SectionCategoryCardSlider;SectionFilterBar(now block-only asBlockFilterBar);SectionShopHeaderNavigation(demoted intoBlockMenuSideBySide);BlockProductReviews→SectionProductReviews. - UI Kit — Breaking:
OnBackground(useOnSurface),BillingCycleSwitch(useHighlightToggle),CategoryCard(useLinkTile),Breadcrumb(moved to ui asBreadcrumbsItem),Menubar,Stepper,HoverCard,DiscountFlag(useBadge),SavingsBadge(useBadgePromotion),SwiperNavigationNumbersWithArrows(useSwiperNavBarwithnavigationStyle="numbers"). - UI Kit — Moved out to
@laioutr-core/ui:LightboxGallery(→MediaGallery),SearchResultHeader(→SearchResultHero),ReviewForm,SocialShare,NavigationNodeButton(→MenuSideBySideNode). - UI App — Breaking (Studio data migration required):
SectionBlogSliderandSectionFlexibleContentSlider→SectionContentSlider;SectionTestimonialQuoteCard→SectionQuoteCardSlider(single slide);SectionCardGrid→SectionContentGrid;SectionInpageNavigationBasic/Big/Compact→SectionCategoryCardGrid/SectionCategoryCardSlider;SectionAmbiendoHeader→SectionHeaderShopwithBlockMenuSideBySide;SectionShopHeaderNavigation→ demoted intoBlockMenuSideBySide;BlockProductReviews→SectionProductReviews;SectionFilterBar→ block-onlyBlockFilterBar.
Migration — imports and template tags (UI Kit)
- import OnBackground from '#ui-kit/components/OnBackground/OnBackground.vue';
+ import OnSurface from '#ui-kit/components/OnSurface/OnSurface.vue';
- import { colorToBackground } from '#ui-kit/imports/colorToBackground';
+ import { colorToSurfaceTone } from '#ui-kit/imports/colorToSurfaceTone';
- import LightboxGallery from '#ui/components/LightboxGallery/LightboxGallery.vue';
+ import MediaGallery from '#ui/components/MediaGallery/MediaGallery.vue';
- import LoginReviewPanel from '#ui-kit/components/LoginReviewPanel/LoginReviewPanel.vue';
+ import StatusMessage from '#ui-kit/components/StatusMessage/StatusMessage.vue';
- import CategoryCard from '#ui-kit/components/CategoryCard/CategoryCard.vue';
+ import LinkTile from '#ui-kit/components/LinkTile/LinkTile.vue';
- import Card from '#ui/components/Card/Card.vue';
+ import Card from '#ui-kit/components/Card/Card.vue';
- import Iframe from '#ui/components/Iframe/Iframe.vue';
+ import Iframe from '#ui-kit/components/Iframe/Iframe.vue';
- import Media from '#ui/components/Media/Media.vue';
+ import MediaPreview from '#ui-kit/components/MediaPreview/MediaPreview.vue';
- <LuiProductSlider /> <!-- old: ui registered components with the Lui prefix -->
+ <LProductSlider /> <!-- new: ui uses L, matching ui-kit. Applies to every native-ui component. -->
Migration — props and bindings (UI Kit)
- <Button type="icon" size="medium" />
+ <IconButton label="..." icon="..." size="m" />
- <Button>
- <template #iconLeft><Icon name="..." /></template>
- </Button>
+ <Button :icon-left="iconName" />
- <Button variant="input-field" />
+ <Button variant="subtle" />
- <Input :is-error="hasError" />
+ <Input :invalid="hasError" />
- <Field :error="errorString" />
+ <Field :error-message="errorString" />
+ <!-- Field auto-derives `invalid` from `errorMessage`. Pass :invalid="…" explicitly only to override. -->
- <Label :error="hasError" />
+ <Label :invalid="hasError" />
- <Select size="small" />
+ <Select :trigger="{ size: 's' }" />
- <Accordion accordion-style="..." />
+ <Accordion variant="..." />
- <SwatchChip color-mode="dark" />
+ <SwatchChip surface-tone="dark" />
- <Badge is-rounded />
+ <Badge rounded />
- <Toast :is-open="open" />
+ <Toast :open="open" />
- <Pagination :is-navigation-numbers="true" />
+ <Pagination variant="numbers" />
- <OptionTileImage :not-available="{ isNotAvailable: true, notAvailableTooltip: '…' }" />
+ <OptionTileImage unavailable unavailable-tooltip="…" />
- <LSwiperCompact />
+ <LSwiperNavBar />
- <LSwiperFadeoutArrows :has-fadeout-background="false" />
+ <LSwiperArrows :fadeout="false" />
- <LSwiperThumbnailsSlider />
+ <LThumbnailsSlider />
- <Switch v-model:checked="open" />
+ <Switch v-model="open" />
- <Checkbox v-model:checked="agreed" />
+ <Checkbox v-model="agreed" />
Migration — CSS class names (UI Kit)
- .swatch-chip.is-selected { ... }
+ .swatch-chip--selected { ... }
- .switch--small { ... }
+ .switch--s { ... }
- .round-spinner-s { ... }
+ .round-spinner--s { ... }
- .swiper-chrome__head-top { ... }
+ .swiper-chrome__head--top { ... }
- .user-avatar { ... }
+ .avatar { ... }
- .toast__progress-bar { ... }
+ .toaster-progressbar { ... }
- .login-review-panel { ... }
+ .status-message { ... }
- .swatch-preview { ... }
+ .swatch { ... }
- .swatch-item { ... }
+ .swatch-option { ... }
- .product-tiles-swatches { ... }
+ .swatch-summary { ... }
- .menu-link-item { ... }
+ .nav-link-item { ... }
- .menu-section-title { ... }
+ .nav-section-heading { ... }
- .navigation-node-button { ... }
+ .menu-side-by-side-node { ... }
- .variant-option-selector-button, .variant-option-selector-image
+ .option-tile-text, .option-tile-image
- .zoom-level-mobile, .zoom-level-desktop
+ .zoom-level-segmented, .zoom-level-stepper
- .media-block* { ... }
+ .media-preview* { ... }
Studio data migration required for every renamed or deleted section/block component string, every renamed schema field, and every promoted or demoted section ↔ block. Custom themes that defined
backgroundAwareBackdropwith'default'must rewrite those entries with the equivalentSurfaceTone('default'was a synonym for'light').
1.34.0
Added
- UI Kit — New z-index token scale in ui-kit:
--z-index-sticky(100),--z-index-modal(1400),--z-index-popover(1500),--z-index-tooltip(1600),--z-index-toast(1700). Modal overlays and content share one token so stacked modals layer correctly via DOM order.
Changed
- UI Kit — All portaled components (Dialog, Sheet, AlertDialog, DropdownMenu, Select, Tooltip, MediaLightbox) now set z-index on their content class for reliable stacking. Header sections opt out of section isolation so their sticky/fixed chrome remains visible, and
FilterBarteleports its scroll-triggered fixed bar to<body>to escape section isolation, using an IntersectionObserver instead of hardcoded scroll thresholds. - UI App — Reworked z-index architecture across the UI stack. Header sections opt out of section isolation so their sticky/fixed chrome remains visible above subsequent sections; the filter bar teleports its scroll-triggered fixed bar to
<body>to escape section isolation.
1.33.0
Added
- UI Kit —
scrollToTopprop onPagination(defaults totrue), which scrolls the viewport to the top when navigating pages. - UI App —
scrollToTopprop on Pagination (defaulttrue), which scrolls the viewport to the top when navigating pages.
Fixed
- UI Kit —
Buttonnow emitsclickevents on theNuxtLinkbranch and forwards$attrson both root elements, enabling reka-uias-childto work correctly. - UI Kit —
SectionBlogPostListingnow uses reactive computed properties for client-side pagination. - UI App — Button now emits
clickevents on the NuxtLink branch and forwards$attrson both root elements, so reka-uias-childworks correctly. - UI App —
SectionBlogPostListinguses reactive computed properties for client-side pagination.
1.32.5
Fixed
- UI — Fixed Card
RichContentprop (:content→:html), simplified SocialShare layout, and wrapped BlogPostDetail content in a container div.
1.32.4
Added
- UI App — Standalone
BlockSocialShareblock for sharing the current page via social platforms.
Fixed
- UI — Prevented breadcrumb item text from wrapping by applying single-line text-overflow with ellipsis.
1.32.2
Fixed
- UI Kit — CSS-layer order in Studio preview.
- UI App — CSS-layer order in Studio preview.
1.32.1
Fixed
- UI App — Workaround for entities not refetching on locale change.
1.32.0
Added
- UI — Added the
$tlVue global for locale translations and expanded the locale system with 13 new message groups, full German translations, and umlaut fixes. Migrated 46 components fromuseLocale().tto$tltemplate calls. - UI App —
BlockProductReviewsblock,PageHeadersection, andSearchResultsHeadersection.
Fixed
- UI — Use correct hover font-colors for some elements.
- UI App — Correct hover font colors for some elements; loading state from async watchers.
1.31.4
Fixed
- UI App — Missing
useResolvedLinkimport inSectionBreadcrumbsthat caused a runtime error.
1.31.0
Added
- UI Kit —
$durationformatter for ISO 8601 duration strings. UsesIntl.DurationFormatwith locale awareness when available, falling back to narrow-style manual formatting (1h 30m 5s). Available as template global and auto-import. - UI App —
LanguageSwitchernow uses market-domain data for available locales; locale props are threaded through Header, TopBar, MobileMenu, and MegaMenu components, and sections useuseAvailableLocalesfor navigation-based locale switching.
Changed
- UI —
LanguageSwitchernow uses market-domain data for available locales instead of the removeduseLocalesDatacomposable. Locale props are threaded through Header, TopBar, MobileMenu, and MegaMenu components, and sections useuseAvailableLocalesfor navigation-based locale switching. - UI — Migrated URL fields from
texttolinktype and resolve links throughlinkResolverfor market-aware URL resolution. - UI Kit —
$moneyand$measurementi18n formatters moved from frontend-core to ui-kit, removing dead code. Template globals and auto-imports work identically — no consumer changes needed. - UI App — URL fields migrated from
texttolinktype and resolved throughlinkResolverfor market-aware URL resolution.
Deprecated
- UI App —
useLinkResolveris converted to a singletonlinkResolverobject that no longer requires a setup context.useLinkResolveris preserved as a deprecated re-export. UnuseduseSwitchLanguagePathanduseSwitchMarketUrlwrappers were removed.
1.30.0
Added
- UI Kit — An ESLint rule (
laioutr/require-css-layer) that enforces all new component CSS is layered.
Changed
- UI — Wrapped all component CSS in
@layer lui-componentsfor cascade layer control, allowing consumers to override component styles with unlayered CSS without needing!important. Existing@layer lui-overridableblocks (Icon, Text) are preserved. A new ESLint rule (laioutr/require-css-layer) enforces that all new component CSS is layered. - UI Kit — All component CSS is now wrapped in
@layer lui-components, allowing consumers to override component styles with unlayered CSS without needing!important. Existing@layer lui-overridableblocks (Icon, Text) are preserved. - UI App — All component CSS is now wrapped in
@layer lui-componentsfor cascade-layer control.
1.29.3
Fixed
- UI Kit —
ThemeSwitcherimport andThemeWithMeta. - UI Kit —
Inputprop fallthrough and theShopHeaderclose-button. - UI App — ThemeSwitcher import and
ThemeWithMeta; Input prop fallthrough and ShopHeader close button.
1.29.2
Fixed
- UI App — Correctly merge theme images.
1.29.1
Fixed
- UI App — Do not show the theme switcher when it is not enabled.
1.29.0
Added
- UI Kit — Theme switcher support.
- UI App — Theme switcher support.
1.28.1
Changed
- UI — Show filter-bar sorting only if sortings are available; made the BuyBoxStockInfo icon optional.
1.28.0
Changed
- UI — Fixed links in the basic header and the logo banner, made
SectionLogoPresentationrespect link fields, fixed pricing table width, and fixed mobile menu hierarchy inSectionBasicHeader.
Fixed
- UI App — Links in the basic header.
1.27.0
Added
- UI App — New Iframe block.
Fixed
- UI Kit —
HeroSliderMedia loading eagerness. - UI App — HeroSlider media loading eagerness.
1.26.1
Changed
- UI App —
SectionBreadcrumbsuses theBreadcrumbItemcanonical type.
1.26.0
Added
- UI — Implemented the Breadcrumb section.
- UI App — Breadcrumb section.
1.25.0
Added
- UI — Added the Error 404, Pricing Plans, and Pricing Table sections.
- UI App — Error 404, Pricing Plans, and Pricing Table sections.
1.24.3
Fixed
- UI — Fixed a hydration error that broke the page when the Header component with submenus was used.
- UI App — Hydration error breaking the page when a Header component with submenus is used.
1.24.2
Fixed
- UI App —
FlexibleContentSliderSectionbreaking whenactionButtonis missing.
1.24.1
Added
- UI App —
WellKnownSectionTagand section tags.
1.24.0
Added
- UI — Added support for multiple CTAs in the basic header.
- UI — Added support for calling PDPs with variant option-values.
- UI App — Support for multiple CTAs in the basic header.
- UI App — Support for calling PDPs with variant option values.
1.23.0
Added
- UI App — Missing testimonial sections.
1.22.0
Added
- UI App — ProductShowcaseSlider section.
Fixed
- UI App — Footer width and background.
1.21.3
Added
- UI App — Support for a variant query parameter in
ProductDetailContainer.
1.21.1
Fixed
- UI App — Links with references are now case-insensitive to their
type.
1.21.0
Added
- UI App — Autosuggest in the shop header on mobile.
1.20.0
Added
- UI — Implemented the SearchOverlay with actual data.
- UI Kit —
SearchOverlayimplemented with actual data. - UI App — SearchOverlay wired to actual data.
1.19.0
Added
- UI App —
ConnectedCartcomponent for use in headers.
1.18.0
Added
- UI App — Correct
SectionBasicHeaderimplementation.
1.17.7
Fixed
- UI App —
SectionIntegratedHeaderrecursion bug.
1.17.0
Added
- UI App — Ability to select a root menu item in the Integrated Shop Header; the root menu places all of its children as navigation items.
1.16.0
Added
- UI App — Basic strawberry-field theme.
1.15.0
Added
- UI App — Integrated Shop Header section.
1.14.0
Added
- UI App —
SectionShopHeaderopens an empty cart; sold-out status shown in DetailAddToCart with LightboxModal connected to the product image gallery; detail variant switcher and add-to-cart toast.
1.13.0
Added
- UI App — Filter bar implementation and type adjustments.
Fixed
- UI App — Removed duplicate product info from
BlockProductDetailDetails.
1.12.0
Added
- UI App — Remote
QueryTemplates.
1.11.0
Changed
- UI App — Studio refresh compatibility.
1.10.0
Changed
- UI App — Improved LCP loading strategy.
1.9.0
Fixed
- UI App — Missing links; added
robots.txtand page SEO meta.
1.6.0
Fixed
- UI App — CSS layer order and swiper styles.
1.5.0
Added
- UI App — Adjusted definition default behaviour; added several sections and blocks.
0.20.0
Changed
- UI — Updated the cart-sheet implementation.
Fixed
- UI Kit — Minor type fixes.
0.19.11
Fixed
- UI Kit — Worked around a nuxt/image Hygraph provider bug that returned broken image URLs.
0.19.7
Fixed
- UI Kit — Prevent a frontend crash when the
Mediacomponent is not provided amediaprop.
0.19.0
Added
- UI Kit —
RichContentsupport for anHtmlFragmentprop.
0.18.0
Added
- UI Kit — A basic
strawberry-fieldtheme.
0.17.5
Changed
- UI — Shopware implementation, minor UI fixes, and quality-of-life improvements.
0.17.4
Fixed
- UI Kit — Build issue.
0.17.0
Added
- UI — Added the Integrated Shop Header section.
0.16.0
Added
- UI —
SectionShopHeaderopens an empty cart. - UI — Show
isSoldOutstatus in DetailAddToCart and connect LightboxModal to ProductImageGallery. - UI Kit — Detail variant switcher and add-to-cart toast.
Fixed
- UI — Fixed a missing import in CartSheet, IconButton sizing, and variant preview sizes.
0.15.0
Added
- UI — Adjusted types and implemented the filter bar.
- UI Kit — Filter bar implementation and type adjustments.
0.14.3
Fixed
- UI Kit —
SectionShopHeaderNavigationnow respects menu data; fixedRichContentmargins.
0.14.1
Changed
- UI — Studio refresh compatibility.
Added
- UI Kit — Studio refresh compatibility.
0.14.0
Changed
- UI — Improved the LCP loading strategy.
- UI Kit — Improved LCP loading strategy.
0.13.0
Changed
- UI — Accessibility improvements for desktop navigation and slider navigation.
- UI Kit — Accessibility improvements for desktop navigation and slider navigation.
0.12.0 – 0.11.0
Added
- UI — Fixed missing links, added
robots.txtand page SEO meta.
Fixed
- UI Kit — Missing links; added
robots.txtand page SEO meta.
0.10.4
Changed
- UI Kit — Default color-mode set to
lightinstead ofsystem, since most e-commerce frontends do not provide a dark mode and being greeted with dark mode could confuse users.
0.10.2
Fixed
- UI — Fixed Shopify product prices and made minor frontend adjustments.
- UI Kit — Shopify product prices and minor frontend adjustments.
0.10.0
Changed
- UI — Fixed CSS layer order and Swiper styles.
Fixed
- UI Kit — CSS layer order and swiper styles.
0.9.0 – 0.8.0
Added
- UI — Added a few sections and blocks; changed definition default behaviour.
- UI Kit — Adjusted definition default behaviour; added several sections and blocks to ui-app.
0.7.1
Fixed
- UI Kit — Import paths.
0.6.0
Added
- UI — Added section definitions in preparation for the Shopify demo.
- UI Kit — Section definitions; preparation for the Shopify demo.
0.5.0
Changed
- UI — Migrated base components to UI Kit.
Added
- UI Kit — Migrated base components into the UI Kit.
0.4.0
Added
- UI — Media Library upload handling and improved documentation generation from canonical types.
earlier versions
Earlier 0.x releases and the intervening patch releases not listed above contained only internal changes and cross-package dependency updates with no user-facing UI changes. For the Section and Block layer, initial development (1.1.0 – 1.4.0) covered the first section definitions, preparation for the Shopify demo, theme loading via ui-kit, and early ShopHeader login wiring.