Guide

Carousel Controller

Organize content in a vertical or horizontal CSS carousel with navigation arrows and dots.

Block Metadata

PropertyValue
Nameprc-block/carousel-controller
TitleCarousel
Categorydesign
Version1.0.0
API3
Textdomaincarousel-controller
Keywordsscroll, carousel, slider
ExampleYes (one carousel-slide with core/paragraph — inserter preview)

Allowed Inner Blocks

Only prc-block/carousel-slide blocks are allowed inside this controller.

Attributes

AttributeTypeDefaultEnum ValuesDescription
viewTypestring"horizontal"horizontal, vertical, coverflowCarousel layout: horizontal scroll, vertical scroll, or coverflow (3D stacked slides).
enableDotsbooleantrueShow dot navigation indicators below (or beside) the carousel.
useSlideBgForDotsbooleanfalseColor each dot from its slide’s background color instead of dotColor.
enableArrowsbooleantrueShow previous/next arrow buttons.
enableRewindbooleantrueAllow the carousel to wrap around from last to first slide and vice versa (frontend).
arrowsSizestring"medium"small, medium, largeSize of the navigation arrows.
dotsSizestring"small"small, medium, largeSize of the dot indicators.
dotColorstring"black"Color for the dot indicators (ignored when useSlideBgForDots is true).
arrowColorstring"black"Color for the arrow buttons.
editorActiveSlideIndexnumberEditor-only index of the visible slide (role: local). Not saved to post content.

Supports

FeatureEnabledNotes
HTML editingNo
AlignWide, Full
Interactivity APIYes
Spacing: marginYes (top, bottom)
Spacing: paddingYes
Typography: fontSizeYes (default control)
Typography: fontFamilyYes (default control)
ShadowYes
Color: backgroundYes
Color: textYes
Color: buttonYes
Color: gradientsYes
Color: headingYes
Color: linkYes
Background: color, gradient, imageYes
Border: radius, color, width, styleYes

Usage Instructions

  1. Insert the Carousel block from the inserter.
  2. The first slide is created automatically with a paragraph placeholder.
  3. Add content to the slide (images, text, any blocks).
  4. Click the + appender to add more slides. New slides inherit style attributes from existing ones.
  5. Use the arrow buttons or dot indicators in the editor to preview slides. Navigation updates a local active-slide index (see Editor Enhancements), so arrows and dots work even when the carousel controller itself is selected rather than a slide.
  6. In the sidebar, configure:
    • View Type: Horizontal (default), vertical, or coverflow.
    • Enable Dots: Toggle dot navigation; optionally use each slide’s background for dot color.
    • Enable Arrows: Toggle arrow navigation.
    • Arrows Size / Dots Size: Small, medium, or large.
    • Dot Color / Arrow Color: Customize navigation element colors (when not using slide backgrounds for dots).

Inserter preview

The example in block.json matches the default template shape: a single prc-block/carousel-slide containing a core/paragraph with sample slide text.

<!-- wp:prc-block/carousel-controller -->
<!-- wp:prc-block/carousel-slide -->
<!-- wp:paragraph -->
<p>Carousel slide content.</p>
<!-- /wp:paragraph -->
<!-- /wp:prc-block/carousel-slide -->
<!-- /wp:prc-block/carousel-controller -->

Block Markup Example

<div
	class="wp-block-prc-block-carousel-controller has-arrows-medium has-dots-small"
	id="prc-block-carousel-controller-abc123"
	data-wp-interactive="prc-block/carousel-controller"
	data-wp-context='{"id":"...","enabled":false,"slideIndex":0,"count":3,"orientation":"horizontal","slides":[...]}'
	data-wp-init="callbacks.onInit"
	data-wp-class--is-enabled="context.enabled"
	data-wp-class--is-selected="context.isSelected"
	data-wp-on--mouseenter="callbacks.onMouseEnter"
	data-wp-on--mouseleave="callbacks.onMouseLeave"
	data-wp-on-document--scroll="callbacks.onCoverScroll"
	style="--prc-carousel-controller-dot-color: var(--wp--preset--color--black); --prc-carousel-controller-arrow-color: var(--wp--preset--color--black);"
>
	<div class="prc-block-carousel-controller__track">
		<div class="prc-block-carousel-controller__track__inner">
			<!-- Carousel slides rendered here -->
		</div>
	</div>
	<button
		class="prc-block-carousel-controller__arrow prc-block-carousel-controller__arrow__prev"
		data-wp-on--click="actions.goToPreviousSlide"
		aria-label="Previous slide"
	>
		<!-- chevron icon -->
	</button>
	<button
		class="prc-block-carousel-controller__arrow prc-block-carousel-controller__arrow__next"
		data-wp-on--click="actions.goToNextSlide"
		aria-label="Next slide"
	>
		<!-- chevron icon -->
	</button>
	<div class="prc-block-carousel-controller__dots">
		<template data-wp-each--dot="context.slides">
			<button
				class="prc-block-carousel-controller__dot"
				data-wp-on--click="actions.goToDot"
				data-wp-bind--data-slide-index="context.dot.index"
				data-wp-bind--aria-label="context.dot.label"
				data-wp-bind--data-active="callbacks.isDotActive"
			>
				<!-- circle icon -->
			</button>
		</template>
	</div>
