{
"items": [
{
"id": "Post Id",
"title": "Sample title",
"excerpt": "Sample excerpt text describing the blog post.",
"alias": "sample-post-alias",
"categoryTitle": "Sample category",
"categoryLink": "sample-category",
"publishedAt": "2026-01-01T00:00:00+00:00"
},
{
"id": "Post Id",
"title": "Sample title",
"excerpt": "Sample excerpt text describing the blog post.",
"alias": "sample-post-alias",
"categoryTitle": "Sample category",
"categoryLink": "sample-category",
"publishedAt": "2025-01-01T00:00:00+00:00",
"mediaItem": {
"id": "Image Id",
"link": "https://example.com/sample",
"mediaType": "Image",
"variants": [
{
"size": "small",
"link": "https://example.com/sample-small",
"width": 150
},
{
"size": "medium",
"link": "https://example.com/sample-medium",
"width": 300
},
{
"size": "large",
"link": "https://example.com/sample-large",
"width": 600
},
{
"size": "xlarge",
"link": "https://example.com/sample-xlarge",
"width": 1000
}
]
}
},
...
],
"name": "BlogHighlights",
"view": "Default",
"section": "SectionA",
"settings": {
"blogIds": [],
"id": "Component Id",
"section": "SectionA",
"type": "NoirBlogHighlights",
"name": "BlogHighlights",
"configuredInContentApi": true,
"view": "Default",
"displayName": "Sample display name",
"cssClass": "Sample cssClass",
"header": "Sample header"
},
"translations": {
"seeAll": "Sample translation",
"noImageAltAvailable": "Sample translation",
"linkTo": "Sample translation"
}
}items[]items is present and items.size > 0.settings.idcomp-{{ id }}blogHighlightsCarousel-{{ id }}swiper-pagination-{{ id }}settings.cssClass(UNDEFINED).settings.headersettings.Alignment (string enum)Center, Right, or any other value (default/left). Defaults to left-aligned on desktop, centered on mobile.item.categoryTitle / item.categoryLinkitem.excerptitem.mediaItemlink, falls back to GlobalData.Settings.noImage.link, and if that is also empty, to the theme's built-in Assets/images/no-image.svg.items is empty.lg: and up): a two-column grid — a divided list of post titles/excerpts on the left, and a stack of absolutely-positioned images on the right. Only the image matching activeIndex is visible (cross-fade via opacity + z-index).@mouseenter / @focusin) calls setActive(index), which updates activeIndex and expands that item's excerpt (grid-rows-[1fr] transition) while collapsing the others.lg:): renders a Swiper carousel of post cards instead of the two-column layout; slideChange keeps activeIndex in sync with the active slide.item.mediaItem/item.mediaItem.link is missing./blog/posts) at the bottom.x-data='bloghighlightsdefault.initComponent({{ items | serialize | escape }})'initComponent(items) returns the Alpine component state + methods for tracking the active highlighted post and booting the mobile Swiper carousel.items, activeIndex, swiperInstanceinit()setActive(index), swiperInit()this.$nextTick(() => this.swiperInit()) so the Swiper markup is present in the DOM before initialization.activeIndex to the given index. Called from the desktop list on @mouseenter/@focusin and from the mobile Swiper slideChange callback, so the active title/excerpt and the active image stay in sync regardless of layout.#blogHighlightsCarousel-{id}), where id is this.$root.id with the comp- prefix stripped and escaped via CSS.escape.slidesPerView: 1, slidesPerGroup: 1, spaceBetween: 24)..swiper-pagination-{id} when present.if (!this.swiperInstance)).on.slideChange callback: sets activeIndex to swiper.realIndex, keeping the active state in sync when the user swipes on mobile.items list independently (two separate DOM trees toggled via hidden lg:grid / lg:hidden), rather than one shared list that's restyled — keep both in sync when changing markup.