BlazeSlider
A wrapper around the blaze-slider library that adapts it to React conventions, exposes slider state via context, and adds a11y / analytics hooks. Replaces the Swiper-based carousel components that previously lived in ~/components/ProductCarousel, ~/components/SwoggleCarousel, etc.
Why we moved off Swiper (pre-mortem)โ
Documenting up-front so the next person reading the diff understands the trade-offs we accepted.
The case for movingโ
- Bundle size.
swiperships ~140 kB minified across its modules;blaze-slideris ~3.5 kB. Carousels appear on every PDP, PLP, and homepage block, so this is paid on most page loads. - DOM reuse.
blaze-sliderphysically reorders existing DOM nodes for loop wrapping (track.append/track.prepend) instead of cloning slides like Swiper. With React-rendered children that contain images, video, favorite buttons, and quick-add forms, cloning was producing duplicate-key warnings and double-mounting fetchers. - Imperative escape hatch. Swiper's React bindings hide the underlying instance behind
onSwiper/swiperRef.current, and several lifecycle behaviors (e.g.slideToduring initial render,update()after dynamic children) are racy.blaze-sliderexposes a plain class instance โ we read state viaslider.states[slider.stateIndex].pageand write viaslider.next(N)/slider.prev(N)directly. - Reconciliation safety. Because
blaze-sliderreorders DOM nodes, React must NOT re-render the slide elements during navigation โ otherwise reconciliation snaps them back into source order, causing a visible flash. OurBlazeSlidecomponent is intentionally context-unaware (it never re-renders on slider state changes); per-slide visibility / focus updates happen via direct DOM writes insyncSlideDOM. This separation is hard to retrofit onto Swiper's render-prop API. - ARIA. We can apply
aria-roledescription="carousel",aria-livepolicies, scoped headings, and per-slidearia-label="N of M"on our own terms instead of fighting Swiper's a11y module.
What could still bite usโ
- Inline styles vs. scoped CSS.
blaze-sliderwrites--slides-to-showand--slide-gapas inline styles on the slider root, which beats any stylesheet rule. OuruseCustomBlazeSliderhook re-asserts the configured value viapreserveSlideWidth()after blaze auto-clamps for static mode. If you find a carousel rendering 1 slide instead of N, suspect this path. - Loop wrapping race conditions. Loop mode involves a
setTimeout(transitionDuration) + DOM reorder + doublerequestAnimationFrame`` sequence. Calls toslider.next/prevwhileisTransitioningis true are ignored โ make sure programmatic navigation (e.g. vianavigateToSlide) waits for the previous transition. - Strict-mode double mount in dev. Initial-slide DOM reordering is idempotent (uses
data-slide-indexto figure out current order), but if you add new imperative effects, verify they're idempotent too.
Component anatomyโ
<BlazeSlider> โ BlazeSlideContext.Provider lives here
<BlazeButton direction="prev" /> โ reads context, calls slider.prev()
<BlazeTrack> โ wraps the .blaze-track DOM blaze needs
<BlazeSlide index={0}>... โ intentionally context-UNAWARE (don't re-render!)
<BlazeSlide index={1}>...
</BlazeTrack>
<BlazeButton direction="next" />
</BlazeSlider>
| Component | Purpose | Reads context? |
|---|---|---|
BlazeSlider | Provider. Owns the lifecycle of the underlying blaze-slider instance via useCustomBlazeSlider. Renders scoped CSS for --slides-to-show / --slide-gap per breakpoint. | Provides |
BlazeTrack | Renders the .blaze-track-container + .blaze-track DOM. Two render modes (see below). | Yes โ isStatic, visibleIndices |
BlazeSlide | Wraps each slide's content; sets data-slide-index so blaze can find it. Intentionally never re-renders on context change to avoid fighting blaze's DOM reordering during loop wrapping. | No |
BlazeButton | prev / next nav button. Uses context (isAtStart, isAtEnd, slider.prev/next) instead of .blaze-prev / .blaze-next class lookup so nested sliders work. | Yes |
CarouselCounter | Optional 1 / N indicator that follows the slider state. | Yes |
Type quick-referenceโ
Live in lib/types.d.ts:
BlazeSliderConfigโ slidesToShow / scroll / gap / breakpoints / loop / autoplay / transition.SimpleBlazeSliderProps<T>โ simple mode (slides + slideRenderer + button/track/slide forwarding).ComposableBlazeSliderPropsโ composable mode (children).BlazeTrackProps<T>โ both children mode (children) and renderer mode (items + slideRenderer + getSlideKey + slideProps). Selected at runtime by the presence ofslideRenderer.BlazeButtonPropsโ direction + chevronSize + analytics hooks.BlazeSlideProps<T>โindex(required, used asdata-slide-index) + optionaltagfor semantic markup.
Existing call-sitesโ
For reference patterns:
| Site | Mode | Notable bits |
|---|---|---|
ProductCarousel | Composable + BlazeTrack renderer | Featured-mode CSS scaling; slidesToScroll: 1 when featured for initialSlide to map cleanly. |
TabbedProductCarousel | Composable + BlazeTrack renderer | All tabs mounted; CSS-hidden via [hidden]. |
SwoggleCarousel | Composable, custom inner component | Two paired sliders kept in positional sync via navigateToSlide on tab change. |
RecentlyViewedCarousel | Composable | Local ProductCarousel (not the shared one). |