</div>

Editor Enhancements

Files: use-editor-active-slide.js, edit.jsx, navigation-components.jsx

use-editor-active-slide.js

  • Tracks the visible slide with a local editorActiveSlideIndex attribute (block.json), decoupled from which block is currently selected in the list view or canvas.
  • Exposes setActiveIndex(index) to move the preview: updates the local index via __unstableMarkNextChangeAsNotPersistent, then selects the target slide so the canvas follows.
  • When you select a slide or content inside a slide, the active index syncs to match that slide.
  • When slides are added or removed, the stored index is clamped (clampSlideIndex) so it stays within bounds.

navigation-components.jsx

  • Dots call onNavigateToSlide(index) (wired to setActiveIndex) instead of selectBlock on the slide client id.
  • PreviousArrow / NextArrow use onNavigate callbacks and disabled at the first/last slide (no rewind in the editor, unlike the frontend when enableRewind is on).

This keeps editor preview aligned with frontend navigation when the controller wrapper is selected, which is the common case while adjusting carousel settings.

PHP Rendering

The render_block_callback in class-carousel-controller.php:

  1. Reads block attributes and counts inner blocks (slides).
  2. Generates a unique block ID.
  3. Adds Interactivity API directives to the wrapper: data-wp-interactive, data-wp-context, data-wp-init, data-wp-class--is-enabled, data-wp-class--is-selected, mouse event handlers, and scroll handlers.
  4. Iterates over each .wp-block-prc-block-carousel-slide to assign unique IDs, slide indexes, and data-wp-class--is-active directives.
  5. Sets CSS custom properties for dot and arrow colors.
  6. Injects arrow navigation if arrows are enabled, replacing the empty .prc-block-carousel-controller__arrows placeholder.
  7. Injects dot navigation markup (using <template data-wp-each>) if dots are enabled, replacing the empty .prc-block-carousel-controller__dots placeholder.
  8. Arrow icons change based on orientation: chevron-left/chevron-right for horizontal, chevron-up/chevron-down for vertical.

Frontend Interactivity

The view.js file registers an Interactivity API store under prc-block/carousel-controller.

State (Derived)

PropertyDescription
isInsideCoverWhether the carousel is nested inside a core/cover block.
coverRefReference to the parent cover block element.
isVerticalWhether orientation is vertical.
trackThe .prc-block-carousel-controller__track__inner DOM element.
hasNextSlideWhether there is a next slide available.
hasPreviousSlideWhether there is a previous slide available.
isActiveWhether the current slide element matches the active slide index.

Actions

ActionDescription
navigateToSlide(index)Scrolls the track to the specified slide index with smooth behavior.
goToDot()Navigates to the slide corresponding to the clicked dot.
goToNextSlide()Advances to the next slide; wraps to the first slide if at the end.
goToPreviousSlide()Goes to the previous slide; wraps to the last slide if at the beginning.
resetCarousel()Resets the carousel to the first slide.

Callbacks

CallbackDescription
onInitSets up a debounced scroll listener on the track to calculate the current slide index from scroll position. Enables the carousel immediately unless inside a cover block.
isDotActiveReturns whether a dot's index matches the current slide index.
onMouseEnter / onMouseLeaveTracks hover state for isSelected context.
onCoverScrollSpecial behavior for carousels inside cover blocks: enables the carousel when the cover reaches the top of the viewport while scrolling down, temporarily locks body scroll, and resets when scrolling past.
onCoverFinalSideDisableDisables the carousel when it reaches the last slide inside a cover block.

Cover Block Integration

When the carousel is placed inside a core/cover block, it has special scroll-triggered behavior:

  • The carousel starts disabled.
  • When the user scrolls the cover into view (top of viewport), the carousel enables and body scroll is temporarily locked (2 seconds).
  • After the last slide, the carousel disables to allow normal page scrolling to continue.
  • The carousel resets when scrolled completely out of view.

Layout

The carousel uses CSS Grid for its layout:

Horizontal layout:

arrow-prev | track | arrow-next
   dots    | dots  |   dots

Vertical layout:

arrow-prev
track
arrow-next
dots

The track uses CSS scroll-snap (scroll-snap-type: x mandatory or y mandatory) for smooth, physics-based slide snapping. Scrollbars are hidden across all browsers.

Styles

  • Minimum height: 200px.
  • Arrows use no background, with configurable icon color via --prc-carousel-controller-arrow-color.
  • Dots are opacity-based: 0.4 for inactive, 1.0 for active, with a 0.3s transition.
  • Three size variants for arrows (has-arrows-small/medium/large: 1rem/1.5rem/2rem icon size).
  • Three size variants for dots (has-dots-small/medium/large: 0.5rem/0.75rem/1rem icon size).
  • When not enabled (.is-enabled is absent), the carousel has pointer-events: none.

Was this helpful?